prometeu-runtime/discussion/workflow/agendas/AGD-0028-deferred-overlay-and-primitive-composition.md

141 lines
8.6 KiB
Markdown

---
id: AGD-0028
ticket: deferred-overlay-and-primitive-composition
title: Deferred Overlay and Primitive Composition over FrameComposer
status: accepted
created: 2026-04-18
updated: 2026-04-18
resolved: 2026-04-18
decision: DEC-0016
tags: [gfx, runtime, render, frame-composer, overlay, primitives, hud]
---
## Contexto
`FrameComposer.render_frame()` hoje recompõe o `back` no fim da logical frame. Quando há scene bound, o caminho `render_scene_from_cache(...)` limpa o buffer e desenha scene + sprites, o que apaga qualquer primitive ou `draw_text(...)` emitido antes via `gfx`.
Isso expôs um conflito de modelo:
- `composer.*` já é o caminho canônico de orquestração de frame;
- `gfx.draw_text(...)` e demais primitives ainda escrevem diretamente no `back`;
- o runtime só chama `render_frame()` no final do frame, então a escrita imediata em `back` deixou de ser semanticamente estável.
- As primitives de `gfx` não são o mecanismo desejado para composição de jogos com scene/tile/sprite; elas existem principalmente como debug, instrumentação visual e artefatos rápidos.
Conteúdo relevante migrado de [AGD-0010](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/agendas/AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md):
- a arquitetura aceita continua sendo de framebuffer destrutivo em memória, não scene graph ou renderer tipo GPU;
- otimizações em primitives devem preservar a semântica observável, mesmo quando ganharem fast paths internos;
- existe preocupação explícita com custo por classe de primitive e com orçamento de memória no alvo handheld;
- caminhos de spans/linhas/clears são desejáveis como aceleração interna, mas sem reabrir o modelo operacional do pipeline do jogo.
## Problema
Precisamos decidir qual é o modelo canônico para primitives e texto no pipeline pós-`FrameComposer`.
Sem isso:
- texto e primitives continuam com comportamento dependente da ordem interna do renderer;
- o stress test e qualquer cartridge que combine `composer.*` com `gfx.*` terão resultado inconsistente;
- fica indefinido se primitives pertencem ao mundo, ao HUD, ou a um overlay final.
## Pontos Criticos
- `draw_text(...)` e primitives screen-space não podem depender de escrita imediata em `back`.
- Para esta thread, primitives de `gfx` devem permanecer agnósticas ao pipeline canônico de render do jogo e não devem ser mescladas semanticamente com tiles/sprites.
- A ordem de composição precisa ser explícita e estável: `scene -> sprites -> HUD -> primitives/debug overlay`, ou outra ordem formal equivalente.
- Precisamos decidir se o contrato público de `gfx.*` muda semanticamente sem mudar ABI, ou se parte dessa superfície migra para `composer.*`.
- A solução deve preservar o caminho sem scene bound.
- A implementação deve evitar contaminar a infraestrutura de `gfx` responsável por scene, sprites e HUD com estado misto de overlay/debug; se necessário, o overlay deve ter fila/fase própria.
- melhorias internas de primitive path devem continuar permitidas, desde que não mudem a semântica de overlay final e não exijam buffers extras incompatíveis com o orçamento de memória aceito.
## Opcoes
### Opcao 1 - Manter escrita direta em `back`
- **Abordagem:** manter `gfx.draw_text(...)` e primitives rasterizando imediatamente.
- **Pro:** zero mudança estrutural agora.
- **Contra:** o modelo continua quebrado sempre que `render_frame()` recompõe o buffer depois.
- **Tradeoff:** só funciona de forma confiável fora do caminho canônico do `FrameComposer`.
### Opcao 2 - Fila única de draw commands pós-scene/pós-sprite
- **Abordagem:** transformar texto e primitives em comandos diferidos, drenados depois de `scene + sprites`.
- **Pro:** resolve o problema imediato de overlay/HUD e estabiliza o stress test.
- **Contra:** mistura HUD e primitives/debug sob o mesmo conceito, reduzindo clareza contratual mesmo quando a ordem prática for a mesma.
- **Tradeoff:** simples para V1, mas semanticamente mais fraco do que separar overlay de jogo e overlay de debug.
### Opcao 3 - Separar HUD diferido de primitives/debug overlay final
- **Abordagem:** tratar `gfx.draw_text(...)` e demais primitives de `gfx` como overlay/debug final, separado da composição canônica de jogo (`scene + sprites + HUD`).
- **Pro:** casa com a intenção declarada para `gfx.*`: debug, artefato rápido e instrumentação visual acima do frame do jogo.
- **Contra:** exige modelar explicitamente uma fase extra no pipeline.
- **Tradeoff:** aumenta a clareza contratual e evita mesclar primitives com o domínio de jogo.
### Opcao 4 - Manter HUD e primitives no mesmo estágio final, mas com categorias separadas
- **Abordagem:** drenar HUD e primitives ambos no fim do frame, porém com filas/categorias distintas e ordem formal `HUD -> primitives`.
- **Pro:** preserva implementação próxima entre caminhos similares, mantendo contrato separado.
- **Contra:** é mais custoso que a opção 3 sem entregar muito valor adicional imediato.
- **Tradeoff:** bom se já houver expectativa de HUD canônico separado no curtíssimo prazo.
## Sugestao / Recomendacao
Seguir com a **Opcao 3**.
Minha recomendação é:
- retirar a escrita direta em `back` como contrato operacional para `gfx.draw_text(...)` e demais primitives de `gfx`;
- introduzir uma fila diferida canônica de primitives/debug overlay drenada no fim do frame;
- tratar `gfx.*` primitive/text como superfície agnóstica ao pipeline de jogo e explicitamente acima da composição canônica;
- não misturar semanticamente primitives com scene/tile/sprite/HUD.
- evitar compartilhar indevidamente o mesmo mecanismo operacional de composição entre overlay/debug e os caminhos de scene/sprite/HUD, mesmo quando o backend de rasterização reutilizado for o mesmo.
Ordem recomendada para o frame canônico:
1. limpar/compor scene;
2. compor sprites;
3. compor HUD canônico, se existir;
4. aplicar `scene_fade`;
5. aplicar `hud_fade`;
6. drenar primitives/debug overlay de `gfx.*`.
## Perguntas em Aberto
- `draw_text(...)` e as demais primitives de `gfx` entram todas na mesma família de overlay final já na V1, ou começamos só com `draw_text(...)`?
- `render_no_scene_frame()` deve usar a mesma fila diferida para manter semântica idêntica com e sem scene?
- HUD canônico precisa existir explicitamente nesta mesma thread, ou pode continuar implícito/externo enquanto as primitives já migram para overlay final?
- quais fast paths internos de primitives continuam desejáveis nessa nova fase, por exemplo spans horizontais/verticais, fills e clears, sem misturar isso com a composição do jogo?
- o overlay/debug final precisa de dirtying próprio por classe de primitive ou isso pode ficar fora da primeira migração?
## Criterio para Encerrar
Esta agenda pode ser encerrada quando tivermos uma resposta explícita para:
- o destino semântico de `draw_text(...)`;
- se haverá uma fila própria para primitives/debug overlay e qual a relação dela com HUD;
- a ordem canônica de composição do frame;
- o escopo exato da primeira migração implementável sem reabrir o restante do pipeline.
## Resolucao Parcial
Direção já aceita nesta agenda:
- primitives e `draw_text(...)` de `gfx.*` devem ser tratadas como overlay/debug final;
- esse overlay deve ser drenado **depois** de `hud_fade`;
- scene, sprites e HUD canônico não devem ser semanticamente misturados com o overlay/debug;
- a implementação deve preservar separação operacional suficiente para que o `gfx` usado pelo pipeline do jogo não passe a depender do estado transitório de primitives/debug;
- otimizações de primitive path discutidas na `AGD-0010` continuam válidas, mas passam a operar dentro do domínio de overlay/debug final, não como parte da composição canônica de scene/sprite/HUD.
## Resolucao
Esta agenda fica aceita com os seguintes pontos fechados:
- `gfx.draw_text(...)` e as demais primitives públicas de `gfx.*` pertencem à mesma família V1 de overlay/debug final;
- esse overlay/debug fica **fora** do `FrameComposer`;
- `FrameComposer` continua restrito à composição canônica do jogo (`scene`, `sprites` e HUD canônico quando existir);
- o overlay/debug deve ser drenado depois de `hud_fade`;
- o caminho sem scene bound deve observar a mesma semântica final de overlay/debug;
- HUD canônico explícito não faz parte desta thread e pode permanecer implícito/externo por enquanto;
- fast paths internos de primitives continuam permitidos, desde que preservem a semântica observável do overlay/debug final;
- dirtying granular ou otimizações finas por classe de primitive não fazem parte da primeira migração normativa desta thread.