Documentacion en catalan

El ingles se queda como fuente canonica (docs/*.en.md, plantilles/template-*)
y el resto pasa a catalan: el agente, la skill, la guia y la referencia
(.es.md -> .ca.md), el documento del aparato (el-aparato -> l-aparell), el
README, el LLEGEIX-ME de instrumentacio y los dos diagnosticos.

Renombradas tambien las carpetas: plantillas/ -> plantilles/,
instrumentacion/ -> instrumentacio/. Los enlaces de la documentacion inglesa
apuntan ya a los nombres nuevos.

Los comentarios de los .frag van sin acentos, en catalan igual que antes en
castellano: el driver de la Pi rechaza los bytes no-ASCII incluso dentro de
comentarios y el shader da pantalla verde sin avisar.

Se quedan en castellano a proposito, y esta dicho en el README: los nombres de
variable y las cadenas de log de la instrumentacion --fps-por-shader.py los
busca tal cual y los registros ya capturados del aparato los llevan asi-- y
los mensajes de check_port.py, que habla ingles y castellano.

Generado con generar-agente-portable.py. No editar aqui.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Xose
2026-09-05 16:43:37 +02:00
parent df958a3237
commit 593abbb06d
26 changed files with 2219 additions and 2219 deletions
+681
View File
@@ -0,0 +1,681 @@
---
doc: SHADERTOY-RECUR-GUIDE
rev: 03
canonical_language: en
canonical_source: docs/conversion-guide.en.md
translations:
- ca: docs/conversion-guide.ca.md (rev 03, al dia)
---
# De Shadertoy a r_e_c_u_r
Shadertoy corre sobre WebGL2, és a dir **GLSL ES 3.00**. r_e_c_u_r corre sobre OpenGL
ES 2.0, és a dir **GLSL ES 1.00**. No és un canvi de noms: és un llenguatge anterior al
qual li falten coses que mig Shadertoy fa servir sense pensar. D'aquí les dues parts de
sota: la traducció mecànica (ràpida) i allò que s'ha de reescriure de debò.
Per saber quins uniforms existeixen i per què, vegeu la
[referència](recur-shader-reference.ca.md).
> **Si el teu shader és 1-input, llegeix abans el tutorial de l'autor.**
> [tutorial_converting_simple_1input_shader_from_shadertoy](https://git.terata.org/eessppeelllloo/r_e_c_u_r/wiki/tutorial_converting_simple_1input_shader_from_shadertoy)
> és el pas a pas del mateix cyberboy666 i el camí més curt per a un shader que processa
> el vídeo d'entrada. Aquesta guia és més ampla —cobreix 0-input, els límits de la GPU i
> quatre errors mesurats a l'aparell— però no el substitueix. Vegeu
> [El tutorial oficial](#el-tutorial-oficial-i-en-què-sen-aparta-aquesta-guia) per saber
> què ensenya i on s'acaba el seu abast.
## Part 1 — la traducció mecànica
### Estructura
Shadertoy et dona només el cos de `mainImage`. r_e_c_u_r vol un fitxer complet:
```glsl
// SHADERTOY
void mainImage(out vec4 fragColor, in vec2 fragCoord){
vec2 uv = fragCoord/iResolution.xy;
fragColor = vec4(uv, 0.5 + 0.5*sin(iTime), 1.0);
}
```
```glsl
// R_E_C_U_R
//0-input
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texcoord;
uniform vec2 u_resolution;
uniform float u_time;
uniform float u_x0;
uniform float u_x1;
uniform float u_x2;
uniform float u_x3;
void main(){
vec2 uv = v_texcoord;
gl_FragColor = vec4(uv, 0.5 + 0.5*sin(u_time), 1.0);
}
```
La marca `//N-input` de la primera línia és el que r_e_c_u_r busca per etiquetar el
shader al seu menú. Sense ella, el shader apareix com a tipus `-`.
### Taula d'equivalències
| Shadertoy | r_e_c_u_r | Compte amb |
|---|---|---|
| `void mainImage(out vec4 fragColor, in vec2 fragCoord)` | `void main()` | |
| `fragColor` | `gl_FragColor` | |
| `fragCoord` | `v_texcoord * u_resolution` | O `gl_FragCoord.xy`, que també existeix |
| `fragCoord/iResolution.xy` | `v_texcoord` | El cas comú: se substitueix sencer |
| `iResolution` | `u_resolution` | Shadertoy dona un **`vec3`**; aquí és `vec2`. `iResolution.z` no existeix |
| `iTime` | `u_time` | Pot anar enrere i ser negatiu |
| `iChannel0` / `iChannel1` | `u_tex0` / `u_tex1` | |
| `texture(ch, uv)` | `texture2D(ch, uv)` | |
| `iMouse.xy/iResolution.xy` | `vec2(u_x0, u_x1)` | Ja ve en 01, no cal dividir |
| `iMouse.z > 0.0` (botó) | `u_x2 > 0.5` | Els params són continus; es fan servir com a interruptor |
| `iTimeDelta`, `iFrame`, `iDate`, `iSampleRate`, `iChannelResolution` | **no existeixen** | Treure'ls o simular-los |
### Els paràmetres: de constants màgiques a comandaments
Aquesta és la part que fa que un port valgui la pena. Un shader de Shadertoy té números
fixos per tot arreu; a r_e_c_u_r aquells números poden ser els comandaments.
`u_x0``u_x3` **arriben sempre entre 0.0 i 1.0** (`conjur.cpp:54`). L'escalat es fa dins
del shader:
```glsl
float velocitat = u_x0 * 4.0; // 0 → 4
float zoom = 0.5 + u_x1 * 3.5; // 0.5 → 4 (mai 0, que rebenta una divisio)
float angle = u_x2 * 6.2831853; // volta completa
bool invertir = u_x3 > 0.5; // interruptor
```
Triar quins quatre números mereixen comandament és la decisió de disseny del port. Dues
regles que funcionen bé:
- **Amb els quatre comandaments a zero, el shader s'hauria de veure com l'original.** És
com arriba en carregar-lo a l'aparell: si a zero es veu pla o negre, sembla trencat.
- **Cada comandament ha de canviar alguna cosa reconeixible a tres metres**, no afinar un
decimal. Si l'original cicla automàticament entre estats, poder **triar** l'estat sol
ser el comandament més valuós de tots: sense ell, toca esperar.
A més: si l'ajust `X3_AS_SPEED` està activat, **`u_x3` no arriba mai al shader**; passa a
ser el control de velocitat. Un shader que necessiti els quatre params només funciona amb
aquell ajust desactivat. Dissenya per a tres.
### Proporció i centrat
Shadertoy sol normalitzar així perquè els cercles surtin rodons:
```glsl
vec2 uv = (fragCoord - 0.5*iResolution.xy)/iResolution.y;
```
`v_texcoord` va de 0 a 1 en **tots dos** eixos, així que copiar-ho tal qual deixa tot
estirat en una sortida panoràmica. L'equivalent:
```glsl
vec2 uv = v_texcoord - 0.5;
uv.x *= u_resolution.x / u_resolution.y;
```
## Part 2 — allò que GLSL ES 1.00 no té
Aquí és on se'n van les hores. Si el shader de Shadertoy fa servir res d'aquesta llista,
no és copiar i enganxar: s'ha de reescriure.
### Operadors de bits — el que més trenca
GLSL ES 1.00 **no té `&`, `|`, `^`, `<<`, `>>` ni `%`**. Els hashos moderns de Shadertoy
els fan servir constantment:
```glsl
// SHADERTOY — no compila a la Pi
uint h = uint(p.x) * 374761393u;
h = (h ^ (h >> 13u)) * 1274126177u;
```
Substituir per un hash de coma flotant dels de tota la vida:
```glsl
float hash(vec2 p){
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}
```
No produeix el mateix soroll, així que el resultat no serà idèntic a l'original. És una
reescriptura, no una traducció, i convé dir-ho en veu alta.
Tampoc no hi ha `uint`, ni `uvec2/3/4`, ni `%`. Per al mòdul: `mod(a, b)` amb floats.
### Bucles
Els límits han de ser **constants en temps de compilació**. Això no compila:
```glsl
for(int i = 0; i < passos; i++){ ... } // `passos` es una variable
```
Es posa el màxim com a constant i se surt amb un `break`:
```glsl
const int MAX_PASSOS = 64;
for(int i = 0; i < MAX_PASSOS; i++){
if(float(i) >= passos) break;
...
}
```
Tampoc no hi ha `while`, ni `switch`, ni indexar arrays amb un índex variable.
### Funcions que falten
| No hi és a ES 1.00 | Alternativa |
|---|---|
| `textureLod`, `texelFetch` | `texture2D` a seques (sense control de nivell de mipmap) |
| `dFdx`, `dFdy`, `fwidth` | Necessiten l'extensió `GL_OES_standard_derivatives`. **[PER VERIFICAR A LA MÀQUINA]** |
| `round`, `trunc`, `roundEven` | `floor(x + 0.5)`, `floor(abs(x))*sign(x)` |
| `sinh`, `cosh`, `tanh` | Escriure-les amb `exp()` |
| `inverse`, `transpose`, `determinant` | A mà, o redissenyar per no necessitar-les |
| `isnan`, `isinf` | No hi ha manera neta |
| `mix()` amb booleans | Només floats |
### Noms que tapen funcions
Una variable local anomenada com una funció incorporada (`mix`, `step`, `length`…) tapa
aquella funció dins del seu àmbit. Compila a escriptori i és imprevisible a la Pi.
Reanomena-la.
### El bucle d'antialiasing multiplica la mida per AA*AA
Un parany que se suma al de la mida, i facil de passar per alt. Un bucle aixi:
```glsl
#define AA 2.
for(float i = 0.; i < AA*AA - 0.5; i += 1.) {
...
vec3 c = el_meu_patro(p);
...
}
```
te limits constants, aixi que el compilador el **desenrotlla**. El cos sencer -- funcio
de patro inclosa -- acaba al codi final **quatre vegades**. Amb `AA` a 3 en serien nou.
Mesurat a la maquina: de cinc ports que nomes es diferencien en la funcio de patro, els
quatre amb un patro de 10-15 linies van funcionar, i el que tenia 31 linies i 8
condicions va sortir negre. Abaixant-lo a `AA 1.` hi va entrar.
Aixi que quan un port es a prop del limit, `AA` es la palanca mes barata: divideix per
quatre el codi generat canviant un caracter. El que es perd es el suavitzat de vores.
Abans de tocar `AA`, busca condicions repetides. En aquell mateix patro hi havia tres
branques que tornaven a comprovar el mateix rang; treure-ho fora va llevar sis
comparacions per mostra -- vint-i-quatre ja desenrotllat -- **sense canviar ni un sol
pixel**.
### Quan el soroll ES l'efecte
Hi ha shaders que no calculen una imatge: calculen un accident controlat. El senyal es
una constant absurda. `time *= 5e19` no es un compte, es llencar la precisio a proposit
perque el `mod()` de despres llegeixi bits que ja no signifiquen res. La sortida es
soroll numeric, volent.
Dues consequencies, i totes dues convé dir-les en veu alta abans de portar:
- **Necessita `highp`.** `5e19` en `mediump` es infinit, i no queda shader.
- **No es veura igual que al navegador**, mai, perque depen de com arrodoneixi cada GPU.
Prometre un resultat identic es prometre el que no es pot complir.
El mateix va passar al reves amb un shader de reixa el clapejat del qual venia de la
precisio baixa del navegador: l'aparell el dibuixava net i el port semblava *malament*
per estar be. En tots dos casos la regla es la mateixa: **quan un render i la seva
referencia no coincideixen, cal preguntar-se si la referencia esta ensenyant un
artefacte.**
`check_port.py` avisa de qualsevol literal per sobre de 65504 justament per aixo.
### El raymarching no hi cap --- mesurat, no suposat
Un shader que marxa raigs no entra en aquest aparell, i la manera de saber-ho no es
intentar-ho. Mesurat el 2026-09-05 amb un shader de keim
([MtGSWc](https://www.shadertoy.com/view/MtGSWc)), l'original del qual costa **234 crides
a `map()` per pixel**:
| Versio | Crides a `map()` | A l'aparell |
|---|---|---|
| Original | 234 | impossible |
| Reduida | 19 | **s'encalla, les figures perden definicio** |
| Mes reduida | 13 | **tambe s'encalla** |
| Redissenyada en 2D | 1 avaluacio | funciona |
Aquell `map()` portava a dins un `exp`, un `sin`, dos `mod` i quatre `length`. Per
comparar: un bucle de 8 voltes amb un cos molt mes barat va a 13.2 fps.
**Aixi que la maniobra no es abaixar els passos.** No es questio d'afinar: no hi cap amb
cap nombre de passos que valgui la pena. Cal redissenyar la idea en 2D --una avaluacio
per pixel, sense marxa-- que es el que va funcionar aqui i abans amb un Mandelbox. I
conve dir-ho aviat, abans de gastar la tarda en una traduccio que no pot correr.
`check_port.py` ja estima les operacions per pixel i avisa per sobre de 250. El llindar
esta calibrat amb shaders reals d'aquest aparell: els que van arriben a 166 i el que
s'encalla n'estima 360.
### Si es massa gran no dibuixa res, i s'ha de partir
Mesurat a la maquina el 2026-09-03. Un shader massa gran **compila, enllaca i despres no
dibuixa**: pantalla negra o el fotograma anterior congelat, sense cap error enlloc -- ni
al registre de GLSL ni a la interficie de r_e_c_u_r.
Dos punts mesurats, un a cada costat del limit, del mateix shader:
| | linies de codi | funcions | operacions | A la Pi |
|---|---|---|---|---|
| Cinc funcions de patro | 146 | 13 | 69 | **pantalla negra** |
| Dues funcions de patro | 85 | 10 | 38 | funciona |
Per comparar, cap dels 146 shaders que porta la imatge de r_e_c_u_r passa de **90 linies
ni de 23 operacions**. Tots porten un efecte per fitxer, i aixo no es una decisio
estetica: es el que hi cap.
Aixi que quan un shader de Shadertoy fica diversos efectes diferents en un fitxer darrere
d'un selector -- cosa molt habitual -- el port son diversos shaders, no un. Es perd el
selector intern i es guanya un comandament, perque r_e_c_u_r ja deixa canviar de shader
des de la interficie.
`check_port.py` avisa per sobre de 100 linies o 45 operacions.
### Dins d'un bucle, assigna des d'una funcio nomes a la declaracio
Comprovat a la maquina el 2026-09-03, despres d'una bisecció llarga.
Dins del cos d'un bucle, aixo genera **codi incorrecte** al compilador GLSL de la Pi:
```glsl
vec3 c;
c = el_meu_patro(p); // assignada des d'una funcio propia
```
El shader **compila sense ni un sol avis** i s'executa, pero la sortida deixa de dependre
d'`u_time` i dels comandaments: surt una imatge congelada, o negra. Ni el registre diu
res, ni la interficie.
Inicialitza-la a la mateixa declaracio:
```glsl
vec3 c = el_meu_patro(p);
```
Donar-li un valor per defecte a la declaracio **no basta**: el problema es la
reassignacio, no que falti l'inicialitzador. Si has de triar entre diverses funcions, mou
l'eleccio a una funcio a part perque el cos del bucle tingui exactament una assignacio:
```glsl
vec3 tria(int quin, vec2 p) {
if (quin == 0) return patro_a(p);
if (quin == 1) return patro_b(p);
return patro_c(p);
}
...
vec3 c = tria(quin, p);
```
Les tres parts de la regla compten, i cadascuna es va provar per separat:
| Dins d'un bucle | Resultat a la maquina |
|---|---|
| `vec3 c = la_meva_funcio(p);` | **funciona** |
| `vec3 c; c = vec3(1.0, 0.0, 0.0);` (expressio en linia) | **funciona** |
| `vec3 c; c = la_meva_funcio(p);` | **congelat / negre** |
| `vec3 c = vec3(0.0); c = la_meva_funcio(p);` | **congelat / negre** |
Fora d'un bucle, declarar sense inicialitzar no dona problema: 22 dels 124 shaders que
porta la imatge de r_e_c_u_r ho fan. **Cap ho fa dins d'un bucle.**
`check_port.py` detecta exactament aquella combinacio: 0 falsos positius sobre els 141
shaders que sabem que funcionen en un aparell real, i caça els 5 nostres que fallaven.
> Aquesta va costar dies, i val la pena entendre per que. Totes les senyals apuntaven en
> la direccio equivocada: compila, s'executa, la interficie mostra el shader com si
> estigues funcionant, i el codi es GLSL absolutament corrent que funciona en qualsevol
> escriptori. L'unica manera de trobar-ho va ser bisecar de canvi en canvi contra un
> shader que se sabia bo en aquell aparell concret.
### Un shader 0-input no pot fer servir `v_texcoord`
Trobat a la maquina el 2026-09-02, despres de dies al darrere. Es el parany mes car de
tots els que ens han sortit.
Mira quina geometria dibuixa conjur (`ofxVideoArtTools/src/conjur.cpp`, `apply()`):
```cpp
if(textures.size() > 0 ){
textures[0].draw(0, 0, ofGetWidth(), ofGetHeight()); // genera texcoords
} else {
ofDrawRectangle(0, 0, ofGetWidth(), ofGetHeight()); // NO les genera
}
```
Sense textures d'entrada, la geometria es un `ofDrawRectangle`, que **no porta
coordenades de textura**. L'atribut `texcoord` de `default.vert` queda indefinit, aixi que
tant `tcoord` com `v_texcoord` arriben al fragment shader **constants**. Tots els pixels
calculen el mateix i surt **una pantalla d'un color pla, sense cap missatge d'error**.
Cal treure la coordenada de `gl_FragCoord`:
```glsl
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
```
Els shaders del repositori original segueixen aquesta regla sense haver-la escrit mai.
Comptat sobre un aparell real:
| | fa servir `v_texcoord` | fa servir `gl_FragCoord` |
|---|---|---|
| Els 5 `0-input` del repositori | **0** | 5 |
| Els 12 `1-input` del repositori | 12 | **0** |
Disset de disset, sense excepcions. `check_port.py` ja ho detecta.
> Per que va costar tant trobar-ho: la fallada depen de l'**estat en execucio, no del
> fitxer**. Si hi ha un video reproduint-se, `textures` no esta buit, la geometria es un
> quadrilater amb textura i `v_texcoord` funciona perfectament — el mateix shader es
> comporta be. Per aixo el mateix fitxer semblava funcionar un dia i no l'endema, i per
> aixo el "color de la fallada" anava canviant: el que es veu es el que hi hagi a sota en
> aquell moment.
### El fitxer ha de ser ASCII pur
Descobert per les males el 2026-08-29: dos shaders que renderitzaven perfectament a
glslViewer donaven **pantalla verda a l'aparell, sense cap missatge d'error**. L'única
cosa que tenien en comú eren comentaris amb accents, guions llargs i cometes
tipogràfiques.
Els 23 shaders que porta r_e_c_u_r són ASCII pur. Ni un accent entre tots ells. El
controlador de la Pi és més estricte que el d'un escriptori i rebutja els bytes no-ASCII
**fins i tot dins de comentaris**, on un controlador d'escriptori simplement els ignora.
Escriu els comentaris en ASCII. `check_port.py` ho detecta.
> Aquest és especialment dolent perquè totes les senyals apunten en la direcció
> equivocada: compila al portàtil, el revisor el donava per bo, i r_e_c_u_r el mostra com
> si estigués corrent.
### r_e_c_u_r no et dirà que el shader ha fallat
`video_centre/shaders.py:18` documenta un estat `'!'` que significa error, però no hi ha
ni una sola línia del codi que l'assigni. Un shader que no compila es mostra exactament
igual que un que funciona.
Per veure l'error real de GLSL cal la sortida estàndard de c_o_n_j_u_r. Es llança amb
`subprocess.Popen` sense redirecció (`actions.py:573`), així que la seva sortida va on
vagi la consola de r_e_c_u_r. Per SSH:
```bash
pkill c_o_n_j_u_r
/home/pi/openframeworks10.1/apps/myApps/c_o_n_j_u_r/bin/c_o_n_j_u_r
```
Després es carrega el shader des de la interfície i es llegeix el terminal.
openFrameworks imprimeix el registre complet de GLSL, amb el número de línia de la
fallada.
### Multipassada: el límit gran
Shadertoy permet `Buffer A/B/C/D`: passades que llegeixen el seu propi fotograma
anterior. Tot el feedback, els autòmats cel·lulars, els fluids i les simulacions depenen
d'això. **No hi ha equivalent directe en un shader de r_e_c_u_r.**
El que sí que hi ha, i dona molt de joc, és diferent:
- **Les 3 capes de shader.** S'apliquen en cadena, i la capa 1 rep a `u_tex0` la sortida
de la capa 0 i a `u_tex1` l'entrada de la capa 0 (`ofApp.cpp:115-129`). Són diverses
passades, però sense memòria del fotograma anterior.
- **El mode `detour`** de r_e_c_u_r, que sí que és un bucle de realimentació de vídeo.
L'ajust `SHADER_POSITION` decideix si els shaders van abans o després.
A la pràctica: un shader de Shadertoy amb Buffers no es porta. O es redissenya la idea al
voltant de detour, o es descarta. Digues-ho aviat, abans de gastar una hora traduint una
cosa que no pot funcionar.
### Precisió
`precision mediump float;` és el que porten tots els shaders del repositori. En `mediump`
el rang útil és curt i `u_time` **creix sense límit**: al cap de pocs minuts apareixen
bandes i salts en qualsevol cosa que faci `sin(u_time * alguna_cosa_gran)`.
Dos remeis, per ordre:
1. Embolcallar el temps. Si el shader té un cicle natural,
`mod(u_time, durada_del_cicle)` dona exactament la mateixa seqüència sense créixer. Si
no, `mod(u_time, 100.0)` sol ser prou.
2. Demanar més precisió sense trencar la compatibilitat:
```glsl
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
```
**[PER VERIFICAR A LA MÀQUINA]** si el `highp` en fragment shader està disponible i què
costa.
### Rendiment
És una Raspberry Pi renderitzant vídeo alhora. Un raymarching de 128 passos que va suau
al navegador aquí va a batzegades. El que més costa, per ordre: bucles d'antialiasing (el
cost va al quadrat — `AA 4.` són 16 mostres per píxel), raymarching amb molts passos, i
`pow`/`log`/`atan` dins de bucles.
Abaixa aquests números i **deixa un comentari dient com pujar-los**, per poder ajustar el
shader a la màquina sense rellegir-lo sencer. Val més un que corre a 25 fps que un altre
més bonic a 4.
**Mesurat a l'aparell (2026-09-04), i canvia la manera d'ajustar.** El sostre són **30.0
fps**, i és un topall *de programari*, no de la pantalla: conjur fa `ofSetFrameRate(30)`
amb `ofSetVerticalSync(false)` (`ofApp.cpp:11-12`). Així que un shader que marca 30.0 és
al topall i potser li sobra marge de GPU; per sota de 30 el que es mesura és la màquina.
El quadre és de 720x480 perquè `/boot/config.txt` porta `sdtv_mode=16` (NTSC progressiu)
i conjur pren la mida de la pantalla. Un shader el bucle del qual feia 8 voltes, amb dos
`smoothstep`+`distance` per volta, va mesurar:
| Voltes del bucle | fps | Interval entre fotogrames |
|---|---|---|
| 8 | 13.2 | 76 ms |
| 10 | 6.6 | 152 ms |
| 12 | 4.8 | 208 ms |
| 16 | 3.9 | 256 ms |
D'aquí en surten dues coses, i cap de les dues es veu des de l'escriptori.
**És un graó, no una costa.** El cost per volta salta de 9.5 ms amb 8 voltes a 15.2 ms
amb 10, i després es queda pla. En créixer el bucle desenrotllat es creua algun llindar de
la GPU, i a partir d'aquí el shader **sencer** surt més car. Així que quan un port va
just, «retallar-lo una mica» és la maniobra equivocada: cal esbrinar de quin costat del
graó és i posar-se al bo. És el mateix tipus de llindar que el límit de mida que fa que un
shader no dibuixi res: primer es posa lent, més enllà desapareix.
**Compara l'interval entre fotogrames amb la transició més curta que vulguis veure.** Si
el shader té una lluïssor, un cop o qualsevol atac breu, mesura quant dura i posa-ho al
costat de l'interval. Al shader de dalt la lluïssor durava 111 ms: amb 8 voltes (76 ms) es
recull; amb 10 (152 ms) es perd més vegades de les que es veu, i aleshores el port sembla
*mal fet* en comptes de lent. Una transició més curta que l'interval no es veu dèbil: **no
es veu**, i res no ho avisa.
Per tenir aquests números en comptes d'endevinar-los, vegeu l'envoltori
d'`instrumentacio/`: compta fotogrames a l'aparell i els escriu al mateix registre que les
càrregues de shader.
### El tutorial oficial, i en què se n'aparta aquesta guia
cyberboy666 en va escriure un:
[tutorial_converting_simple_1input_shader_from_shadertoy](https://git.terata.org/eessppeelllloo/r_e_c_u_r/wiki/tutorial_converting_simple_1input_shader_from_shadertoy).
**Si estàs portant un shader 1-input, llegeix-lo.** És curt, és del mateix autor i és el
que segueix la resta de la comunitat. El que ve ara no el corregeix —tot el que diu quadra
amb el codi font— sinó que dibuixa on s'acaba el seu abast.
Està escrit en la **convenció antiga** (`tcoord`, `tex`, `tres`, `fparams`, `ftime`), que
ve del mòdul Structure d'Erogenous Tones. Aquella convenció funciona —conjur enllaça els
dos jocs de noms a cada fotograma, sense condició—, però dels 22 shaders que porta el
mateix repositori de r_e_c_u_r, 20 són moderns, 1 antic i 1 mixt. Aquesta guia fa servir
el joc modern perquè els shaders nous s'assemblin a ells.
Tres coses seves que convé endur-se:
- **Provar al sandbox abans que a l'aparell** (vegeu *Previsualitzar sense la Pi*). És el
més útil que té, i que no fos a les nostres eines no és casualitat: va costar una sessió
sencera de depuració descobrir el forat que tapa.
- **Que el comandament a zero signifiqui «sense efecte».** El seu idioma per a un
multiplicador és `(1.0 + f1)` en comptes de `f1`: en repòs multiplica per un, a fons
duplica. Multiplicar pel paràmetre pelat anul·la la imatge en la posició de repòs.
- Els literals numèrics necessiten punt decimal: `1.0`, mai `1`.
Quatre llocs on s'acaba el seu abast. No són errors del tutorial, són casos que no tracta,
i cadascun d'ells dona pantalla plana o congelada sense cap missatge d'error:
- La seva idea central —*«`tcoord` ja ve normalitzada, no divideixis per la resolució»*—
val **només mentre s'estigui dibuixant una textura**. En un shader generatiu sense vídeo
carregat, `conjur::apply()` cau a `ofDrawRectangle()`, que no genera coordenades de
textura, i les dues varyings arriben constants. Per a `0-input`,
`gl_FragCoord.xy / u_resolution`. Vegeu *Un shader 0-input no pot fer servir
`v_texcoord`*.
- La seva plantilla declara `iparams` com a «4 ints coming in». Està mort: fixat a
`(0,0,0,0)`.
- El seu `float time = float(itime) + ftime;` no és un rellotge continu, perquè `itime` és
`ceil()` i no `floor()`. Fes servir `u_time`.
- El seu shader final no porta marca `//N-input`, i `shaders.py` sí que la llegeix.
Les seves marques `//f0::` són metadades del sandbox, no de r_e_c_u_r:
`determine_shader_parameter_number` retorna 4 sense mirar res.
## Part 3 — el procediment
1. **Mirar la llicència primer.** Shadertoy publica sota
[CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/) per defecte,
llevat que l'autor digui una altra cosa al mateix codi. La clàusula **NC** vol dir que
no es pot fer servir en un bolo pagat ni en res comercial. I compte: r_e_c_u_r és
GPL-3.0, amb la qual NC **no** és compatible, així que un port derivat de material NC
no es pot aportar de tornada al projecte.
2. **Guardar l'original** amb una capçalera d'atribució (autor, URL, llicència, data de
consulta). Si no tens l'URL, escriu `[PENDENT]` en comptes d'inventar-te-la: els
identificadors de Shadertoy són opacs i un enllaç endevinat apunta a un altre shader.
3. **Traduir el mecànic** amb la taula de la part 1.
4. **Passar el revisor**, que fa aquella feina per tu:
```bash
./check_port.py el_meu_shader.frag
```
Marca `[BREAKS]` allò que no compila en GLSL ES 1.00, `[MISSING]` els requisits de
r_e_c_u_r que no hi són i `[CHECK]` allò que depèn de la màquina. No és un compilador:
que surti net no garanteix que compili a la Pi, però que surti brut garanteix que no.
(Els seus missatges són en anglès; amb `--lang es` surten en castellà. Encara no parla
català.)
5. **Triar els params.** Quins tres o quatre números es converteixen en comandaments.
6. **Previsualitzar en un escriptori abans de tocar la Pi** (vegeu més avall).
7. **Guardar a `shaders/N-input/`** amb la marca correcta.
8. **Copiar a la màquina** i provar de debò.
### Previsualitzar sense la Pi
Dues eines, i cacen coses diferents. Fes servir totes dues.
**El sandbox, per a compatibilitat.** <https://glsl.erogenous-tones.com> — *click to
create new shader*, hi enganxes i compila a mesura que escrius. És WebGL 1, que és **el
mateix perfil GLES 2.0 que corre la Pi**, així que rebutja allò que rebutjaria l'aparell.
Això és justament el que no pot fer la previsualització d'escriptori de sota. Compte: porta
la plantilla antiga, així que un shader escrit amb `u_x0`/`u_resolution` necessita que li
adaptis els uniforms —o que els declaris com a variables soltes— per compilar-hi.
No veu el límit de mida ni pot reproduir els paranys que depenen de l'estat. Tanca el
forat de sintaxi, no el de l'aparell.
**glslViewer, per iterar sobre la forma.** L'autor el recomana, del mateix que va escriure
The Book of Shaders, i diu que els uniforms de conjur hi són compatibles
(`notes_on_shader_formats.md`). Edites, guardes i la finestra s'actualitza sola.
```bash
glslViewer shaders/0-input/el_meu_shader.frag -l
```
Amb textura d'entrada, per als d'1 i 2 inputs:
```bash
glslViewer shaders/1-input/el_meu_shader.frag una_imatge.png -l
```
Per moure un paràmetre, s'escriu a la consola de glslViewer mentre corre:
```
u_x0,0.35
```
Per automatitzar —útil per generar captures de diversos shaders de cop— hi ha `--headless`
i `-E`, que executa ordres i surt:
```bash
glslViewer el_meu_shader.frag --headless -e "u_x0,0.6" -E "screenshot,sortida.png"
```
### Comparar el port amb l'original, amb numeros
Renderitzar tots dos i mirar-los no basta per als canvis que importen. Treu PNG dels dos i
resta'ls canal a canal, pero **clava abans el temps dins del shader**:
```bash
sed -e 's/^uniform float u_time;/const float u_time = 3.0;/' \
-e 's/^uniform float u_x\([0-3]\);/const float u_x\1 = 0.0;/' port.frag > fixat.frag
glslViewer fixat.frag --headless -E "screenshot,sortida.png"
```
El `-e "time,3.0"` de `glslViewer` **no** el clava: el mateix shader renderitzat dues
vegades al mateix temps nominal donava fins a 55/255 de diferencia per canal en un instant
de canvi rapid, mes que diverses de les diferencies reals que voliem mesurar. Amb el valor
compilat a dins, dues passades surten identiques byte a byte i el soroll baixa a zero.
Es el que converteix un "es veu semblant" en un "0/255 de diferencia en tres instants, aixi
que l'unica desviacio es la que vaig decidir a proposit".
### El parany de glslViewer: ofereix molt més del que hi ha a la màquina
glslViewer dona uns 40 uniforms (vegeu `UNIFORMS.md` al seu repositori). conjur n'enllaça
**cinc**. Els únics presents a tots dos són `u_time`, `u_resolution`, `u_tex0` i `u_tex1`.
Tota la resta funciona a l'escriptori i **falla en silenci a la Pi**: l'uniform no
s'enllaça, arriba a zero, i el shader es veu congelat o pla sense donar cap error. Els que
més s'hi colen:
| Existeix a glslViewer | A r_e_c_u_r |
|---|---|
| `u_mouse` | **No.** L'error número u. Fer servir `u_x0`/`u_x1` |
| `u_delta`, `u_frame`, `u_date` | **No** |
| `u_pixelDensity`, `u_sceneFps`, `u_sceneMs` | **No** |
| `u_view2d` (pan/zoom amb el ratolí) | **No** |
| `u_camera*`, `u_light*`, `u_*Matrix` | **No** |
Al revés també: `u_x0`…`u_x3` són de conjur, no de glslViewer. Allà es fixen a mà des de la
seva consola.
> Avís: glslViewer en un escriptori compila amb un GLSL més modern que el de la Pi. Que
> funcioni allà **no garanteix** que compili a l'aparell. Serveix per iterar sobre la
> forma. Per a compatibilitat, el sandbox de dalt; l'última paraula, la màquina.
### Copiar a la màquina
r_e_c_u_r llegeix shaders des d'un USB muntat, des de `/home/pi/r_e_c_u_r/Shaders` i des
de `/home/pi/Shaders` (`data.py:34`). El menys invasiu és `/home/pi/Shaders`, que deixa net
el repositori de r_e_c_u_r:
```bash
rsync -av --dry-run shaders/ pi@IP_DE_LA_TEVA_PI:/home/pi/Shaders/
```
Treure `--dry-run` quan la llista de fitxers sigui l'esperada.