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

16 KiB

id ticket title status created resolved decision tags
AGD-0010 perf-gfx-render-pipeline-and-dirty-regions Agenda - [PERF] GFX Render Pipeline and Dirty Regions open 2026-03-27

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

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