--- id: AGD-0010 ticket: perf-gfx-render-pipeline-and-dirty-regions title: Agenda - [PERF] GFX Render Pipeline and Dirty Regions status: open created: 2026-03-27 resolved: decision: tags: [] --- # Agenda - [PERF] GFX Render Pipeline and Dirty Regions ## Problema O renderer `gfx` recompõe a cena inteira a cada frame logico, mesmo quando a mudanca visual e pequena. Hoje `render_all()` reconstrói buckets de sprites, escaneia os 512 sprites, redesenha layers e aplica dois passes fullscreen de fade sem politica de invalidacao. Ao mesmo tempo, a arquitetura atual ja opera como um framebuffer destrutivo em memoria: draws escrevem diretamente no buffer de trabalho e operacoes posteriores sobrescrevem o que estiver por baixo. A discussao nao e migrar para um modelo tipo GPU, scene graph ou retained rendering. Existe ainda um driver de produto importante: o objetivo continua sendo viabilizar hardware handheld proprio com limitacoes reais de orcamento e com economia de memoria o mais agressiva possivel. Isso significa que ganhos de CPU nao podem ser avaliados isoladamente; custo de RAM adicional, buffers extras e estruturas auxiliares entram diretamente no criterio de aceitacao. ## Dor - custo visual basico cresce demais para hardware simples. - pequenos updates pagam preco de full redraw. - fade, HUD e world composition ficam sempre no caminho critico. ## Hotspots Atuais - [gfx.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-drivers/src/gfx.rs#L563) - [gfx.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-drivers/src/gfx.rs#L671) ## Alvo da Discussao Definir ate onde o v1 pode sofisticar o modelo de desenho destrutivo em memoria para reduzir custo e manter previsibilidade, sem migrar para um renderer de estilo GPU. ## O Que Precisa Ser Definido 1. Perfil real de custo. Medir separadamente: - rebuild de buckets; - rasterizacao de layers; - rasterizacao de sprites; - fades fullscreen; - conversao/copia no host; - apresentacao no host. 2. Granularidade minima de invalidacao compativel com framebuffer destrutivo. Escolher entre: - dirty flag por frame inteiro; - dirty flags por subsistema (`world`, `hud`, `fade`, `sprites`); - dirty regions restritas e derivadas de primitivas/sprites; - combinacao minima viavel. 3. Buckets e traversal de sprites. Decidir se os buckets continuam sendo reconstruidos por frame ou se viram estrutura incremental. 4. Fades. Definir se fade neutro precisa bypass total e se fade parcial pode operar por regiao/camada. 5. Cache de composicao. Delimitar se layers estaticas/HUD podem manter cache intermediario. 6. Primitivas e contrato operacional. Definir quais draws devem continuar sendo vistos como escrita destrutiva direta no framebuffer e onde valem aceleracoes internas sem mudar a semantica observavel. 7. Meta de custo. Fechar qual teto de pixels/trabalho por frame e aceitavel no baseline. 8. Orcamento de memoria. Definir quais otimizacoes aceitam memoria adicional e quais precisam caber no modelo mais economico possivel para handheld barato. ## Open Questions de Arquitetura 1. Quais partes do custo atual estao no `render_all()` e quais partes estao na conversao/apresentacao do host? 2. Qual o maior ganho de curto prazo dentro do modelo atual: buckets, fades, HUD, primitivas ou host copy/convert? 3. Dirty regions restritas quebram alguma expectativa de determinismo visivel ou sao apenas um detalhe interno de implementacao? 4. O HUD deve continuar no mesmo pipeline da world scene? 5. Quais primitivas merecem tratamento especial para reduzir overdraw sem mudar a semantica de sobrescrita? 6. Qual o teto aceitavel de memoria extra para caches, buffers separados ou estruturas auxiliares no handheld alvo? ## Problemas de Medio e Grande Porte Identificados ### 1. Composicao completa do framebuffer a cada frame logico `render_all()` recompõe o `back` inteiro por software, mesmo quando a mudanca visual e pequena. Impacto: - custo cresce com a quantidade de pixels tocados, nao apenas com a quantidade de estado alterado; - layers, sprites e fades disputam o mesmo budget de CPU; - o modelo atual favorece previsibilidade sem ainda ter atalhos internos suficientes. ### 2. Rasterizacao pixel a pixel de tilemaps Cada layer visivel percorre tiles e, para cada tile nao vazio, resolve indices e escreve pixel a pixel no framebuffer. Impacto: - o custo real esta mais na escrita de pixels do que na manutencao do `TileMap`; - scroll e barato como estado, mas caro na hora de compor a imagem se tudo for repintado; - layers estaticas continuam pagando custo de rasterizacao em toda recomposicao. ### 3. Rasterizacao pixel a pixel de sprites e escolha por bucket full scan Os 512 sprites sao varridos para reconstruir buckets e os sprites ativos sao desenhados pixel a pixel. Impacto: - quando poucos sprites mudam, ainda existe custo fixo de rebuild dos buckets; - overdraw entre sprites e layers pode explodir o numero de writes no `back`; - a escolha de sprites ativos/prioridade ainda e simples demais para cenarios com alta ocupacao. ### 4. Fades fullscreen com custo proporcional ao frame inteiro `scene fade` e `hud fade` percorrem o framebuffer inteiro quando ativos. Impacto: - custo grande e previsivel, mas potencialmente desproporcional; - fade neutro ja pode ser bypass, mas fade parcial ainda custa uma passada completa; - dois passes fullscreen no mesmo frame podem virar gargalo antes de layers ou sprites. ### 5. HUD no mesmo caminho critico da composicao principal O HUD e redesenhado como tilemap depois da world scene e antes do `hud fade`. Impacto: - HUD estatico continua gerando trabalho por frame; - mistura responsabilidades de world/UI no mesmo budget de composicao; - dificulta isolar ganho de cache ou dirtying so para interface. ### 6. Conversao obrigatoria de RGB565 para RGBA8 no host No `RedrawRequested`, o host percorre todo o `front_buffer` e converte para o frame do `pixels`. Impacto: - ha custo de leitura do framebuffer inteiro e escrita de um segundo buffer inteiro; - esse custo existe mesmo quando o host esta apenas apresentando o mesmo quadro logico; - pode competir com o custo do renderer sem estar visivel na discussao do `gfx`. ### 7. Pipeline serializado no host desktop `tick`, `render_all()`, conversao de formato e apresentacao ocorrem no mesmo fluxo operacional. Impacto: - o frame time final acumula custo de runtime, composicao, copy/convert e present; - falta desacoplamento para esconder latencia entre producao e apresentacao; - dificulta atribuir gargalo sem instrumentacao por etapa. ## Estudo Inicial de Possiveis Optimizacoes ### A. Dirty flags por subsistema em vez de dirty rect generico Ideia: - flags independentes para `world`, `sprites`, `hud`, `scene_fade`, `hud_fade`, `host_present`. Vantagens: - preserva a semantica de framebuffer destrutivo; - reduz recomposicao desnecessaria quando so um subsistema mudou; - e muito mais simples de validar do que dirty regions arbitrarias. Riscos: - exige definir dependencias claras entre world, sprites, HUD e fades; - pode induzir falsos positivos conservadores, o que e aceitavel no v1. ### B. Rebuild incremental ou condicional dos buckets de sprite Ideia: - so reconstruir buckets quando algum `GfxSetSprite` mudar atividade, prioridade, banco ou tile; - opcionalmente manter contadores/sinais de mutacao da OAM. Vantagens: - elimina custo fixo por frame quando OAM permanece estavel; - combina com a ideia de sprites como estado, nao como draw list efemera. Riscos: - precisa invalidacao correta para evitar bucket desatualizado; - ganho pode ser pequeno se o custo dominante estiver no draw dos sprites, nao no rebuild. ### C. Separar world e HUD em buffers logicos distintos Ideia: - manter um buffer da world scene e um buffer do HUD, compondo no final apenas quando necessario. Vantagens: - HUD estatico deixa de participar da recomposicao da world; - permite cache e fade especificos para cada dominio; - se alinha bem com a separacao conceitual entre cena e interface. Riscos: - aumenta uso de memoria e pontos de sincronizacao; - precisa manter semantica clara de sobrescrita entre mundo e HUD. - pode ser inviavel no perfil de handheld barato se exigir buffers adicionais permanentes. ### D. Cache de layer estatica ou composicao parcial da world Ideia: - layers que nao mudam podem manter imagem intermediaria pronta; - scroll, tile updates ou troca de bank invalidam o cache correspondente. Vantagens: - reduz rasterizacao repetida de cenario estavel; - aproxima o ganho de hardware tile-based sem abandonar software raster. Riscos: - cache amplo demais vira complexidade estrutural; - se scroll muda constantemente, o ganho cai bastante. - pode consumir memoria demais para um hardware com orcamento agressivo. ### E. Otimizacao de fades Ideia: - bypass total para fade neutro; - opcionalmente aplicar fade apenas sobre buffers relevantes; - estudar LUTs ou blend mais barato por pixel. Vantagens: - ataca um custo fullscreen claramente delimitado; - baixo risco conceitual. Riscos: - fade parcial por regiao pode complicar demais o contrato; - LUT ajuda aritmetica, mas nao elimina custo de varrer memoria. ### F. Melhorias em primitivas e spans de memoria Ideia: - acelerar `fill_rect`, linhas horizontais/verticais, clears e possiveis blits contiguos; - explorar caminhos de row spans contiguos em vez de loops mais genericos. Vantagens: - melhora direta do modelo de desenho destrutivo; - baixo risco arquitetural; - cria fundacao para outros atalhos internos. Riscos: - ganho localizado se o workload dominante vier de tile/sprite compositing; - precisa medir por primitive class. ### G. Reduzir custo de copy/convert no host Ideia: - medir separadamente `draw_rgb565_to_rgba8` e `pixels.render()`; - estudar formato mais proximo do host ou conversao mais barata; - evitar redraw host quando nao houver novo front relevante. Vantagens: - ataca custo fora do `gfx` que ainda entra no frame time total; - pode render ganho imediato no desktop host. Riscos: - parte do custo depende da stack `pixels/wgpu`, nao apenas do runtime; - alguns ganhos podem ser especificos do host desktop e nao do contrato do console. ## Restricao de Plataforma Qualquer recomendacao desta agenda deve ser filtrada por um criterio adicional: - priorizar solucoes que melhorem custo sem multiplicar buffers; - tratar memoria extra como recurso escasso de primeira classe; - preferir flags, metadados pequenos e estruturas incrementais a caches grandes; - evitar solucoes cuja performance dependa de assumir um host desktop mais forte do que o handheld alvo. Direcao atual da discussao: - nesta etapa, buffers extras devem ficar fora; - o foco deve ser otimizar ao maximo o pipeline existente; - cache intermediario ou novos buffers so entram em estudo depois que as otimizacoes de baixo custo de memoria forem medidas e esgotadas. ## Sugestao / Recomendacao Atual Priorizar o estudo em camadas, nesta ordem: 1. instrumentar o pipeline por etapa; 2. validar dirty flags por subsistema como mecanismo minimo de invalidacao; 3. testar rebuild condicional de buckets e bypass/isolamento de fades; 4. otimizar primitivas e caminhos contiguos de escrita no framebuffer atual; 5. estudar separacao world/HUD e cache de layer apenas se os dados justificarem e o teto de memoria permitir; 5. tratar copy/convert do host como frente paralela de otimizacao, nao como substituto da analise do `gfx`. ## Achados Consolidados Ate Aqui 1. O contrato base continua sendo framebuffer destrutivo em memoria. Draws escrevem diretamente no buffer de trabalho e operacoes posteriores sobrescrevem o que estiver por baixo. A agenda nao aponta para migracao a um renderer tipo GPU, retained mode ou compositor sofisticado. 2. O `present()` atual nao parece ser o problema principal. No `gfx`, `present()` faz swap de buffers, nao copia completa de pixels. 3. O custo suspeito esta na recomposicao e na apresentacao, nao na manutencao do estado logico. Escritas de `TileMap`, scroll e `GfxSetSprite` sao pequenas e pontuais; o custo potencialmente dominante esta em rasterizar pixels, aplicar fades fullscreen e converter o framebuffer para o host. 4. Os maiores suspeitos atuais de custo sao: - rasterizacao de layers; - rasterizacao de sprites; - fades fullscreen; - conversao `RGB565 -> RGBA8` no host; - `pixels.render()` / apresentacao no host. 5. O pipeline atual deve ser otimizado antes de considerar buffers extras. A direcao aceita da discussao e esgotar primeiro flags pequenas, bypasses, melhorias de primitive paths e ajustes incrementais no pipeline existente. 6. Restricao de plataforma pesa tanto quanto CPU. Como o alvo e um handheld proprio com limitacoes de orcamento e memoria, solucoes que consumam RAM adicional significativa devem ficar fora desta fase. 7. A instrumentacao minima desejada para retomar o estudo ficou definida. Quando houver workload representativo, queremos medir ao menos: - `render_all_total_us` - `bucket_rebuild_us` - `layer_raster_us` - `sprite_raster_us` - `scene_fade_us` - `hud_raster_us` - `hud_fade_us` - `host_convert_us` - `host_present_us` 8. A retencao de metricas deve ser barata e orientada a analise posterior. A preferencia atual e por acumuladores e ring buffer pequeno de snapshots, sem logging pesado por frame no hot path. 9. Ring buffer no `TileMap` nao e prioridade nesta fase. O custo suspeito nao esta na manutencao da grade logica do mapa, e sim na composicao da janela visivel no framebuffer. Mudar a estrutura do mapa so faria sentido se o gargalo principal estivesse em streaming/atualizacao estrutural do cenario, o que nao e a hipotese atual. 10. A direcao de otimizacao mais promissora e padronizar copias massivas para o framebuffer atual. Em vez de atacar apenas tilemaps com loops pixel a pixel, a discussao passa a favorecer um padrao geral de blit/copia massiva com: - calculo previo de offsets e spans; - fast paths para casos contiguos; - trabalho por linha/chunk em vez de trabalho atomizado por pixel quando possivel; - reaproveitamento dessa infraestrutura para tilemaps, sprites e outras operacoes de desenho destrutivo. 11. Migrar o `back` para `RGBA8888` nao e direcao aceita neste momento. Isso aumentaria custo de memoria e largura de banda no alvo handheld, alem de deslocar a otimizacao para o host desktop. O contrato interno continua preferencialmente em `RGB565`, mesmo considerando fades e uma futura pipeline de lights. ## Nova Direcao Tecnica Em Estudo Quando esta agenda for reaberta, a linha principal de investigacao deve considerar: - manter o `TileMap` simples como estado logico; - assumir que a janela visivel provavelmente continuara sendo recomposta na maior parte dos frames; - concentrar a otimizacao em como essa recomposicao escreve no `back`; - desenhar uma infraestrutura compartilhada de copias massivas/blits para o framebuffer atual; - usar essa infraestrutura nao apenas para tilemaps, mas como base comum para raster de sprites e outros caminhos de desenho. Pergunta orientadora da reabertura: - como transformar o renderer de um pipeline de writes atomizados por pixel em um pipeline com fast paths de spans/chunks, sem perder a semantica de framebuffer destrutivo e sem pagar memoria extra relevante? ## Status de Standby Esta agenda deve ficar em espera ate existir um game ou workload real que exercite de forma representativa: - tilemaps com scroll e composicao de world; - sprites em quantidade suficiente para testar buckets e overdraw; - HUD ativo; - fades; - apresentacao completa no host. Antes disso, qualquer conclusao sobre gargalo ou prioridade de otimizacao tende a ser prematura. ## Proximo Gatilho Para Reabrir Reabrir esta agenda quando houver: - um game jogavel ou cena de teste que percorra o pipeline completo; - dados de workload mais proximos do uso real; - necessidade concreta de justificar uma rodada de instrumentacao e profiling. ## Dependencias - `../specs/04-gfx-peripheral.md` - `../specs/11-portability-and-cross-platform-execution.md` ## Criterio de Saida Desta Agenda Pode virar PR quando houver decisao escrita sobre: - filosofia explicita de framebuffer destrutivo como contrato base; - plano de instrumentacao para localizar o custo dominante do pipeline; - nivel minimo de invalidacao no v1; - politica de rebuild de buckets de sprites; - bypass/cache de fade e HUD; - politica para otimizar primitivas sem mudar a semantica observavel; - meta de custo para o render pipeline.