stack-RFC-025 — Seleção de dispositivo GPU e política de energia (contrato compartilhado, backends por-engine)
Define COMO componentes da Koder Stack decidem em qual GPU física rodar (iGPU integrada vs dGPU discreta, em máquinas híbridas) e como gerenciam a energia da dGPU. A decisão central é **separar política de mecanismo**: (1) **Contrato/política compartilhado (camada 1)** — um único modelo que todos os componentes herdam: preferência por-superfície (`auto`/`igpu`/`dgpu`), heurística de "este workload merece a dGPU", lifecycle de power-management (dGPU dorme em runtime-D3 quando ociosa), modelo de override do usuário e naming. Vive no backbone **Kore** como spec normativa + um helper fino no SDK (`koder_kit`). (2) **Backends por-engine (camada 2)** — cada engine de render implementa o mecanismo conformando ao contrato: **Kroma** (apps nativos, via seleção de adapter wgpu) → todos os apps Kroma herdam de graça; **Kruze** (Chromium/CEF, via GPU-process pinning — KRUZE-277); **caminho de vídeo** (decode HW VA-API/NVDEC). (3) **Apps (camada 3)** — herdam via seu engine, sem código próprio de GPU. Decisão explícita: **NÃO** centralizar a implementação inteira no Kore/Kroma (impossível para o Kruze, que embarca um engine de terceiro com subsistema de GPU próprio), e **NÃO** reimplementar por módulo (drift de thresholds/power-lifecycle/UX). O comum sobe como contrato; o específico fica no engine.
1. Problema
Máquinas híbridas (laptop com iGPU Intel + dGPU NVIDIA — ex.: o Avell do owner) têm duas GPUs físicas. Componentes Koder que renderizam (apps nativos, o navegador Kruze, players de vídeo) precisam decidir qual GPU usar e quando ligar/desligar a dGPU (que consome muito mais energia/calor e, no laptop do owner, é historicamente frágil — manter em runtime-D3 quando ociosa é mandatório). Hoje:
- Não existe nenhuma spec/política de GPU na Stack.
- O KRUZE-277 está prestes a implementar isso só para o Kruze.
- Sem um contrato comum, cada componente inventaria seu próprio modelo
(thresholds, nomes de setting, lifecycle de energia) → drift garantido.
2. Decisão — separar política de mecanismo (3 camadas)
Camada 1 — Contrato/política compartilhado (Kore + SDK)
Um único modelo normativo que todo componente segue. Conteúdo:
GpuPreferencepor-superfície:auto(default) ·igpu·dgpu.Superfície = abajanela (Kruze), surfaceview (Kroma), pipeline de mídia.
- Heurística "merece dGPU" (em
auto): sinais canônicos — contextoWebGLWebGPU
high-performance, canvas grandealto-DPI, vídeo ≥4K, tempo de GPU sustentado acima de limiar. Os thresholds são definidos aqui; a coleta dos sinais é da camada 2. - Power-management lifecycle: dGPU permanece em runtime-D3 até existir ≥1
superfície roteada pra ela; volta a D3 N segundos após a última fechar. Contrato define o lifecycle + o budget; o engine executa o spawn/release.
- Modelo de override do usuário: setting tri-state por-superfície +
memória por-site/por-app + indicador visual ("rodando na GPU").
- Naming + telemetria: nomes de setting, labels, métricas (observability-
first) padronizados — um vocabulário só em toda a Stack.
Vive como spec normativa no Kore (engines/ backbone) + um helper fino no koder_kit (tipos GpuPreference/GpuRouter + a heurística pura, sem I/O de engine). Reuse-first: ≥3 consumidores → SDK justificado.
Camada 2 — Backends por-engine (conformam ao contrato)
| Engine | Componentes | Mecanismo |
|---|---|---|
| Kroma (wgpu/Vello) | apps Koder nativos | seleção de adapter wgpu (PowerPreference + enumeração) — todos os apps Kroma herdam |
| Chromium/CEF | Kruze (KRUZE-277) | GPU-process pinning (env var __NV_PRIME_RENDER_OFFLOAD/DRI_PRIME no Linux; LUID no Windows; powerPreference nativo no macOS) |
| Decode de vídeo | players (mpv etc.) | VA-API/NVDEC — escolha de device de decode |
Cada backend: (a) coleta os sinais da heurística no vocabulário do seu engine, (b) executa o roteamento/spawn no device escolhido, (c) reporta o estado pro indicador/telemetria do contrato.
Camada 3 — Apps
Herdam via seu engine. Um app Kroma não escreve código de GPU; ganha o roteamento + power-management do backend Kroma. Zero duplicação.
3. Por que não os extremos
- "Tudo no Kore/Kroma" ❌ — o Kroma só governa apps nativos dele. O Kruze
embarca o Chromium, que tem subsistema de GPU process próprio que o Kroma não controla; forçar quebraria o caso mais visível. A detecção também é engine-específica (camadas do compositor do Chromium ≠ primitivas de cena do Kroma).
- "Cada módulo por si" ❌ — duplicar thresholds, lifecycle de energia e
modelo de setting em cada módulo deriva e contradiz reuse-first.
4. Escopo — o que NÃO é deste RFC
- Per-elemento numa GPU: já é nativo do Chromium (compositing layers) e do
Kroma (cena na GPU). Sem trabalho.
- Per-elemento entre DUAS GPUs físicas: inviável (cópia cross-PCIe por
frame anula o ganho). Fora de escopo permanentemente.
5. Plano de adoção (slices)
- S1 — contrato (camada 1): ratificar o modelo + escrever a spec no Kore +
tipos/heurística pura no
koder_kit. Bloqueia os backends (define a interface que eles implementam).- S1a — spec normativa: ✅ LANDED 2026-06-27 →
meta/docs/stack/specs/gpu/device-selection.kmd(R1–R12, T1–T6). - S1b — tipos/heurística + persistência no
koder_kit: ✅ DONE 2026-06-27—
koder_kit#101(tipos+GpuRouter, 0.71.0) +#102(GpuPreferenceStore, 0.72.0). 1919 testes off-laptop. S1 completo → S2S3 destravados.
- S1a — spec normativa: ✅ LANDED 2026-06-27 →
- S2 — backend Kruze: KRUZE-277 implementa contra o contrato (vira o
backend Chromium; deixa de inventar modelo próprio).
- S1 spike ✅ 2026-06-27: per-TAB routing inviável (CEF usa 1 GPU process
compartilhado) → reframe pra per-window/per-instance (2º processo host CEF pinado à dGPU). Linux ainda gated por GPU-OSR (KRUZE-199/200, software-only hoje).
- S1 spike ✅ 2026-06-27: per-TAB routing inviável (CEF usa 1 GPU process
- S3 — backend Kroma: quando o Kroma existir (roadmap), implementa o
backend nativo → apps herdam.
- S4 — backend vídeo: formaliza o caminho VA-API/NVDEC sob o mesmo modelo
de preferência/telemetria.
- S5 — UX comum: indicador + setting tri-state + memória por-site, via SDK.
- SDK widgets ✅ DONE 2026-06-27 —
koder_kit#103(KoderGpuPreferenceControl + KoderGpuIndicator, 0.73.0). Per-engine wiring remains in the backends.
- SDK widgets ✅ DONE 2026-06-27 —
6. Estado dos engines (2026-06-27, honesto)
- Kroma ainda não existe como código (roadmap). Por isso o contrato (S1)
vem primeiro: define o alvo pra quando o Kroma nascer.
- Kruze existe e tem o KRUZE-277 aberto — é o primeiro backend real e o
driver desta padronização.
- Vídeo já usa VA-API hoje (caminho ad-hoc); S4 só o traz pro contrato.