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