prometeu-runtime/discussion/workflow/agendas/AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md
2026-03-30 09:56:35 +01:00

410 lines
16 KiB
Markdown

---
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.