wandres.dev
WebGPU cheatsheet
⌘ REFERENCIA · 205 elementos

Cheatsheet de WebGPU

La API entera en un solo sitio: dispositivo, buffers, WGSL, recursos, pipeline, compute, medición y depuración. Busca por nombre o por lo que quieres conseguir.

⚙️ Adaptador, dispositivo y límites 14

Si no existe, no hay WebGPU: comprueba antes de nadanavigator.gpu
Resuelve con null en vez de fallar; hay que mirarlorequestAdapter()
Hay dispositivo, pero es el de softwareadapter.isFallbackAdapter
vendor, architecture, device y descriptionadapter.info
Lo que este hardware ofrece de másadapter.features · has()
Lo que no pides no lo tienes, aunque existarequestDevice({ requiredFeatures })
Semántica de al menos esto; si no llega, rechazarequiredLimits
El rechazo de requestDevice: no hay degradación solaOperationError
Solo 4: obliga a agrupar por frecuencia de cambiomaxBindGroups
640 recursos dentro de un mismo grupomaxBindingsPerBindGroup
Empieza por min: pedir menos es pedir másminUniformBufferOffsetAlignment
16, y sustituye al obsoleto ...ShaderComponentsmaxInterStageShaderVariables
Una promesa que resuelve, no un evento de errordevice.lost
Provoca una pérdida con reason destroyeddevice.destroy()

🖥️ El canvas y el frame 12

El contexto no lleva dispositivo hasta configurarlocanvas.getContext("webgpu")
Sin esto getCurrentTexture no devuelve nada útilcontext.configure({ device, format })
bgra8unorm o rgba8unorm según la plataformagetPreferredCanvasFormat()
opaque o premultiplied: cómo compone con la páginaalphaMode
Declarar vistas sRGB del mismo canvasviewFormats
RENDER_ATTACHMENT por defecto; amplíalo si vas a copiarusage
Una textura distinta por frame: no la guardesgetCurrentTexture()
Lo que come el color attachment del pasetexture.createView()
Píxeles del buffer frente a píxeles CSScanvas.width frente a style.width
Redimensionar sin volver a configurar el contextoResizeObserver
Soltar el contexto al desmontarunconfigure()
transferControlToOffscreen para renderizar en un workerOffscreenCanvas

📦 Buffers y lectura 14

El tamaño es inmutable; los usos tambiéncreateBuffer({ size, usage })
Atributos por vértice y el índiceGPUBufferUsage.VERTEX · INDEX
Pequeño y constante, frente a grande y escribibleUNIFORM · STORAGE
Sin COPY_DST, writeBuffer es un error de validaciónCOPY_SRC · COPY_DST
El primero solo con COPY_DST; el segundo solo con COPY_SRCMAP_READ · MAP_WRITE
El buffer del que salen los parámetros de dibujoINDIRECT
Escribir el contenido inicial sin copia intermediamappedAtCreation
La vista muere al desmapear: no guardes el ArrayBuffergetMappedRange() · unmap()
La vía cómoda: copia y encola por tiqueue.writeBuffer()
La GPU no escribe donde la CPU lee: hay un saltoBuffer de staging
Del buffer de trabajo al mapeable, en el encodercopyBufferToBuffer()
Espera a que el trabajo previo termine: un frame o másmapAsync(GPUMapMode.READ)
Saber que se acabó, sin traerse los datosonSubmittedWorkDone()
unmapped, pending o mapped: evita mapear dos vecesbuffer.mapState

🎬 Comandos, passes y cola 12

Nada ocurre hasta el submit: por eso se valida antesGrabar, no ejecutar
Un encoder es de un solo usocreateCommandEncoder()
Ámbito con attachments; fuera de él no se dibujabeginRenderPass(desc)
El otro ámbito, sin attachments ni rasterizadobeginComputePass()
Solo puede haber un pase abierto por encoderpass.end()
Devuelve el command buffer y cierra el encoderencoder.finish()
Un command buffer no se puede enviar dos vecesqueue.submit([cb])
El estado vive dentro del pase, no del encodersetPipeline · setBindGroup
Recortar sin cambiar de attachmentsetViewport · setScissorRect
Copias fuera de cualquier pasecopyBufferToTexture · copyTextureToTexture
La vía portable es timestampWrites, no writeTimestampMarcas de tiempo del pase
Nombrar bloques para las herramientas de capturapushDebugGroup · popDebugGroup

🔤 WGSL: tipos y espacios 13

Sin implícitas: mezclar tipos es error de compilaciónf32 · i32 · u32 · bool
Necesita la feature shader-f16 y enable en el módulof16
Alias cómodos de vec2 de f32 y compañíavec2f · vec3f · vec4f
Columnas por filas, al revés de la costumbre matemáticamat4x4f
La unidad que se comparte con el host y se alineastruct
Fijo en todas partes; variable solo al final de un storagearray
El espacio por defecto dentro de una funciónvar<function>
Global por invocación, no compartido con nadievar<private>
Compartido por el grupo; se inicializa a cerovar<workgroup>
Solo lectura, tamaño acotado, alineación estrictavar<uniform>
Grande y escribible: prohibido en la etapa de vérticevar<storage>
Inmutable en runtime, en compilación y en creaciónlet · const · override
Reinterpretar los bits sin convertir el valorbitcast

🏷️ WGSL: atributos y builtins 14

La dirección del recurso: tiene que casar con el layout@group · @binding
Atributo de entrada o interfaz entre etapas@location(n)
Marcan el punto de entrada de cada etapa@vertex · @fragment · @compute
Clip space al salir, coordenada de píxel al entrar@builtin(position)
Dibujar sin vertex buffer solo con el índice@builtin(vertex_index)
Qué instancia soy dentro del draw@builtin(instance_index)
Si el fragmento viene de la cara delantera@builtin(front_facing)
Escribir profundidad y perder el early-z@builtin(frag_depth)
El índice absoluto en el despacho entero@builtin(global_invocation_id)
La posición dentro del workgroup@builtin(local_invocation_id)
Qué grupo soy; con num_workgroups da el total@builtin(workgroup_id)
Sin interpolar: obligatorio para enteros@interpolate(flat)
Muestreo dentro del triángulo con MSAA@interpolate(perspective, centroid)
Forzar la disposición de un miembro de struct@align · @size

🧮 WGSL: flujo y librería 13

Sintaxis de Rust sin paréntesis obligatoriosif · switch · for · while
El bucle primitivo del que salen los demásloop · continuing
Todo el grupo tiene que llegar al mismo sitioControl de flujo uniforme
Exige flujo uniforme: necesita las derivadas del quadtextureSample
La salida cuando el flujo no puede ser uniformetextureSampleLevel
Leer un valor compartido con la barrera implícitaworkgroupUniformLoad
Descarta el fragmento y desactiva el early-z del pipelinediscard
Elegir sin ramificarselect(f, t, cond)
La caja de herramientas de todo shadingmix · step · smoothstep · clamp
Partir un número en trozosfract · floor · round · trunc · modf
Una instrucción en vez de dosfma · inverseSqrt
Derivadas de pantalla: solo en fragmentodpdx · dpdy · fwidth
Meter cuatro canales en un u32pack4x8unorm · unpack2x16float

🔗 Bind groups y layouts 12

Un puntero por grupo en vez de N cambios de estadoAgrupar, no bindear uno a uno
El contrato: binding, visibility y tipocreateBindGroupLayout({ entries })
GPUShaderStage.VERTEX, FRAGMENT o COMPUTE, combinablesvisibility
uniform, storage o read-only-storagebuffer.type
Valida el tamaño al crear el grupo, no al dibujarminBindingSize
float, unfilterable-float, depth, uint, sinttexture.sampleType · viewDimension
Los recursos concretos que cumplen ese contratocreateBindGroup({ layout, entries })
La lista ordenada de layouts: define los índicescreatePipelineLayout
Cómodo, pero sus grupos no se comparten entre pipelineslayout: "auto"
Cambiarlo cuesta; ordenar el bucle para no repetirlosetBindGroup(i, grupo)
Un buffer, muchos objetos, un offset por drawhasDynamicOffset
Frame, pase, material y objeto: justo los cuatro gruposOrganizar por frecuencia

📏 Uniform buffers y alineación 11

Dos funciones por tipo, y de ahí sale todo el layoutAlignOf y SizeOf
Cada miembro sube al siguiente múltiplo de su alineaciónRegla del offset
La mayor de las de sus miembrosAlineación de la struct
El tamaño se redondea: el que nadie cuentaPadding de cola
El tamaño del elemento redondeado a su alineaciónStride del array
Ocupa 12 y se alinea a 16: el bug de layout más comúnvec3f
Son 48 bytes, no 36: cada columna se alinea a 16mat3x3f
Padding explícito y declarado en el shader@align(n) · @size(n)
65536 por enlace: mil matrices caben, dos mil nomaxUniformBufferBindingSize
Van de 256 en 256 por minUniformBufferOffsetAlignmentOffsets dinámicos
Genéralos desde el WGSL o desde una tabla únicaNo calcules offsets a mano

💾 Storage buffers y texturas 11

Grande y solo lectura: el sitio de los datos por instanciavar<storage, read>
Escritura arbitraria; no existe en la etapa de vérticevar<storage, read_write>
Solo como último miembro, y solo en storagearray sin tamaño
El tamaño real llega en tiempo de ejecuciónarrayLength()
Leer de A y escribir en B: elimina la carrera de raízDoble buffer
Un contador compartido sin perder incrementosatomic<u32> · atomicAdd
Escribir a una imagen sin pasar por un attachmentStorage texture
Escritura por coordenada, sin filtrado ni mezclatextureStore()
read_write pide la feature de storage textures ampliadasaccess write · read_write
Lista corta: r32float, rgba8unorm, rgba16float y poco másFormatos permitidos
Hay que pedirlo al crear la texturaSTORAGE_BINDING

🎨 Texturas y samplers 13

size, format, usage, mipLevelCount y sampleCountcreateTexture(descriptor)
El mismo campo sirve para 3D y para arraysdepthOrArrayLayers
Un array de 2D no es una textura 3D aunque lo parezcadimension
Muestrear y dibujar encima son usos distintosTEXTURE_BINDING · RENDER_ATTACHMENT
Acotar niveles, capas y reinterpretar la dimensióncreateView()
No exige alinear a 256: eso es de copyBufferToTexturequeue.writeTexture()
La vía rápida desde ImageBitmap o canvascopyExternalImageToTexture()
A diferencia de WebGL, aquí los generas túSin mipmaps automáticos
Un quad por nivel muestreando el anteriorGenerarlos a mano
Es estado aparte: la misma textura con varios filtroscreateSampler()
Los tres hay que ponerlos: por defecto es nearestmagFilter · minFilter · mipmapFilter
Solo surte efecto con los tres filtros en linearmaxAnisotropy
El sampler de sombras: compara en vez de devolversampler_comparison · compare

⚗️ Render pipeline 12

Todo el estado gráfico compilado y validado de golpecreateRenderPipeline(desc)
No bloquea el hilo mientras el driver compilacreateRenderPipelineAsync()
Un pipeline layout compartido evita recrear bind groupslayout
Un módulo puede tener varias entradasvertex.module · entryPoint
Cuántos bytes separan dos vérticesvertex.buffers · arrayStride
vertex o instance: la misma tabla sirve para instanciarstepMode
float32x3, unorm8x4, snorm8x4, unorm10-10-10-2shaderLocation · offset · format
Listas y tiras; las tiras piden stripIndexFormatprimitive.topology
ccw por defecto: el winding decide qué se descartacullMode · frontFace
Un objeto por salida: formato, blend y writeMaskfragment.targets
Variantes del mismo módulo sin duplicar el códigoOverride constants
Se especializa ahí, no en el shaderconstants al crear el pipeline

🕳️ Depth, stencil y mezcla 14

El formato habitual; con stencil8 si necesitas máscaradepth24plus
Precisión real y el que pide el reversed-zdepth32float
less, less-equal, greater, equal, always...depthCompare
A false en el pase de transparentesdepthWriteEnabled
Cerca en 1 y lejos en 0: reparte los bits donde hacen faltaReversed-z
Descarta antes del fragment; discard y frag_depth lo apaganEarly-z
Separar coplanares sin tocar la geometríadepthBias · depthBiasSlopeScale
compare, failOp, depthFailOp y passOp por carastencilFront · stencilBack
Repartir los ocho bits entre varios usosstencilReadMask · writeMask
La ecuación de mezcla, por color y por alfasrcFactor · dstFactor · operation
El clásico over con alfa rectosrc-alpha y one-minus-src-alpha
Factor one: evita el halo negro al interpolarAlfa premultiplicado
La mezcla no es conmutativa: hay que ordenarEl orden sigue importando
Weighted blended, depth peeling o listas por píxelOIT

🔲 Targets, MSAA e indirect 13

Varias salidas a la vez: el G-buffer sale de aquícolorAttachments
clear es más barato en hardware de tilesloadOp: "clear" o "load"
Descartar lo que no se va a leer ahorra ancho de bandastoreOp: "store" o "discard"
32 por defecto: cuatro rgba16float justo lo llenanmaxColorAttachmentBytesPerSample
MSAA; el pipeline y el attachment tienen que coincidirsampleCount: 4
Dónde aterriza la textura multimuestreada ya resueltaresolveTarget
Solo suaviza bordes de geometría, no el shadingLo que MSAA no arregla
Convertir el alfa en cobertura de muestrasalphaToCoverageEnabled
Los dos parámetros de draw que casi nadie usainstanceCount · firstInstance
Los parámetros salen de un buffer, no de la CPUdrawIndexedIndirect()
Y el orden exacto de los cinco u32 en el bufferGPUBufferUsage.INDIRECT
Sin esa feature, firstInstance indirecto tiene que ser 0indirect-first-instance
Pregrabar comandos y reejecutarlos con executeBundlescreateRenderBundleEncoder()

🧵 Compute 13

Se fija en el shader, no en la llamada@workgroup_size(x, y, z)
Cuenta grupos, no invocaciones: divide y redondeadispatchWorkgroups(n)
Si el dominio no es múltiplo del grupo, sobran hilosEl guardia
256: el producto de las tres dimensionesmaxComputeInvocationsPerWorkgroup
65535 grupos por eje en un solo despachomaxComputeWorkgroupsPerDimension
Cuántos grupos lo decide la propia GPUdispatchWorkgroupsIndirect()
var<workgroup>: rapidísima, y el techo son 16 KiBMemoria compartida del grupo
Sincroniza el grupo; todos tienen que llegarworkgroupBarrier()
Ordena los accesos a storage dentro del grupostorageBarrier()
Para sincronizar de verdad hace falta otro despachoEntre workgroups no hay barrera
La primitiva con la que se construye lo demásatomicCompareExchangeWeak
log(n) pasos con la mitad de hilos parados en cada unoReducción en árbol
Menos pasos y más trabajo, o justo al revésHillis-Steele frente a Blelloch

📊 Medir y depurar 14

Es una feature: hay que pedirla al crear el dispositivotimestamp-query
beginningOfPassWriteIndex y endOfPassWriteIndextimestampWrites
Del query set a un buffer, y de ahí a un BigUint64ArrayresolveQuerySet()
El reloj de la CPU solo ve cuándo enviasteperformance.now() no mide GPU
Cuántas muestras pasaron, uno o dos frames despuésOcclusion queries
La intensidad aritmética dice cuál te limitaCómputo o memoria
Hilos vecinos leyendo direcciones vecinas: una transacciónCoalescencia
Registros y memoria compartida limitan los grupos en vueloOcupación
La API no lanza: devuelve algo roto y sigueObjetos inválidos
validation, out-of-memory o internal, y se apilanpushErrorScope(filtro)
Devuelve el primer error capturado, o nullpopErrorScope()
Se traga en silencio todos los errores posterioresUn scope sin pop
La red de seguridad para lo que nadie capturóuncapturederror
Es lo que convierte un mensaje ilegible en uno útillabel en todo