Teu progresso
0 / 83 módulos0%
Estágio 03 · 03-14
BloqueadoWeb é mais que documento + form. Aplicações modernas crescentemente envolvem mídia: mapas com millions de pontos, dashboards reativos com 60fps, videoconferência, áudio capture/processing, edição de imagem no browser, jogos, AR/VR (WebXR). Cada uma dessas capabilities tem stack próprio: Canvas 2D, WebGL/WebGPU, Web Audio, MediaStream, codecs (H.264, VP9, AV1, Opus), MSE, WebRTC.
Maioria dos devs trata isso como caixa-preta, usa biblioteca, cruza dedos. Mas apps competitivos no espaço de mídia (Figma, Excalidraw, Loom, Tldraw, Google Meet, Discord) dominam pipelines internos: GPU shaders custom, audio worklets, frame budgets de 16ms, codec choice e bitrate adaptation. Quando você precisa pixel-perfect 60fps com ms de latency, abstrações vazam.
Este módulo é graphics/audio/video pipelines no browser e nativo, com foco em entender frame budget, GPU pipeline, codec internals, latência fim-a-fim. Logística pode se beneficiar de mapa GPU-rendered, vídeo de comprovação de entrega, dashboards 60fps.
60fps = 16.67ms por frame. Browser deve completar JS + style + layout + paint + composite dentro disso, ou frame drops.
requestAnimationFrame (raf) sincroniza com vsync. Trabalho em raf é tudo que rola por frame. Long tasks (> 50ms) são interrupção visível.
Targets: 60fps = 16ms; 120fps mobile/iPad = 8ms. Animations precisam ser GPU-only (transform, opacity); layout/paint mata budget.
API imperativa. Estado (transform, fillStyle, lineWidth) e métodos (fillRect, arc, drawImage). CPU-bound mas com hardware accel em compositing.
Bom pra: gráficos médios (até ~5k objetos), texto, sprites simples.
Anti-pattern: ctx.fillStyle = 'red' num loop com mesma cor (custo de state change). Batch.
OffscreenCanvas: roda em Worker; libera main thread.
WebGL 1 (~OpenGL ES 2.0) e WebGL 2 (~OpenGL ES 3.0). API low-level, descrita em estados (program, buffers, uniforms, textures, framebuffers).
Pipeline:
Shaders em GLSL ES. Você compila string em WebGLProgram.
Padrão W3C novo (Chrome 113+, Firefox/Safari atrás). Baseado em Vulkan/Metal/D3D12. Trade off: mais boilerplate, mas:
Pra novo projeto greenfield com browsers modernos: WebGPU. Pra suporte amplo hoje: WebGL ainda.
GPU é massivamente paralelo. Vertex shader roda 100k+ vezes/frame; fragment shader, milhões. Pensar data-parallel:
Compute shaders (WebGPU) deixam você usar GPU pra non-graphics: image processing, physics, ML.
Critério: count de elementos + interatividade individual.
Graph de AudioNode: source (oscillator, buffer, mediaStream), processor (gain, filter, convolver, panner), destination.
const ctx = new AudioContext();
const src = ctx.createMediaStreamSource(stream);
const gain = ctx.createGain();
src.connect(gain).connect(ctx.destination);
AudioWorklet: processador custom em thread dedicada. Substitui ScriptProcessorNode (depreciado, rolava em main).
Sample rate típico 48kHz. Buffer sizes 128-1024 samples. Latency target < 20ms pra interactive.
getUserMedia({video, audio}) retorna MediaStream. WebRTC (02-14) usa pra peer-to-peer.
Pipeline: capture → encode (browser-controlled) → network → decode → render. Peer connection negocia codecs (SDP) e candidates (ICE).
Conceitos:
Bitrate adaptation (HLS/DASH): múltiplas renditions, player escolhe via bandwidth/buffer.
Voz pode usar 16kHz mono em 24-32 kbps (Opus). Música stereo 128-256 kbps.
Media Source Extensions (MSE): feed bytes em <video> via SourceBuffer. Permite players adaptive (DASH/HLS no browser).
.m3u8 + segmentos .ts/fmp4.Players: hls.js, dash.js, Shaka Player.
Live streaming: low-latency variants (LL-HLS, LL-DASH) com chunks de 1-2s. WebRTC < 500ms; HLS standard 6-30s.
Cada estágio (capture, encode, network, decode, render) consome budget. Profile e otimize estágio dominante.
VRAM é finita. Texturas grandes (4k+) acumulam. Strategies:
sRGB → Display 03-03 → wide gamut. <canvas color-space="display-p3">. WebGL color spaces. HDR vídeo (HDR10, Dolby Vision) chegando.
Importa em apps de design/photo. Em maioria, sRGB basta.
performance.measure pra spans custom.chrome://gpu pra diagnose.Frame drops aparecem como "long frames" no panel. Identifique culprit.
Canvas em Worker: render fora do main thread. Não bloqueia UI events, scrolling, input.
Patterns:
Suporte: Chrome desde 2018; Safari/Firefox alcançaram.
Se browser não dá conta, native shells (Tauri, Electron, Capacitor) abrem APIs:
Trade-off: complexidade de build / store, vs capability.
WebGL é legacy. WebGPU é o stack moderno: compute shaders first-class, storage buffers, async error handling, bindless textures, command encoders explícitos. Baseline 2024: Chrome/Edge desde 2023, Safari iOS 17.4+ / macOS 14.4+, Firefox 130+ (em flag até 2024). Coverage real 2026: ~80-85% browsers; WebGL fica como fallback obrigatório.
Diferença conceitual: WebGL é state-machine imperativa herdada de OpenGL ES 2.0/3.0; WebGPU é command-buffer + pipeline-state-objects estilo Vulkan/Metal/D3D12. WGSL substitui GLSL — sintaxe Rust-like, type-safe, sem preprocessor macros. Validation acontece em createPipeline (não em runtime), erros via device.lost Promise + pushErrorScope/popErrorScope.
Pipeline canônico: adapter → device → commandEncoder → computePass / renderPass → queue.submit().
Compute shader Logística (image diff entre delivery proof e pickup photo, fraud detection client-side):
@group(0) @binding(0) var<storage, read> img_a: array<u32>;
@group(0) @binding(1) var<storage, read> img_b: array<u32>;
@group(0) @binding(2) var<storage, read_write> diff: array<atomic<u32>>;
@compute @workgroup_size(8, 8)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.y * 1024u + gid.x;
if (idx >= arrayLength(&img_a)) { return; }
let a = img_a[idx];
let b = img_b[idx];
let d = abs(i32(a & 0xFFu) - i32(b & 0xFFu));
atomicAdd(&diff[0], u32(d));
}
const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' });
if (!adapter) throw new Error('WebGPU adapter unavailable');
const device = await adapter.requestDevice();
device.lost.then((info) => console.error('GPU device lost:', info.reason, info.message));
const module = device.createShaderModule({ code: SHADER_WGSL });
const pipeline = device.createComputePipeline({
layout: 'auto',
compute: { module, entryPoint: 'main' }
});
const bufA = device.createBuffer({ size: imgA.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
const bufB = device.createBuffer({ size: imgB.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
const bufDiff = device.createBuffer({ size: 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC });
device.queue.writeBuffer(bufA, 0, imgA);
device.queue.writeBuffer(bufB, 0, imgB);
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: bufA } },
{ binding: 1, resource: { buffer: bufB } },
{ binding: 2, resource: { buffer: bufDiff } }
]
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(128, 128); // 1024x1024 / 8x8
pass.end();
const readback = device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST });
encoder.copyBufferToBuffer(bufDiff, 0, readback, 0, 4);
device.queue.submit([encoder.finish()]);
await readback.mapAsync(GPUMapMode.READ);
const totalDiff = new Uint32Array(readback.getMappedRange())[0];
readback.unmap();
Performance real: 4MP image diff em ~5ms desktop GPU (RTX 3060 / M2), ~20ms mobile A17/Adreno 740. CPU equivalente (loop JS single-thread): 200-500ms. Speedup 40-100x justifica complexidade. Workgroup size 8x8=64 threads é sweet spot pra texture-like workload; aumentar pra 16x16=256 pode dar +20% em GPUs desktop, mas estoura register budget em mobile.
WebGPU vs WebGL trade-off: WebGL cobre 95% browsers mas não tem compute shaders, sem storage buffers, e usa estado global imperativo (bind point / texture unit). WebGPU é modern (compute first-class, async errors, explicit command buffers) mas 80-85% coverage. Pattern produção: WebGPU primary + WebGL fallback via if (!navigator.gpu) { useWebGL(); }. Lib wgpu-matrix (matemática) ou three.js r160+ (motor) já suportam WebGPU backend.
WebCodecs API (Baseline 2024 Chromium/Safari, Firefox 130+): hardware-accelerated encode/decode direto em browser via VideoToolbox (Apple), MediaCodec (Android), NVENC/QuickSync (desktop). Substitui hacks MediaRecorder + canvas + getUserMedia que tinham latência variável e zero controle de bitrate/keyframe. APIs principais:
VideoEncoder / VideoDecoder: encode raw frames → H.264/VP9/AV1 chunks; decode chunks → raw frames.AudioEncoder / AudioDecoder: AAC, Opus, FLAC.VideoFrame / AudioData: transferable objects, zero-copy cross-Worker.ImageDecoder: decode JPEG/PNG/WebP/AVIF/GIF off-main-thread.Pipeline Logística (compress delivery proof video em-browser antes de upload, sem aguardar full processing):
const stream = canvas.captureStream(30);
const reader = new MediaStreamTrackProcessor({ track: stream.getVideoTracks()[0] }).readable.getReader();
let frameCount = 0;
const encoder = new VideoEncoder({
output: (chunk, meta) => { /* push pra IndexedDB ou upload streaming via fetch ReadableStream */ },
error: (e) => console.error(e)
});
encoder.configure({ codec: 'avc1.42E01E', width: 1280, height: 720, bitrate: 2_000_000, framerate: 30 });
while (true) {
const { value: frame, done } = await reader.read();
if (done) break;
encoder.encode(frame, { keyFrame: frameCount % 60 === 0 });
frame.close();
frameCount++;
}
await encoder.flush();
encoder.close();
Output H.264 chunks empacotados via mp4-muxer ou webm-muxer (CMAF), upload streaming via fetch com ReadableStream body sem aguardar full processing. Numbers: 1080p 30s → ~5MB H.264 (vs ~50MB raw); encode ~0.5x realtime mobile A14+, ~0.2x desktop GPU. Bitrate 2 Mbps adequado pra delivery proof; subir pra 4 Mbps se cena tem texture (boxes empilhadas).
Feature detection robusta antes de configurar:
const support = await VideoEncoder.isConfigSupported({
codec: 'avc1.42E01E',
width: 1280, height: 720, bitrate: 2_000_000, framerate: 30
});
if (!support.supported) {
// fallback: MediaRecorder com canvas.captureStream()
}
OffscreenCanvas + WebGPU + Worker: main thread canvas.transferControlToOffscreen() → worker.postMessage({ canvas: offscreen }, [offscreen]). Worker pede próprio adapter/device (device é per-document/per-realm, não cross-thread transferable) e faz createCommandEncoder() + render isolado. Libera main thread pra UI/scroll. Combina com WebCodecs: encoder roda em Worker, encoded chunks voltam via postMessage pra main thread fazer upload.
Mobile gotchas: iOS 17.4+ tem WebGPU mas só GPU tier A14+ (iPhone 12+); fallback graceful obrigatório em devices mais antigos. Android Chrome WebGPU OK em GPU Vulkan-capable (Adreno 6xx+, Mali-G7x+); Mali antigos e PowerVR caem pra WebGL. Battery: compute shaders consomem GPU em sustained load >30s ativam thermal throttling, performance cai 30-50%. Detecte via Performance API + requestVideoFrameCallback deltas e degrade qualidade (drop framerate ou resolution) automaticamente.
HDR + wide-color em delivery photos: HDR10/HLG metadata via WebCodecs colorSpace field. Display-P3 wide-color displays renderizam gamut maior; CSS @media (color-gamut: p3). Logística: courier iPhone HDR captura → preserve HDR no upload → dashboard exibe em P3.
Stack Logística aplicado: courier app captura via MediaRecorder + WebCodecs encode → upload streaming. Dashboard lojista: WebGPU compute shader compara photo diff pickup/delivery em <50ms client-side. Edge: image transformations via Imgproxy (cobertos em 03-10 §2.20), não WebGPU.
Observability: instrumente device.lost Promise (telemetry de GPU crashes), pushErrorScope('validation') em dev pra catch shader bugs cedo, e meça encoder.encodeQueueSize pra detectar back-pressure (queue >5 indica encode mais lento que captura, drop frame ou downscale). Real User Monitoring: log encoder.state, codec efetivo (hardware vs software via support.config), e tempo wall-clock por chunk pra correlacionar com device GPU tier.
Decision matrix (quando usar):
filter ou Canvas2D (não justifica GPU setup).03-12.Anti-patterns observados:
workgroup_size apropriado (sub-utilização GPU; 64-256 thread/group sweet spot).device.queue.submit() por command (overhead; batch em encoder).keyFrame regular (chunks dependentes; seek impossível).getUserMedia full resolution sem downscale antes de encode (4K mobile = OOM).mapAsync() em hot path (stalls pipeline; double-buffer e read frame anterior).requestAdapter({ powerPreference: 'high-performance' }) (mobile pega integrated GPU).Cruza com: 02-03 (DOM Web APIs, OffscreenCanvas + Workers), 03-09 (frontend perf, GPU pipeline, image LCP em delivery photos), 02-06 (RN, Skia paralelo nativo), 03-12 (WebAssembly, codec libs Wasm-compiled).
Você precisa, sem consultar:
Construir dashboard ao vivo da Logística com mapa GPU-rendered (deck.gl ou MapLibre + WebGL) + comprovação de entrega via vídeo.
ScatterplotLayer.LineLayer.HeatmapLayer.getUserMedia, encode H.264 via MediaRecorder.VideoEncoder.isConfigSupported).Threshold de Maestria
Acerte todas as 5 pra marcar o módulo como concluído. Sem pressa, sem timer. Tudo fica salvo no teu navegador.
Q1Qual é o frame budget em milissegundos para target de 60fps?
Q2Qual é a diferença chave entre WebGL e WebGPU?
Q3Por que AudioWorklet substitui ScriptProcessorNode em audio processing?
Q4Qual é o latency target apropriado para voz interativa (VoIP)?
Q5Por que WebCodecs API é preferida sobre MediaRecorder + canvas para upload de vídeo?
Destrava
03-14 é prereq dos seguintes módulos: