tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 614Markdown 274TOML 149RON 310606 Definiciones

⎇ main llimphi-ui/examples/counter_web.html HTML · 109 líneas · 5.1 KB Historia crudo

chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros d251eb32b · Sergio ·

1<!doctype html>
2<html lang="es">
3<head>
4<meta charset="utf-8">
5<meta name="viewport" content="width=device-width, initial-scale=1">
6<title>llimphi · counter web</title>
7<style>
8  /* Magenta a propósito, y NO el color de la app. Es un **centinela**: si en
9     una toma aparece magenta, el canvas no pintó. Con un fondo parecido al de
10     la app (lo que había antes) un canvas vacío se ve idéntico a uno pintado,
11     y el arnés `probar-llimphi-web.sh` daba un falso «PINTADO=SI». */
12  html, body { margin: 0; height: 100%; background: #ff00ff; }
13  /* El canvas se dimensiona por CSS; el backing store lo pone winit según el
14     devicePixelRatio. `display:block` evita la línea base de texto que le
15     agrega unos píxeles de más a un canvas inline. */
16  #llimphi { display: block; width: 100vw; height: 100vh; }
17  #aviso {
18    position: absolute; inset: 0; display: none;
19    align-items: center; justify-content: center;
20    color: #c8d2e0; font: 16px/1.6 system-ui, sans-serif; text-align: center;
21    padding: 2rem;
22  }
23</style>
24</head>
25<body>
26<canvas id="llimphi"></canvas>
27<div id="aviso">
28  Este navegador no expone <b>WebGPU</b>.<br>
29  Llimphi rasteriza con vello por compute shader, y WebGL2 no tiene storage
30  textures — ver <code>PORTING.md</code> §wasm32.
31</div>
32<script type="module">
33  // Aviso temprano y legible: sin esto el fallo aparece como un error suelto
34  // en la consola y la página queda en negro sin explicar por qué.
35  if (!navigator.gpu) {
36    document.getElementById('aviso').style.display = 'flex';
37  }
38  import init from './pkg/counter_web.js';
39  // `init()` instancia el módulo y dispara el `#[wasm_bindgen(start)]`.
40  init().catch((e) => console.error('llimphi · no arrancó:', e));
41
42  // --- Sonda para el arnés headless (`scripts/probar-llimphi-web.sh`) ---
43  // La página se mide a sí misma y lo dice por consola. Es mejor que el
44  // `--screenshot` de Chrome por dos razones: mide el CANVAS y no la página
45  // compuesta (el fondo del documento no puede disfrazarse de render), y
46  // corre en tiempo real, sin que `--virtual-time-budget` adelante los
47  // timers mientras el trabajo de GPU sigue tardando lo que tarda.
48  // Inofensiva en uso normal: son unos pocos readbacks de 64×64 y para.
49  const cv = document.getElementById('llimphi');
50  const mini = document.createElement('canvas');
51  mini.width = 64; mini.height = 64;
52  const g = mini.getContext('2d', { willReadFrequently: true });
53  // ⚠️ LÍMITE DE ESTA SONDA, medido en metal el 2026-08-03 — leer antes de
54  // sacar conclusiones de lo que imprime. `drawImage` sobre un canvas WebGPU
55  // devuelve **transparente** cuando el canvas dejó de redibujar, aunque el
56  // compositor siga mostrando el último cuadro perfectamente. Y llimphi corre
57  // un bucle Elm: presenta cuando cambia el estado y después se calla. O sea
58  // que en la app quieta esta sonda da `colores=1 rgba(0,0,0,0)` **siempre**,
59  // esté pintada o no. Comprobado con WebGPU puro, sin una línea de Rust: un
60  // canvas que se limpia a verde dentro de un `rAF` cada cuadro se lee
61  // `rgba(0,255,0,255)` desde cualquier lado (rAF o timer), y el mismo clear
62  // hecho UNA sola vez se lee `rgba(0,0,0,0)` medio segundo después.
63  // Por eso un `rgba(0,0,0,0)` acá NO prueba que no pintó. El juez de verdad
64  // es una captura de pantalla: el fondo magenta del documento delata un
65  // canvas vacío, y contar píxeles del color de la app lo confirma.
66  let intentos = 0;
67  let timer = null;
68  const medir = () => {
69    intentos++;
70    let colores = 0, muestra = '';
71    try {
72      g.clearRect(0, 0, 64, 64);
73      g.drawImage(cv, 0, 0, 64, 64);
74      const d = g.getImageData(0, 0, 64, 64).data;
75      const set = new Set();
76      for (let i = 0; i < d.length; i += 4) {
77        set.add((d[i] << 16) | (d[i + 1] << 8) | d[i + 2]);
78      }
79      colores = set.size;
80      // El ALFA es imprescindible: un canvas transparente y uno pintado de
81      // negro se leen los dos `rgb(0,0,0)`. Sin alpha la sonda no los
82      // distingue y el diagnóstico se va por el camino equivocado.
83      muestra = `rgba(${d[0]},${d[1]},${d[2]},${d[3]})`;
84    } catch (e) {
85      console.log(`llimphi-sonda: readback falló: ${e}`);
86      clearInterval(timer);
87      return;
88    }
89    // >1 color = el árbol de vista llegó a píxeles. 1 color puede ser un
90    // canvas en blanco, un fondo liso, O la app quieta (ver el bloque de
91    // arriba): por eso se informa el alfa y se dice que no es concluyente.
92    const veredicto = colores > 1
93      ? 'PINTADO'
94      : (muestra === 'rgba(0,0,0,0)'
95          ? 'no concluyente (la app puede haber dejado de redibujar) — juzgá por captura'
96          : 'un solo color');
97    console.log(
98      `llimphi-sonda: intento=${intentos} canvas=${cv.width}x${cv.height} ` +
99      `colores=${colores} esquina=${muestra} · ${veredicto}`
100    );
101    if (colores > 1 || intentos >= 20) clearInterval(timer);
102  };
103  // Medir DENTRO de un `requestAnimationFrame`: es la única ventana en la que
104  // el canvas puede estar vigente. Desde el timer pelado la lectura es aún
105  // menos confiable.
106  timer = setInterval(() => requestAnimationFrame(medir), 400);
107</script>
108</body>
109</html>