17 KiB
| id | ticket | title | status | created | updated | tags | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| AGD-0026 | render-all-scene-cache-and-camera-integration | Agenda - Integrate render_all with Scene Cache and Camera | accepted | 2026-04-14 | 2026-04-15 |
|
Contexto
A thread DSC-0025 fechou a base arquitetural para SceneBank, SceneViewportCache, SceneViewportResolver e o decoder binário de SCENE. O renderer já possui um caminho explícito render_scene_from_cache(&SceneViewportCache, &ResolverUpdate), mas o loop operacional do runtime ainda chama apenas render_all().
Hoje, em tick.rs, o frame segue pelo hw.gfx_mut().render_all(). Isso significa que o caminho novo de world render ainda não está integrado ao ciclo normal do runtime.
Ao mesmo tempo, a integração correta depende de fechar o contrato mínimo da câmera, porque o SceneViewportResolver já assume uma posição de câmera em pixel space e produz ResolverUpdate a partir dela. Sem essa integração, o runtime fica com duas verdades práticas:
- a arquitetura aceita para world rendering;
- o caminho ainda efetivamente usado pelo frame loop.
Problema
Precisamos integrar render_scene_from_cache() ao render_all() e ao ciclo real do runtime sem reabrir a arquitetura já aceita para SceneBank / SceneViewportCache / SceneViewportResolver.
O problema concreto não é só “chamar uma função”. É decidir:
- quem é dono do estado de câmera mínimo;
- onde
SceneBank,SceneViewportCacheeSceneViewportResolverpassam a residir em runtime; - quando o cache é atualizado;
- como o
render_all()deixa de ser um caminho “scene-blind” e vira o entrypoint normal da composição final.
Pontos Criticos
render_all()deve continuar funcional mesmo quando nenhuma scene estiver carregada.- O
render_all()atual não desenha world layers; ele só compõe sprites de prioridade 0 e fades. - O modelo atual de
Sprite.prioritymistura duas responsabilidades:- em qual faixa de composição o sprite entra;
- qual a ordem relativa entre sprites naquela faixa.
render_scene_from_cache()existe, mas exigeSceneViewportCacheeResolverUpdatejá preparados por fora.- O modelo atual de sprites ainda é slot-first para o chamador:
- há armazenamento fixo;
- o dev informa índice;
- e o renderer precisa filtrar
active.
- O
SceneViewportResolverjá carrega política importante:- câmera em pixel space
- anchors
- clamp
- histerese
- refresh requests
- copy requests
- Ainda não existe um dono explícito do estado operacional:
- cena ativa
- cache ativo
- resolver ativo
- câmera ativa
- O
FrameComposernão pode regredir o contrato já aceito no scene model:SceneLayer.tile_sizejá é por-layer e aceita8x8,16x16e32x32;- o decoder de
SCENEjá materializa esses tamanhos; - fixar o pipeline em
16x16dentro do orquestrador de frame criaria uma restrição artificial que não existe no modelo canônico.
- Se a integração for mal feita, o renderer pode voltar a misturar:
- política de câmera
- atualização de cache
- composição final
Opcoes
Opcao 1 - Integrar tudo diretamente dentro de Gfx
Como seria:
Gfx passa a possuir a scene ativa, o cache, o resolver e a câmera; render_all() atualiza resolver/cache e já compõe tudo.
Vantagens:
- caminho curto de integração;
- menos objetos atravessando o runtime;
- fácil de chamar a partir do tick.
Desvantagens:
- empurra para
Gfxresponsabilidade demais; - mistura composição com estado de cena/câmera;
- reduz clareza para testes e evolução futura.
Opcao 2 - Integrar no runtime com um controlador explícito de scene viewport
Como seria: O runtime ou um pequeno controlador operacional passa a possuir:
- scene ativa
- cache
- resolver
- câmera
Esse controlador atualiza o cache quando a câmera muda e entrega ao Gfx apenas o que ele precisa para compor.
Vantagens:
- separa melhor estado operacional de composição;
- mantém
Gfxmais focado em render; - preserva a ideia de que o resolver é dono da política de movimento/rematerialização.
- permite manter um caminho explícito de
render_all()sem scene carregada.
Desvantagens:
- adiciona mais um objeto operacional no runtime;
- exige definir uma superfície clara entre runtime e renderer.
Opcao 3 - Fazer uma integração mínima temporária em render_all() e postergar a arquitetura do dono da câmera
Como seria:
Criar um caminho temporário para que render_all() receba ou consulte estado suficiente para chamar render_scene_from_cache(), mas sem ainda fechar onde mora a câmera a longo prazo.
Vantagens:
- acelera a ligação do caminho novo ao frame loop;
- destrava testes end-to-end rapidamente.
Desvantagens:
- alto risco de solução transitória virar definitiva;
- deixa ambiguidade operacional exatamente no ponto mais sensível da integração.
Sugestao / Recomendacao
Seguir com a Opcao 2.
Ou seja:
FrameComposerpassa a ser o orquestrador de frame/scene;FrameComposerdeve morar emhardware/drivers;Hardwarepassa a agregarFrameComposerao lado deGfx;Gfxpermanece como backend de composição e blit;- a política do frame não deve ficar presa ao hardware atual;
- isso preserva espaço para:
- fast paths com diretivas/capacidades de GPU quando existirem;
- uma futura implementação mais próxima de PPU / bare metal;
render_all()deve continuar sendo o entrypoint normal de composição;render_all()deve continuar funcionando mesmo sem scene ativa;- mas ele não deve virar dono da câmera nem do ciclo de atualização do cache;
- precisamos de um orquestrador operacional no runtime, ou imediatamente adjacente a ele, que:
- mantenha a scene ativa opcional;
- mantenha a câmera / viewport mínima;
- mantenha o controlador de sprites do frame;
- atualize o
SceneViewportResolverquando houver scene; - aplique refreshes ao
SceneViewportCachequando houver scene; - e entregue ao
Gfxo estado pronto para compor.
Mais explicitamente:
FrameComposerpassa a ser dono de:- scene ativa;
- câmera / viewport;
SceneViewportCache;SceneViewportResolver;- sprites emitidos no frame;
- o state de scene/sprite que hoje esteja em
Gfxdeve migrar paraFrameComposer; Gfxdeve ficar focado em:- composição;
- blit;
- raster;
- execução visual do frame preparado.
Para V1, o contrato mínimo de câmera pode continuar pequeno:
camera_x_px: i32camera_y_px: i32- representando o canto superior esquerdo da viewport no mundo
Sem follow/smoothing/shake/cut nesta etapa.
O comportamento mínimo recomendado fica:
- sem scene ativa:
FrameComposercontinua válido;render_all()compõe apenas o que já existe fora do pipeline de world (sprites,fades, e futuramenteHUDquando aplicável);- não existe
clearimplícito; - limpar o
backcontinua sendo responsabilidade explícita do chamador / dev;
- com scene ativa:
FrameComposeratualiza resolver/cache;render_all()compõe o world a partir do cache e preserva a ordem já aceita.
Esta direção é provisoriamente aceita mesmo sem a figura final completa, justamente para permitir que a integração avance e revele os pontos onde a separação runtime/backend ainda precise de ajuste.
Para sprites, a direção provisória recomendada fica:
- cada
Spritedeve carregar:layerpriority
Sprite.activedeve ser removido;layerdefine em qual faixa de composição o sprite entra;prioritydefine a ordenação entre sprites daquela mesma faixa;- a composição observável passa a ser por camada:
(sprites -> scene) layer_0(sprites -> scene) layer_1(sprites -> scene) layer_2(sprites -> scene) layer_3
Isso substitui o modelo atual em que um único priority tenta representar ao mesmo tempo posição macro na composição e ordenação fina.
O modelo operacional recomendado para sprites passa a ser:
- capacidade máxima interna de
512sprites por frame; - contador zerado a cada frame;
- o dev não informa mais índice de sprite;
- cada emissão ocupa o próximo slot interno disponível;
- o registro já coloca o sprite no bucket correto da layer;
- a composição consome apenas os sprites emitidos naquele frame.
Perguntas em Aberto
- Fechado provisoriamente:
FrameComposeremhardware/drivers;HardwareagregaFrameComposereGfx;Gfxatua como backend operacional de composição.
- O contrato mínimo do
FrameComposerprecisa ser fechado normativamente. - Fechado:
- o subsistema interno de sprites se chama
SpriteController.
- o subsistema interno de sprites se chama
Sprite.layerdeve ser um enum fechado (Layer0..Layer3) ou um tipo mais genérico?- fechado provisoriamente:
- manter numérico;
- usar o mesmo tipo/referência de layer do
SceneBank.
- fechado provisoriamente:
- A composição por camada deve ser:
sprites -> scenedentro de cada layer, como direção inicial,- ou
scene -> spritespara alguma camada específica?
- A ordenação entre sprites de uma mesma layer será:
- fechado:
prioritymenor blita primeiro;- em empate, FIFO por ordem de registro.
- fechado:
- Overflow de sprite no frame:
- fechado: excedentes são ignorados;
- deve existir espaço para log/telemetria;
- futuramente isso pode virar sinal negativo para certificação.
emit_sprite(...)precisa retornar algo, ou ter reset separado além debegin_frame()?- fechado por enquanto:
- não;
- usar apenas log do sistema para overflow/eventos operacionais;
- não introduzir reset extra além do fluxo normal do frame.
- fechado por enquanto:
render_all()deve:- continuar sem parâmetros e consultar estado já preparado,
- ou ganhar uma nova superfície interna para receber o scene state preparado?
- direção aceita:
FrameComposerchama o entrypoint de composição do backend visual;Gfx.render_all()deve morrer;- o serviço deve migrar para
FrameComposer.renderFrame().
Contrato Minimo Proposto
Direção proposta para V1 do FrameComposer:
-
bind_scene(...)- recebe um
scene bank id; FrameComposerdeve possuir acesso aSceneBankPoolAccess;- resolve a scene ativa através do pool;
- o acesso ao bank deve ser sempre por ponteiro / referência compartilhada, nunca por cópia;
- ao bindar, o compositor guarda:
scene_bank_id;Arc<SceneBank>já resolvido;
- consegue verificar se a scene está carregada;
- inicializa ou reinicializa cache/resolver conforme necessário.
- recebe um
-
unbind_scene()- remove a scene ativa;
- invalida o pipeline de world;
- descarta o cache associado à scene bindada;
- mantém o compositor funcional para
sprites + fades.
-
set_camera(x, y)- atualiza a posição da câmera em pixel space;
x, yrepresentam o canto superior esquerdo da viewport no mundo.
-
begin_frame()- zera o contador de sprites emitidos;
- limpa buckets internos de sprite;
- prepara o estado transitório do frame.
-
emit_sprite(...)- registra um sprite no próximo slot interno disponível;
- associa o sprite à sua
layer; - insere no bucket correspondente;
- overflow é ignorado com espaço para log/telemetria.
-
compose_frame()- se houver scene ativa:
- atualiza
SceneViewportResolver; - aplica
CacheRefreshRequests aoSceneViewportCache; - aciona o caminho de composição world + sprites;
- atualiza
- se não houver scene ativa:
- aciona o caminho
sprites + fades;
- aciona o caminho
- delega a composição efetiva ao
Gfx.
- se houver scene ativa:
Observacoes
end_frame()não parece obrigatório na V1.begin_frame()+compose_frame()já cobrem o ciclo mínimo.FrameComposerdecide e prepara;Gfxexecuta a composição.- o binding de scene deve ser por
scene bank id, não por ownership direto deSceneBank. - o
SceneViewportCachevive dentro doFrameComposerenquanto a scene estiver bindada. - troca do conteúdo do slot/bank exige novo
bind_scene(...); oFrameComposernão deve ficar fazendo polling constante do pool para revalidar a scene ativa. - o fluxo operacional aceito é:
FrameComposer.compose_frame()- chama o serviço
FrameComposer.renderFrame().
FrameComposerdeve ser capaz de renderizar algo 100% do tempo:- cache/resolver ficam
Nonesem bind; - deve existir uma forma explícita de saber se a scene está disponível para render.
- cache/resolver ficam
bind_scene(...)substitui completamente a scene anterior.
Sugestao / Recomendacao Atualizada
Aceitar o contrato mínimo acima como base de fechamento da agenda, a menos que apareça alguma necessidade concreta de:
- separar
compose_frame()em múltiplas fases públicas; - expor refresh manual de cache para o chamador;
- ou introduzir um
end_frame()com semântica real além do reset que já ocorre embegin_frame(). - manter o binding de scene como:
scene_bank_id + Arc<SceneBank>;- com rebind explícito quando o slot mudar.
- Quem é responsável por aplicar
CacheRefreshRequestaoSceneViewportCache:- fechado: sempre o
FrameComposer.
- fechado: sempre o
- Qual é o contrato explícito de “nenhuma scene carregada”:
- fechado:
sprites + fades, semclearimplícito.
- fechado:
- Como a cena ativa é selecionada e trocada no ciclo real:
- fechado:
bind_scene(scene_bank_id)com resolução através deSceneBankPoolAccess.
- fechado:
- O HUD entra nesta integração já agora, ou o foco da primeira integração é apenas world + sprites + fades?
- fechado: sem HUD nesta primeira integração.
Reabertura 2026-04-15
Contexto adicional
Ao revisitar a thread, apareceu uma restrição indevida: tratar o FrameComposer como se aceitasse apenas tilesets 16x16.
Isso conflita com o estado atual do runtime:
TileSizeno HAL já enumeraSize8,Size16eSize32;SceneLayercarregatile_sizepor layer;- o decoder de
SCENEjá aceita8,16e32; SceneViewportResolvereSceneViewportCachejá calculam offsets, anchors e cópia a partir dotile_sizeda própria layer.
O risco aqui não é apenas de implementação. Se o contrato do FrameComposer assumir 16x16 como pré-condição, ele quebra a neutralidade do orquestrador e reabre uma limitação artificial acima do scene model.
Problema reaberto
Precisamos fechar explicitamente que o FrameComposer aceita cenas/layers com tile_size 8x8 e não impõe 16x16 como tamanho mínimo ou obrigatório para o world path.
Opcoes adicionais
Opcao 4 - Fixar 16x16 no FrameComposer e tratar 8x8 como fora de escopo
Vantagens:
- reduz casos de teste imediatos;
- simplifica implementação inicial se alguém estiver assumindo viewport/caches calibrados manualmente para
16.
Desvantagens:
- contradiz o scene model já aceito;
- introduz restrição artificial no orquestrador;
- obriga futura revisão de contrato para reaceitar algo que a base já suporta.
Opcao 5 - Manter FrameComposer tile-size agnostic e aceitar 8x8 desde V1
Vantagens:
- preserva o contrato canônico já existente em
SceneLayer; - mantém o
FrameComposercomo orquestrador, não como redefinidor de formato; - evita bifurcação entre pipeline de scene e pipeline de composição.
Desvantagens:
- exige deixar isso explícito na decisão e nos planos;
- aumenta a exigência de testes para viewport/cache/cópia com
8x8.
Recomendacao adicional
Seguir com a Opcao 5.
Norma proposta para fechamento desta reabertura:
FrameComposerdeve aceitar scenes/layers cujotile_sizeresolvido seja8x8,16x16ou32x32;FrameComposernao deve impor16x16como pré-condição para bind, cache, resolver ou composição;- qualquer validação de compatibilidade deve ser derivada do
tile_sizedeclarado pela própria layer / glyph bank, nunca de um default rígido no compositor; - os planos derivados desta thread precisam citar testes explícitos para
8x8.
Criterio para Encerrar
Esta agenda pode ser encerrada quando estiver explícito:
- quem é dono do estado mínimo de câmera;
- quem é dono da scene/cache/resolver ativos;
- como funciona o bind/unbind da scene ativa;
- quando o cache é atualizado;
- como
render_all()passa a compor o world path aceito; - que o
FrameComposerpermanece agnóstico aotile_sizecanônico da layer e aceita8x8sem downgrade contratual; - e qual é a superfície mínima de integração para implementação sem reabrir a arquitetura base.
Resolucao
Esta agenda fica aceita com a seguinte direcao:
Gfx.render_all()deve ser aposentado;- o fluxo operacional deve convergir para
FrameComposer.render_frame(); FrameComposervive emhardware/drivers, ao lado deGfx, e passa a ser dono do estado operacional do frame;FrameComposerdeve manter:- scene ativa opcional;
- camera/viewport;
SceneViewportCache;SceneViewportResolver;SpriteController;
- scene ativa e acessada por
scene_bank_id + Arc<SceneBank>viaSceneBankPoolAccess, sem copias; - troca de slot exige novo
bind_scene(...); - sem scene ativa, o frame continua valido com
sprites + fades, semclearimplicito; - sprites passam a ser emitidos por frame, sem
Sprite.active, com capacidade maxima de512, overflow ignorado e ordenacao porlayer,priority, e FIFO em empate; - HUD fica fora desta primeira integracao.