ajustments on budgets

This commit is contained in:
bQUARKz 2026-03-30 09:56:35 +01:00
parent 201226b892
commit 214a189a5f
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 348 additions and 13 deletions

View File

@ -42,8 +42,8 @@ pub struct VirtualMachineRuntime {
} }
impl VirtualMachineRuntime { impl VirtualMachineRuntime {
pub const CYCLES_PER_LOGICAL_FRAME: u64 = 100_000; pub const CYCLES_PER_LOGICAL_FRAME: u64 = 5_000_000;
pub const SLICE_PER_TICK: u64 = 100_000; pub const SLICE_PER_TICK: u64 = 5_000_000;
pub const MAX_LOG_LEN: usize = 256; pub const MAX_LOG_LEN: usize = 256;
pub const MAX_LOGS_PER_FRAME: u32 = 10; pub const MAX_LOGS_PER_FRAME: u32 = 10;
} }

View File

@ -17,6 +17,10 @@ O renderer `gfx` recompõe a cena inteira a cada frame logico, mesmo quando a mu
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. 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 ## Dor
- custo visual basico cresce demais para hardware simples. - custo visual basico cresce demais para hardware simples.
@ -30,34 +34,362 @@ Hoje `render_all()` reconstrói buckets de sprites, escaneia os 512 sprites, red
## Alvo da Discussao ## Alvo da Discussao
Definir ate onde o v1 precisa sair do modelo brute force para um pipeline com invalidacao e custo previsivel. 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 ## O Que Precisa Ser Definido
1. Granularidade de invalidacao. 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: Escolher entre:
- dirty flag por frame inteiro; - dirty flag por frame inteiro;
- dirty region por layer; - dirty flags por subsistema (`world`, `hud`, `fade`, `sprites`);
- dirty list por sprite/HUD; - dirty regions restritas e derivadas de primitivas/sprites;
- combinacao minima viavel. - combinacao minima viavel.
2. Buckets e traversal de sprites. 3. Buckets e traversal de sprites.
Decidir se os buckets continuam sendo reconstruidos por frame ou se viram estrutura incremental. Decidir se os buckets continuam sendo reconstruidos por frame ou se viram estrutura incremental.
3. Fades. 4. Fades.
Definir se fade neutro precisa bypass total e se fade parcial pode operar por regiao/camada. Definir se fade neutro precisa bypass total e se fade parcial pode operar por regiao/camada.
4. Cache de composicao. 5. Cache de composicao.
Delimitar se layers estaticas/HUD podem manter cache intermediario. Delimitar se layers estaticas/HUD podem manter cache intermediario.
5. Meta de custo. 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. 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 ## Open Questions de Arquitetura
1. O fantasy console quer simular hardware de full redraw ou quer um renderer pragmatico para handheld barato? 1. Quais partes do custo atual estao no `render_all()` e quais partes estao na conversao/apresentacao do host?
2. Dirty regions quebram alguma expectativa de determinismo visivel? 2. Qual o maior ganho de curto prazo dentro do modelo atual: buckets, fades, HUD, primitivas ou host copy/convert?
3. O HUD deve continuar no mesmo pipeline da world scene? 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 ## Dependencias
@ -68,7 +400,10 @@ Definir ate onde o v1 precisa sair do modelo brute force para um pipeline com in
Pode virar PR quando houver decisao escrita sobre: 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; - nivel minimo de invalidacao no v1;
- politica de rebuild de buckets de sprites; - politica de rebuild de buckets de sprites;
- bypass/cache de fade e HUD; - bypass/cache de fade e HUD;
- politica para otimizar primitivas sem mudar a semantica observavel;
- meta de custo para o render pipeline. - meta de custo para o render pipeline.