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

8.6 KiB

id ticket title status created updated resolved decision tags
AGD-0028 deferred-overlay-and-primitive-composition Deferred Overlay and Primitive Composition over FrameComposer accepted 2026-04-18 2026-04-18 2026-04-18 DEC-0016
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:

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