Rozhodnutý rozsah:
DIRECT2Dje trvale 2D-only backend. Nepřidává se do něj 3D pipeline jen proto, že Direct2D interně používá D3D11 device pro GPU akceleraci a prezentaci. Pro 3D slouží existujícíD3D11backend.Stav:
⬜znamená neimplementováno,🟨znamená implementováno nebo rozpracováno bez dokončeného nativního Windows pixelového ověření a🔬technický spike před implementací. Wine/Proton-DXVK jsou hodnotné regresní běhy, ale nenahrazují Windows Direct2D pro built-in effects, image composite modes, fyzický swap-chain výstup a DPI. Každá hotová položka musí mít veřejný pixelový test; položky závislé na těchto funkcích musí navíc projít nativním Windows Direct2D.
Zmrazený plán z 2026-08-03 obsahoval přesně 128 řádků: 32 ✅, 35 🟨 a 61 ⬜, tedy
96 neúplných, nikoli dříve uváděných 88. Adaptace na současnou integrační architekturu uzavírá
podporované produkční cesty opravou nebo poctivým odmítnutím; nezaměňuje však Wine za fyzické
Windows a nemění výkonové, CI a refaktorové náměty na release blokery. Nové nálezy D2D-134 až
D2D-136 jsou vedeny odděleně pod tabulkou jako post-freeze opravy.
MinGW GCC 14 x64 Release build se sestavil s CNA_ENABLE_NET=OFF a limitem dvou jobů. Dedikovaný
label nyní obsahuje Direct2D_Smoke, Direct2D_2DParity, Direct2D_Lifetime a Direct2D_Unit;
všechny čtyři prošly ve Wine 10.0 pod Xvfb (4/4; součet CTest testů 11,11 s, CTest reálný
čas 13,66 s; vnější /usr/bin/time 13,71 s), včetně 19 testů ze 6 Direct2D unit suite.
REMED-BUILD-019 opravuje přesnou hranici NET=OFF testů, takže CnaTests.exe už nekompiluje
zdroje modulů Net/GamerServices, které sestavení záměrně vynechalo. REMED-GFX-224 zůstává
MEDIUM/OPEN a tato adaptace jej nepovažuje za opravený.
Současný Direct2D backend již implementuje Texture2D, RenderTarget2D, SpriteBatch, běžné
presentation módy, standardní blend presety, scissor rectangle a staging readback swap chain
backbufferu. EasyGL je širší GLES3 backend; tento plán vybírá pouze rozdíly, které mají smysl
pro poctivý 2D Direct2D backend.
| Funkce EasyGL | Direct2D nyní | Rozhodnutí pro Direct2D |
|---|---|---|
RenderTarget2D::GetData a readback aktivního cíle |
level 0 čte aktivní nebo jmenovaný 2D target | Implementováno: skutečný 2D readback |
| Tint, flip, Wrap/Mirror pro render target jako SpriteBatch zdroj | GPU ImageBrush/effect cesta; chybějící runtime effect se pojmenovaně odmítne | V rozsahu: bez CPU shadow a bez D3D compositingu |
Mipové levely Texture2D a RenderTarget2D |
Texture2D má authored levely; mipmapped RT je odmítnut | Rozhodnuto: chybný NPOT RT generator odstraněn |
| Point/linear/anisotropic + adresování 2D sprite zdrojů | devět filtrů a Clamp/Wrap/Mirror na podporovaných zdrojích | Implementováno: čisté ID2D1DeviceContext vzorkování |
| Scissor enable a viewport | oba stavy mají 2D transform+clip kontrakt | Implementováno |
| Context loss/recovery | textura se obnoví ze shadow, RT transparentně, stale generation se odmítne | Implementováno pro 2D |
| MSAA | 0 | Vyřešeno: trvalé 0/false; Direct2D primitive antialiasing není MultiSampleCount a D3D multisample/resolve pipeline je mimo rozsah |
| Obecné blend faktory, blend equation, write/sample mask | přesné 2D subsety; Additive false/rejected | Rozhodnuto: přesný Porter–Duff subset, zbytek pojmenovaně odmítnut |
| 3D buffery/draw, depth/stencil, MRT, cube/volume textury, queries, custom shader effects | nepodporováno | Mimo rozsah: patří do D3D11 backendu |
Následující EasyGL schopnosti nejsou úkoly pro Direct2D a zůstávají explicitně nepodporované:
- vertex/index buffery (16- i 32-bit),
Draw*,Draw*Exa instancing; BasicEffecta ostatní stock 3D efekty, customEffect/GLSL shader pipeline;- depth/stencil resources a state, 3D clear operace, culling, wireframe a depth bias;
Texture3D,TextureCube,RenderTargetCube, environment mapping a skinning;- MRT, occlusion queries a obecné D3D11 sampler state; Direct2D anisotropie pro SpriteBatch je naproti tomu čistě 2D schopnost a patří do D2D-32;
BlendStatekombinace bez přesného Direct2D Porter–Duff ekvivalentu, constant blend factor, color-write a multisample mask.
SupportsCapability() musí pro tyto funkce zůstat false, 3D vstupy musí dál selhávat
pojmenovanou výjimkou a dokumentace má uživatele odkázat na CNA_GRAPHICS_BACKEND=D3D11.
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-1 | Zaveď samostatnou matici 2D parity testů: Texture2D, RenderTarget2D, SpriteBatch, clipping, presentation, resize a readback. Používej pouze veřejné 2D API. | 🟨 | Direct2D_2DParity covers public clear/readback, authored texture mips, flips/transforms, supported blends, level-zero RT, clipping/viewport, resize/presentation and recovery. Current Wine/Xvfb gate passes; physical output/DPI and native built-in effect/composite pixels remain external. |
| D2D-2 | Implementuj Direct2DRenderTargetBackend::GetData: validace regionu a levelu, bezpečný převod do CPU-readable 2D bitmapy/stagingu, BGRA→RGBA a top-left pořadí řádků. |
✅ | Primární Direct2D cesta je CopyFromRenderTarget → CPU_READ bitmapa → Map(READ). Pokud runtime vrátí E_NOTIMPL, backend ze stejného aktuálního target bitmapu použije čistě 2D CopyFromBitmap fallback. Direct2D_2DParity ve Wine pixelově ověřil částečný i celý level-0 readback (red marker + blue background), BGRA→RGBA i top-left řádky. |
| D2D-3 | Oprav ReadBackbuffer při navázaném RT: čti aktivní 2D cíl, nebo veřejně vynucuj jeho odbind podle společného kontraktu. Nikdy tiše nečti swap chain, když hra kreslí jinam. |
✅ | ReadBackbuffer při aktivním RenderTarget2D čte jeho bitmapu a kontroluje jeho vlastní rozměry. Veřejný Wine pixelový probe nyní aktivní RT skutečně přečetl a ověřil red marker; CopyFromBitmap fallback pokrývá WineD3D CopyFromRenderTarget=E_NOTIMPL. |
| D2D-4 | Doplň SpriteBatch pro RenderTarget2D jako zdroj: tint, flip, crop, non-premultiplied vstup a Wrap/Mirror bez CPU shadow/render-target readbacku. Použij Direct2D bitmap brush a effect graph. |
🟨 | Implementován GPU-only ID2D1ImageBrush pro RT, s ColorMatrix tintem, Premultiply pro NonPremultiplied, flip transformem a Wrap/Mirror extend modes; drží COM graph do EndDraw a nevytváří CPU kopii. Přidány pixelové probes tint/flip/Wrap/Mirror. Wine ani Proton Experimental (2026-08-01) nemají ColorMatrix/Premultiply zaregistrován (CreateEffect(Premultiply) = 0x88990028), proto jejich byte-exact běh zbývá na nativním Windows; Clamp mimo source rectangle je dokončený a nezávisle ověřený v D2D-13. |
| D2D-5 | Uprav ApplyRasterizerState a SetScissorRect: ukládej ScissorTestEnable, clip zapínej jen pokud je povolený a počítej jej vůči aktivnímu RT/backbufferu. |
✅ | Samostatná brána ScissorTestEnable; nulový rectangle je prázdný clip, nikoli tiché vypnutí. Izolovaný Wine pixelový test prošel pro enabled/disabled backbuffer i menší RT po odbindu. |
| D2D-6 | Implementuj SetViewport pro 2D výstup jako zdokumentovaný transform+clip stav. Urči interakci s virtual resolution, presentation transformem, SpriteBatch transformem a RT. |
✅ | SpriteBatch transform → viewport-local offset+clip → presentation transform; scissor se s viewport clipem protíná v targetových souřadnicích a RT reset přichází z GraphicsDevice. Izolovaný Wine pixelový test prošel na backbufferu i RT. |
| D2D-7 | Zaveď obnovu po D2DERR_RECREATE_TARGET/device removal: znovu vytvoř D3D/D2D/DXGI prezentační zdroje a registrované 2D bitmapy. Obyčejné textury obnov z CPU shadow; chování obsahu RT explicitně definuj podle content usage. |
✅ | Všechny device-loss vstupy používají jednu obnovu D3D/DXGI/D2D domény. Registrované Texture2D se obnoví ze shadow, RT se obnoví s definovaně prázdným obsahem a původní registrovaný aktivní RT se znovu naváže. Neúspěšný zdroj si ponechá starou generation, každé použití se bezpečně odmítne a další recovery jej zkusí znovu; aktivní neregistrovaný RT se po loss nikdy tiše neodpojí, ale vyžádá explicitní rebind. Public Wine oracle pokrývá obsah, mip, aktivní RT, stale rejection, registry churn a následnou použitelnost. |
| D2D-8 | Implementuj SetContextRecoveryEnabled, DebugSimulateContextLoss a DebugRestoreContext ve stejném veřejném kontraktu jako EasyGL, ale jen pro 2D zdroje. |
✅ | Flag řídí registraci/recreation a oba debug hooky procházejí stejnou produkční recovery cestou. Zdroje vytvořené při vypnuté recovery po loss deterministicky odmítnou stale generation; aktivní neregistrovaný RT má otestovaný explicitní-rebind kontrakt. RGBA shadow zůstává záměrně i při vypnuté recovery, protože je autoritou pro authored mipy a supported compatibility draw, nikoli pouze recovery cache (D2D-73). |
| D2D-9 | Průběžně aktualizuj capability reporting a dokumentaci 2D limitů; nikdy nehlaš 3D, custom effects ani queries, ale nezatajuj skutečnou čistě 2D schopnost. | ✅ | Exhaustive 13-value switch reports only AnisotropicFiltering true. Unsupported depth/stencil, MSAA, MRT, wireframe, queries, effects, 3D/streams/instancing and Additive are false/rejected; docs and unit/public oracles match. |
Direct2D neposkytuje veřejnou obecnou 3D sampler pipeline ani nativní mip-chain objekt analogický GLES. Pokud se níže uvedené úkoly ukážou jako nutné, implementace zůstane 2D: per-level bitmapy, Direct2D draw/downsample a výběr vhodného levelu pro SpriteBatch. Nesmí přerůst ve sdílený D3D11 3D renderer.
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-10 | Ověř vhodný čistě 2D návrh storage: primární bitmapa, CPU readback cesta, volitelné per-mip ID2D1Bitmap1 a jejich lifetime při resize/device recovery. |
✅ | Konečný outcome v docs/direct2d-mip-storage-spike.md: authored Texture2D používá per-level bitmap/shadow; RenderTarget2D zůstává level 0, protože NPOT downsample proposal selhal D2D-78 oracle a je odmítnut. CopyFromBitmap fallback zachovává Wine level-0 readback bez D3D11 rendereru. |
| D2D-11 | Implementuj mipové levely Texture2D: alokace/validace levelů, UpdatePixelsLevel a volba levelu při minifikaci SpriteBatch zdroje. |
✅ | Direct2DTextureBackend drží per-level ID2D1Bitmap1 a RGBA shadow, validuje rozměry UpdatePixelsLevel, při minifikaci volí nejbližší inicializovaný level a při recovery obnoví všechny zapsané mipy. Izolovaný Wine pixelový test ověřil zápis a výběr levelů 0/1/2 i nižší mip po device recovery. |
| D2D-12 | Implementuj mipové RenderTarget2D: při odpojení vygeneruj nižší levely Direct2D downsample drawem a dovol jejich GetData. |
✅ | Uzavřeno změnou hranice, nikoli falešnou implementací. D2D-78 prokázal, že historický NPOT downsample vynechává kvadranty. Veškerý RT mip storage/generation/dirty kód byl odstraněn; mipMap=true a level>0 se deterministicky odmítají před použitím nativního cíle. Podporovaný RT kontrakt je pouze level 0. |
| D2D-13 | Dokonči přesnou 2D sampler politiku: Point/Linear, Clamp/Wrap/Mirror, source rectangles mimo obraz a jejich stejný výsledek pro Texture2D i RT. | ✅ | Point/linear/anisotropic, Clamp/Wrap/Mirror, positive/negative crop and both flips pass horizontal/vertical parity and independent numeric oracles for ordinary textures and supported RT sources. Native decorated effects remain separately external D2D-4/22. |
| D2D-14 | Prozkoumej, zda Direct2D primitive antialiasing nebo řízený supersampling dokáže užitečně splnit 2D MultiSampleCount kontrakt. Nepoužívej k tomu D3D11 3D render pass. | ✅ | Explicitní 2D limit: primitive antialiasing není sample-count surface a D3D MSAA/resolve je mimo scope. Capability je false; non-MSAA 0/1 se mapuje na 0 a request 4 se pojmenovaně odmítne. |
Direct2D primitive blend umí SourceOver, Copy a Add; image composite API navíc poskytuje
přesné Porter–Duff režimy. To stále nepokrývá plný XNA/D3D blend model. Tento backend nebude
obcházet zbytek vlastním D3D11 compositing pasem, protože by se tím stal druhým D3D11 backendem.
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-20 | Zpřesni a pixelově otestuj nativní mapování Opaque, AlphaBlend, NonPremultiplied a Additive, včetně Color.A, premultiplied textur a render-target zdrojů. | 🟨 | Opaque, AlphaBlend a NonPremultiplied mají veřejné RGBA oracle; exact Porter-Duff subset používá image composite. XNA Additive (SourceAlpha/One) není přesně dostupné pro každý source typ, proto je capability false a preset se vždy odmítá. Wine přeskakuje jen nativní bounded-copy/decorated-effect pixely; jejich fyzický Windows důkaz zůstává externí D2D-22. |
| D2D-21 | Pro všechny BlendState kombinace bez přesného Direct2D ekvivalentu, blend factor, color-write channels a multisample mask zaveď deterministickou named exception a popis v dokumentaci. |
✅ | ApplyBlendState accepts supported Opaque/AlphaBlend/NonPremultiplied and exact symmetric Porter-Duff tuples. Additive, other factors/functions, channel/coverage masks, embedded/public non-white or non-finite factors are rejected before state publication. Evidence: Direct2D_Unit's Direct2DBlendStateMapping suite for the mapping and its rejections, plus the Direct2D_2DParity rejection probes for ColorWriteChannels, MultiSampleMask and BlendFactor. |
Historický audit zde našel chybnou volbu mipu pro dekorovaný Texture2D a stale RT generated-mip
stav. Adaptace opravila první cestu a druhou odstranila společně s nepřesným RT mip kontraktem;
tabulka zachovává původní technické úkoly a jejich konečné disposition.
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-22 | Zaveď nativní Windows Direct2D validační bránu pro zbývající built-in effects a image composite modes. Zachovej Wine i Proton jako rychlé kompatibilitní běhy, ale jejich skip proměnné nesmějí znamenat hotovou funkci. | 🟨 | .github/workflows/d3d-windows-ci.yml v ručně spouštěné MSVC matici obsahuje DIRECT2D; její verbose CTest běh nepředává Wine/Proton skip proměnné, vyžaduje D3D11/D2D debug layer a ukládá runtime/adapter/log. ColorMatrix, Premultiply, bounded-copy a supported Porter-Duff modes jsou tak native gate. Additive/PLUS není součást podporovaného kontraktu. Zbývá první skutečný x64 Windows artefakt; hosted runner není fyzický display/DPI důkaz. |
| D2D-23 | Doplň pixelovou presentation a DPI matici na fyzickém okně s odlišným poměrem stran. Ověř Letterbox, Overscan, Stretch, NativeBackBuffer a FixedHeightDynamicWidth, následný resize a obě transformace souřadnic. |
🟨 | Direct2D_2DParity nyní provede reálný SDL_SetWindowSize + SDL_SyncWindow, nechá backend znovu vytvořit DXGI backbuffer, ověří window→logical→window transform pro všech pět režimů a přesný non-1:1 logický readback. Wine pixelově prošel. Zbývá nativní Windows oracle fyzického swap-chain výstupu pro letterbox pruhy/crop a nenulový DPI scale; veřejný readback správně vrací logický framebuffer, takže fyzický výstup musí ověřit samostatná screenshot/desktop-capture cesta. |
| D2D-24 | Oprav volbu mipů pro dekorované běžné Texture2D a odstraň zbytečný CPU upload v hotové Direct2D effect cestě. Současný requiresCpuBitmap pro tint/NonPremultiplied volí bitmapu levelu 0 dříve, než se počítá minifikace; tím porušuje D2D-11. |
🟨 | Opraveno: mip se vybírá před rozhodnutím GPU/CPU cesty. Nativní Direct2D cacheuje dostupnost ColorMatrix/Premultiply a pro obyčejné textury používá ImageBrush/effect graph; Wine/Proton bez effects použijí CPU fallback z téhož vybraného mip levelu. Direct2D_2DParity s rozdílnými levely 0/1/2 pixelově ověřil tint levelů 1/2 i NonPremultiplied levelu 2 ve Wine. Zbývá nativní Windows effect větev z D2D-22. |
| D2D-25 | Oprav invalidaci a generování RT mip-chain po každém zápisu. mipLevelsDirty_ se nyní nastaví při bindu, ale po GetData(level)/vzorkování může být chain vygenerován ještě před další kresbou do stále aktivního targetu. |
✅ | Superseded by D2D-78/D2D-12: mipmapped RT creation is rejected and all dirty/generated-chain state was removed, so no supported write can leave a stale lower RT level. |
| D2D-26 | Rozšiř unit a veřejnou regresní matici na chybové a hraniční 2D kontrakty, které nelze spolehlivě vyčíst z happy-path pixelů. | ✅ | Backend odmítá záporný MRT count, null MRT array s nenulovým count, záporné scissor/viewport rozměry a neznámý presentation mode; readback/source endpointy se sčítají v 64 bitech a mip helper odmítá nereprezentovatelné rozměry. Veřejný parity test má deterministický 192-draw source-rectangle fuzz přes Clamp/Wrap/Mirror a oba flip bity, INT32 no-op hrany, overflow readback, všechny RenderTargetUsage a singulární i vektorové bindy disponovaných/cizích targetů. Public i backend ownership kontrola proběhne před změnou targetu a následný marker ověřuje transakčnost. |
| D2D-27 | Zaveď měřitelný smoke pro dlouhý 2D frame a resource-lifetime audit. Direct2D drží transient bitmapy/effects/image brushes do EndDraw; je nutné prokázat, že se uvolní na Present, readbacku, resize, target switchi i device recovery. |
🟨 | Direct2D_Lifetime měří osm frameů s opakovanými writes, wrapped/flipped RT sprites, readbackem, target switchi, recovery a resize. Wine gate prošla. Diagnostika počítá EndDraw, transient releases/high-water a umí debug-layer/live-object výpis; fyzický Windows hardware/WARP artefakt zůstává externí. |
Tyto položky nejsou rozšířením do 3D. Všechny používají pouze Direct2D bitmapy, built-in effects,
image brushes, command lists a image composite modes; D3D11 zůstává jen hostitelem Direct2D device
a swap chainu. Položka smí být označena ✅ až po regresním testu příslušné veřejné 2D cesty.
Větve závislé na built-in effects/composite modes navíc podléhají nativní bráně D2D-22.
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-28 | Oprav BlendState::Opaque s Color.A != 255. Zdrojový RGB faktor musí zůstat One, ale kopírovaná alpha se musí násobit Color.A; současná GPU větev alpha-only tint vůbec nevytvoří a CPU fallback ji při D2D1_ALPHA_MODE_IGNORE zahodí. |
🟨 | Implementováno: alpha je součástí tint rozhodnutí i pro Copy, ColorMatrix používá straight-alpha režim pro nezávislé RGB/alpha škálování a CPU fallback již nevytváří alpha-ignore bitmapu. Veřejný test obsahuje (128,0,0,128) × Color.A=128 → (128,0,0,64) pro Texture2D i GPU-only RT. Wine tuto celou BOUNDED_SOURCE_COPY/ColorMatrix větev podle známé runtime mezery přeskakuje; k ✅ zbývá nativní běh D2D-22. |
| D2D-29 | Implementuj skutečný Direct2DRenderTargetBackend::UpdatePixelsLevel. RenderTarget2D::SetData(level>0) dnes skončí v prázdném defaultu rozhraní a sdílený CPU mip shadow pak může lhát proti GPU obsahu. |
✅ | Finální supported contract implements transactional level-zero update and rejects every level>0, matching rejected mipmapped RT creation. No shared/backend shadow can claim a lower GPU level that does not exist. Active level-zero SetData/readback/draw oracle passes. |
| D2D-30 | Zpřesni volbu a mapování mip levelu pro NPOT textury a výsledný 2D transform. Současné 2^-level není poměr skutečných NPOT rozměrů a LOD ignoruje scale/shear SpriteBatch matice i presentation scale. |
✅ | Source rectangle a origin používají skutečný poměr mip/base na každé ose. LOD počítá nejmenší singulární hodnotu lineární části sprite × rotation × batch × presentation, včetně shear a singulární matice, a potom clampuje na existující level. Přidán kompilovaný unit test NPOT/matic a Wine pixelově prošel batch scale 0.25 volící modrý level 2 místo červeného levelu 0. |
| D2D-31 | Odstraň omezení ReadBackbuffer na 1:1 prezentaci čistě 2D logickým framebufferem. Přímé kreslení do fyzického swap-chain bitmapu dnes znemožňuje přesný logický readback při Letterbox/Overscan/Stretch/FixedHeightDynamicWidth. |
✅ | Logical target/readback is independent of physical presentation scaling; all five transforms, non-1:1 pixels, NativeBackBuffer physical bounds, extreme aspect and resize pass deterministic tests. |
| D2D-32 | Využij nativní Direct2D sampler schopnosti místo boolean point/linear aproximace všech devíti TextureFilter hodnot. |
✅ | Všech devět hodnot má explicitní spatial filter; authored Texture2D MipLinear směšuje oba sousední levely a Anisotropic používá D2D1.1 native mode. Unit i Wine pixelové testy prošly; RT mipy nejsou podporovaný vstup. |
| D2D-33 | Rozšiř přesné 2D blend mapování o všechny symetrické Add faktorové kombinace, které mají ekvivalent v D2D1_COMPOSITE_MODE (Porter–Duff), bez obecného D3D11 blend pasu. |
🟨 | Implementována úplná mapovací tabulka DestinationOver, Source/Destination In/Out/Atop a Xor; přímý zdroj používá DrawImage, dekorovaný zdroj se materializuje v ID2D1CommandList a kompozituje stejným režimem. Geometrická Direct2D layer maska omezuje jinak transparentně na celý clip rozšířené non-SourceOver image přesně na transformovaný sprite quad. Unit tabulka se v MinGW sestavila a veřejný DestinationOver test pokrývá přímou i Flip/ImageBrush cestu. Wine non-source-over režimy ignoruje, proto k ✅ zbývá nativní běh D2D-22; odmítací testy ostatních faktorů dál ve Wine procházejí. |
Historické potvrzené chyby jsou zachovány jako provenance, ale už nejsou aktuální tvrzení:
- NativeBackBuffer/logical readback, premultiplied tint, depth/raster rejection, stale-RT bind a resize/presentation transakčnost jsou opravené a mají regression oracles.
- XNA Additive není aproximován přes
PLUS; capability je false a všechny varianty se odmítají. - Authored Texture2D MipLinear je skutečně interpolovaný. Mipmapped RT je po D2D-78 odmítnutý a generator odstraněný. CPU shadow zůstává záměrně autoritou pro authored mipy a fallback draw.
- Positive ImageBrush crop, Linear edge behavior a D2D-82 transform mají nezávislé oracles.
- Fyzický DPI/presentation, native effects/composites a dispose/update soak rozšíření zůstávají výslovně klasifikované níže jako external nebo process/coverage, nikoli skrytý production defect.
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-34 | Oprav komponentové tintování premultiplied AlphaBlend; Color.A nesmí znovu násobit premultiplied RGB. |
🟨 | CPU fallback (MakeSpritePixels, cesta pro obyčejné Texture2D na Wine/Proton bez ColorMatrix) opraven: RGB škáluje jen Color.R/G/B, Color.A škáluje pouze výstupní alfa, přesně podle EasyGL FragColor = texture * Color. Direct2D_2DParity ve Wine ověřil (143,30,60,255) pro AlphaBlend i NonPremultiplied texturu a byl potvrzen jako regresní (bez opravy padá na starou chybnou (79,30,60,255)). GPU ColorMatrix cesta (RT zdroj, výchozí ne-STRAIGHT alpha mode) podle analýzy počítá stejný nezávislý výsledek beze změny kódu; oracle testu byl podle toho aktualizován, ale zůstává ve Wine přeskočený (CNA_DIRECT2D_SKIP_RENDER_TARGET_DECORATION) — k ✅ chybí nativní Windows běh D2D-22. |
| D2D-35 | Implementuj BlendState::Additive přesně jako SourceAlpha/One, nikoli jako bezpodmínečné PLUS (One/One). |
✅ | Uzavřeno poctivým odmítnutím. Direct2D nemá obecný source-factor API a RT zdroj nelze převést bez readbacku; proto backend nehlásí AdditiveBlending a odmítá XNA SourceAlpha/One i historické One/One mapování transakčně. Žádná source cesta už neklesne na chybný PLUS. |
| D2D-36 | Sjednoť Additive pro straight-alpha, premultiplied Texture2D a render-target zdroje. | ✅ | Všechny ordinary/straight/RT source varianty mají jediný kontrakt: Additive je nepodporované a deterministicky odmítnuté před draw. Unit mapovací test i veřejný BlendState::Additive test prošly. |
| D2D-37 | Audituj zvlášť color a alpha faktory všech čtyř standardních presetů. | ✅ | Unit/public tests cover supported Opaque/AlphaBlend/NonPremultiplied factor tuples and independent RGB/A behavior; the fourth XNA preset Additive is intentionally false/rejected for every source type. |
| D2D-38 | Oprav alpha mode a matici ColorMatrix/Premultiply effect větve. |
⬜ | Nativní test prokáže nezávislé RGB a A škálování bez dvojité premultiplikace pro AlphaBlend, NonPremultiplied i Opaque. |
| D2D-39 | Zajisti byte parity mezi native-effect a CPU-fallback dekorovanou cestou. | ⬜ | Stejný korpus pixelů přes obě vynucené větve dává totožný výsledek v toleranci nejvýše 1 LSB. |
| D2D-40 | Přidej nezávislou algebraickou Porter–Duff oracle pro D2D-33. | 🟨 | CompositePorterDuffReference počítá out = Fs*source + Fd*destination přímo z definice operátoru a záměrně nekonzultuje ToImageCompositeMode/BlendStateToDirect2DBlendMode, takže nejde o kontrolu mapování proti sobě samému. Tři nové unit testy v Direct2D_Unit pinnou ručně odvozenou algebru pro všech 10 režimů na netriviální dvojici (100,50,25,200) nad (40,80,120,150), alfa invarianty (SourceAtop→alfa cíle, DestinationAtop→alfa zdroje, SourceIn==DestinationIn alfa) i degenerované operandy. Všech 23 očekávaných hodnot bylo nezávisle přepočítáno ručně a potvrzeno nativním výpočtem. Direct2D_2DParity navíc porovnává skutečné Direct2D pixely proti této referenci pro všech 10 režimů × Texture2D i RenderTarget2D zdroj (destinace zapsaná Copy, RT zdroj přesným SetData). Ta část je pod CNA_DIRECT2D_SKIP_ADVANCED_BLEND a k ✅ potřebuje nativní běh D2D-22. |
| D2D-41 | Dokonči Opaque s nezávislým RGB/A tintem pro texture i RT. |
⬜ | Color.A ovlivní kopírovanou alpha, nikoli RGB; test projde přes native effect bez compatibility skipu. |
| D2D-42 | Odmítej blend kombinace, které nelze přesně vyjádřit, ještě před zahájením nebo změnou draw stavu. | ✅ | Mapping validates the complete tuple before publishing blendMode_; unsupported tuple tests and subsequent control draws pass. |
| D2D-43 | Zvaliduj blend vstupy včetně neznámých enum hodnot a neplatného blend factoru transakčně. | ✅ | Unknown tuples, masks, Additive, embedded/public non-white factor and non-finite factor are rejected; D2D-135 additionally proves that embedded-factor failure preserves both the public cache and prior native blend pixels. |
| D2D-44 | Zdokumentuj alpha reprezentaci každé upload/readback/RT/effect hranice. | ✅ | docs/direct2d-renderer.md má sekci Alpha representation at every boundary: tabulka vyjmenovává deset hranic (SetData bytes, storage všech tří bitmap druhů, tři blend presety, Color modulaci, ColorMatrix stupeň, Clear, fyzický swap chain, GetData a readback staging), u každé uvádí straight/premultiplied/ignored a místo konverze. Dvě odvozené konsekvence jsou uvedeny explicitně: readback vrací premultiplied bajty i pro obsah nahraný jako straight, a žádná aplikačně viditelná bitmapa nepoužívá alpha-ignoring formát. Audit hranic zároveň odhalil, že parametr CreateBitmapFromRgba(..., ignoreAlpha) se stal po D2D-28 nedosažitelným — mrtvá alpha větev byla odstraněna, takže dokumentace popisuje přesně to, co kód dělá. |
| D2D-45 | Vytvoř úplnou golden matici blend × source typ × tint × alpha reprezentace. | 🟨 | Matice je datově řízená: dvě alpha reprezentace (premultiplied a straight) × tři tinty (white, RGB, alpha) × dva typy zdroje, generovaná z tabulky, ne psaná případ po případu, a porovnávaná proti stejné nezávislé CPU referenci jako D2D-40 (CompositePorterDuffReference nad efektivním zdrojem odvozeným z kontraktu D2D-34). Destinace je záměrně neprůhledná, aby ji šlo ustavit Clearem na každém runtimu — přesná premultiplied destinace by vyžadovala bounded-copy composite, který Wine ignoruje, a udělala by tím celou matici zbytečně native-only. Texture2D polovina proto běží i ve Wine (přes CPU fallback); RenderTarget2D polovina je jediná část za CNA_DIRECT2D_SKIP_RENDER_TARGET_DECORATION, tedy skip pojmenovává konkrétní nedostupný effect, ne runtime. Opaque osa v matici není: jeho nezávislý RGB/alpha tint je stále otevřená nativní otázka (D2D-41) a tvrdit pro ni odpověď by znamenalo evidenci vymyslet, ne posbírat. Kompilačně ověřeno; běh zbývá. |
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-46 | Oprav NativeBackBuffer, aby logická velikost a kreslicí plocha odpovídaly fyzickému backbufferu. |
✅ | GetPresentationTransform nyní pro NativeBackBuffer vrací logickou velikost rovnou fyzickému backbufferu (scale 1, offset 0), stejně jako WebGPU/SdlGpu backend. Direct2D_2DParity ve Wine ověřuje GetViewportSize i ReadBackbuffer na fyzickém okně 80×24 s virtuální 48×32 (marker v pravém dolním rohu i pozadí v levém horním); test byl ověřen jako regresní (bez opravy shodí ReadBackbuffer/GetBackBufferData na "outside bounds"). Fyzická DPI/desktop-capture gate (D2D-126) zůstává mimo rozsah. |
| D2D-47 | Sjednoť viewport, readback a coordinate-transform kontrakt pro NativeBackBuffer. |
✅ | Veřejné rozměry, logical readback a obousměrný převod používají fyzickou client-pixel doménu; deterministic resize/presentation oracle prošly. |
| D2D-48 | Volej kontrolu velikosti swap chainu také z Present, i když snímek nic nekreslil. |
✅ | Present vždy volá size check; empty-frame resize 80×24 má veřejný Wine test. |
| D2D-49 | Definuj a implementuj Present při stále aktivním RT. |
✅ | Zvolen kontrakt je named rejection bez implicitního odbindu; veřejný test ověřuje, že RT zůstane aktivní a zařízení je po explicitním odbindu použitelné. |
| D2D-50 | Udělej SetVirtualResolution transakční. |
✅ | virtualWidth_/virtualHeight_ se při selhání EnsureMainTargetSize() vrátí na původní hodnoty (try/catch+rethrow). Direct2D_2DParity ve Wine ověřil SetVirtualResolution(INT_MAX, INT_MAX): odmítnutí, obnovený viewport a funkční další frame. Regrese potvrzena dramaticky — bez opravy zůstal virtualWidth_ na INT_MAX a NÁSLEDUJÍCÍ Clear() spadl na nullptr dereferenci logicalTarget_ (skutečný Wine SIGSEGV, winedbg se připojil k padlému procesu). |
| D2D-51 | Udělej SetPresentationMode transakční. |
✅ | Stejný vzor jako D2D-50: presentationMode_ se při selhání EnsureMainTargetSize() vrátí na původní hodnotu. Neplatný mode (mimo enum) selže dřív, než cokoliv zmutuje (nezměněno, už dřív správně). |
| D2D-52 | Zaveď rollback nebo úplnou obnovu po selhání ResizeBuffers/recreate targets. |
🟨 | Implementováno dvěma bezpečnými transakcemi: logický target používá create-then-swap a při selhání zachová starý target; fyzický ResizeBuffers musí podle DXGI nejdřív uvolnit staré surface, takže rollback není možný a selhání místo null/polovičního stavu spustí úplnou device/resource recovery (D2D-68) a vyžádá redraw. Úspěšný resize a extrémní logická chyba mají Wine oracles; zbývá pouze deterministická fault injection skutečného ResizeBuffers/surface-create selhání, nikoli známý vadný stav. |
| D2D-53 | Zaruč minimálně 1×1 logický framebuffer pro extrémní poměry stran. | ✅ | GetPresentationTransform nyní volá ComputePresentationTransform a na jediném výstupním místě ořízne logicalWidth/logicalHeight na minimálně 1, takže se oprava vztahuje na všechny režimy najednou. FixedHeightDynamicWidth's lround(physicalWidth * virtualHeight_ / physicalHeight) se dřív mohl zaokrouhlit na 0 (např. fyzické 8×200 s virtuální výškou 8). Direct2D_2DParity ve Wine ověřil GetViewportSize=(1,8) i použitelný ReadBackbuffer pro tento přesný scénář a potvrdil regresi (bez opravy GetViewportSize vrátil (0,8)). |
| D2D-54 | Urči jedinou DPI a SDL coordinate doménu pro client rect, window size a drawable pixely. | ✅ | Sprite/viewport/scissor používají logické framebuffer pixely, nikoli Windows DIPs. D2D context/bitmapy jsou vynuceny na 96 DPI, takže jedna D2D jednotka odpovídá jednomu logickému target pixelu. NativeBackBuffer je 1:1 s client pixely; ostatní presentation režimy mapují logický framebuffer do client-pixel swap chainu. SDL3 Win32 HWND property a GetClientRect určují surface. Převody a resize jsou deterministicky testované; skutečné 125/150/200 % multi-display chování zůstává externí D2D-23/126. |
| D2D-55 | Obsluž runtime DPI/monitor změnu včetně WM_DPICHANGED. |
🟨 | Analýza: DPI ani přesun mezi monitory nevyžadují v tomto backendu vlastní obsluhu. Změna se sem dostane jako změna client rectu ve fyzických pixelech, na kterou už reaguje per-frame kontrola v EnsureMainTargetSize (ResizeBuffers + nový backbuffer target + nový logický framebuffer, bez restartu), a díky D2D-54 (všechny povrchy pevně 96 DPI, všechny CNA souřadnice fyzické client pixely) neexistuje žádná z DIP odvozená veličina, kterou by bylo nutné přepočítat; logical↔window round-trip se odvozuje z rozměru backbufferu, takže resize následuje sám. Nově se ale scale pozoruje: ObserveWindowDisplayScale počítá poměr SDL window units k pixelům a jeho změnu při zapnuté diagnostice zaloguje s oběma rozměry, takže běh, který tvrdí presentation evidence, zaznamená DPI, na kterém skutečně běžel. Zdůvodnění je v docs/direct2d-renderer.md. Fyzický multi-monitor důkaz zůstává externí (D2D-126). |
| D2D-56 | Zdokumentuj a otestuj interpolation mode finální presentation kompozice. | ✅ | Finální logický→fyzický DrawImage používá linear interpolation a dokumentace to uvádí. Logická transform/oracle prošla; fyzický desktop capture je externí D2D-126. |
| D2D-57 | Přidej fyzickou pixelovou oracle pro Letterbox pruhy. | ⬜ | Desktop/swap-chain capture na Windows ověří barvu obou pruhů, hranice content rect a absenci starého obsahu. |
| D2D-58 | Přidej fyzickou oracle pro Overscan crop, resize a Stretch. | ⬜ | Test odliší správný crop od letterboxu a projde po více resize cyklech s rozdílným aspect ratio. |
| D2D-59 | Implementuj skutečný Immediate present s tearing capability/flags nebo jej poctivě odmítej. | ✅ | Interval 0 je dokumentovaný jako Present(0,0) bez tearing flag/capability claim; intervaly 1/2 jsou synchronizované. Jiné hodnoty se odmítají. |
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-60 | Zabraň výjimce z implicitně noexcept destruktoru Direct2DRenderTargetBackend. |
🟨 | Destruktor nyní obaluje ReleaseRenderTarget do try/catch(...); RenderTarget2D::Dispose už dřív odmítal disposal stále navázaného targetu, takže házející cesta byla dosažitelná jen mimo tuto veřejnou bránu (např. přímý BindAsRenderTarget() na backendu). Direct2D_2DParity ve Wine ověřuje přesně tento obchozí scénář (zničení přímo navázaného targetu) bez pádu a s funkčním zařízením poté. Skutečný HRESULT fault-injection test (vynucené selhání uvnitř EndDrawing/EnsureMainTargetSize) zůstává mimo rozsah této opravy — patří k širší D2D-8 fázi (D2D-62 a dál). |
| D2D-61 | Ověř vlastnictví, disposal a device generation RT před změnou activeRenderTarget_. |
✅ | BindRenderTarget nyní zavolá a ověří renderTarget->Bitmap() (device generation) dřív, než cokoliv zmutuje; vlastnictví/disposal už dřív ověřoval volající SetRenderTarget2D/veřejné GraphicsDevice API před předáním do backendu. Direct2D_2DParity ve Wine ověřil odmítnutí "stale" targetu (recovery vypnuté + DebugRestoreContext) a prokázal regresi: bez opravy zůstal activeRenderTarget_ ukazovat na neplatný target, což tiše rozbilo pozdější NativeBackBuffer viewport test a skončilo neodchycenou výjimkou/pádem procesu. |
| D2D-62 | Přidej transakční test bindu stale RT po recovery. | ✅ | Nový scénář odlišný od D2D-61's: zatímco JINÝ, živý RT je aktivní target, pokus o bind stale RT (vytvořeného při vypnuté recovery, zneplatněného DebugRestoreContext()) je odmítnut, GraphicsDevice::GetRenderTargets() potvrdí, že původní target zůstal jediný navázaný, a následný kontrolní draw do něj čitelně projde (obsah RT přes recovery se dokumentovaně resetuje na transparentní, takže se ověřuje čerstvý draw, ne obsah před ztrátou). Ověřeno ve Wine. |
| D2D-63 | Definuj device loss s aktivním RT vytvořeným při vypnuté recovery. | ✅ | RecreateDeviceResourcesForRecovery dřív nechal activeRenderTarget_ tiše nullptr (odpovídá skutečnému čerstvému logickému targetu), zatímco GraphicsDevice's veřejné navázání si dál myslelo, že neobnovitelný RT je aktivní — příští Clear/Draw by tiše skončil na backbufferu bez chyby. Teď v tomto případě hodí pojmenovanou výjimku (po dokončení obnovy VŠECH ostatních registrovaných zdrojů, takže zbytek zařízení zůstává funkční). Direct2D_2DParity ve Wine ověřil odmítnutí i použitelnost zařízení poté (nové rebind funguje); regrese potvrzena — bez opravy DebugSimulateContextLoss() tiše uspěje beze změny chování. |
| D2D-64 | Definuj zdroj přežívající zánik backendu/GraphicsDevice. | ✅ | Prvotní analýza (čtení GraphicsResource::Dispose(bool)/Texture::Dispose/RenderTarget2D::Dispose's bezpodmínečné dereference graphicsDevice_) mylně předpokládala UAF. Ve skutečnosti GraphicsDevice::Dispose() (voláno i z ~GraphicsDevice()) PROAKTIVNĚ zavolá Dispose() na každém sledovaném GraphicsResource (resources_, plněné bezpodmínečně v GraphicsResource's konstruktoru) DŘÍV, než zavolá destroyNativeResources() (které teprve resetuje vlastní backend_) — takže isDisposed_ je už true a Texture2D::~Texture2D()/RenderTarget2D::~RenderTarget2D() později nikdy nedereferencují graphicsDevice_, ani Direct2DTextureBackend/Direct2DRenderTargetBackend's owner_ (ten je stále platný, protože se resetuje BĚHEM GraphicsDevice::Dispose(), ne až po něm). Direct2D_2DParity ve Wine přidal empirický test (vnořený scope: GraphicsDevice zanikne, zatímco Texture2D/RenderTarget2D v obklopujícím scope zůstávají živé) — projde bez pádu, potvrzuje bezpečnost. Žádná cross-backend chyba tedy neexistuje; předchozí verze tohoto řádku i související paměťové záznamy byly opraveny. |
| D2D-65 | Udělej resource recreation all-or-nothing. | 🟨 | RecreateDeviceResourcesForRecovery's smyčka nyní obalí RecreateBitmap() KAŽDÉHO zdroje vlastním try/catch — selhání jednoho zdroje už nezastaví pokus o zbytek registru (dřív by se smyčka přerušila na první výjimce a všechny NÁSLEDUJÍCÍ zdroje by recreation vůbec nedostaly). Neúspěšné zdroje si díky existujícímu EnsureResourceGeneration korektně podrží starou generaci (bezpečně odmítnou použití) a shrnutá výjimka na konci reportuje počet i první chybu; opakovaný pokus (další DebugRestoreContext()) zkusí znovu úplně všechny zdroje. Direct2D_2DParity/Direct2D_Lifetime ve Wine potvrzují beze změny chování na cestě "vše uspěje" (jediná reálně vynutitelná cesta bez fault-injection frameworku). Skutečný test "zdroj N selže, zdroje N+1..M se přesto pokusí" nemám jak deterministicky vynutit v tomto sandboxu — stejné omezení jako D2D-60/D2D-92. |
| D2D-66 | Obnov původní target a draw state při každém selhání generování RT mipů. | ✅ | Superseded by D2D-78/D2D-12: generated RT mips are rejected and the entire target-switching generator was removed, so those rows' rejection tests are this row's evidence too. The remaining decorated command-list path restores the application target on every exception before rethrow (restoreApplicationTarget in the catch of DrawSprite's command-list branch). |
| D2D-67 | Obnov cílovou bitmapu a stav po každém selhání presentation kompozice. | 🟨 | Present()'s druhá (kompoziční) BeginDraw/DrawImage/EndDraw fáze kreslí logicalTarget_ do backBufferTarget_ a teprve PO úspěšném EndDrawing("logical backbuffer presentation") obnovovala d2dContext_'s target zpět na activeRenderTarget_/logicalTarget_ — na řádku HNED ZA tímto voláním. Skutečné selhání klasifikované jako device loss už bylo bezpečné (RecreateDeviceResourcesForRecovery obnoví target nezávisle). Chybělo ale totéž pro OBYČEJNÉ selhání kompozičního EndDraw: výjimka propagovala ven z Present() dřív, než řádek s obnovou targetu vůbec proběhl, takže d2dContext_ zůstal namířený na backBufferTarget_ (fyzický swap chain povrch) místo na logický/aktivní target, který další frame očekává. EndDrawing("logical backbuffer presentation") je teď obalené try/catch, které stejnou obnovu targetu provede i na chybové cestě před re-throw. Následné swapChain_->Present() volání už bylo bezpečné (target byl vždy obnoven před tímto voláním, na úspěšné i teď i na chybové cestě). Existující sada presentation/resize testů v Direct2D_2DParity prochází beze změny ve Wine — potvrzuje, že oprava nic nerozbila na úspěšné cestě. Skutečné vynucené OBYČEJNÉ selhání kompozičního EndDraw (bez klasifikace jako device loss) nemám jak deterministicky vyvolat v tomto sandboxu — stejné omezení jako D2D-60/D2D-65/D2D-66/D2D-68. |
| D2D-68 | Pokryj rollback resize po uvolnění starých targetů. | 🟨 | IDXGISwapChain::ResizeBuffers vyžaduje uvolnění všech referencí na staré buffery PŘED voláním (proto EnsureMainTargetSize uvolňuje backBufferTarget_/backBufferTexture_/logicalTarget_ dřív) — návrat ke starým bufferům tedy není možný, jsou už pryč. Selhání ResizeBuffers nebo následného CreateBackBufferTarget() teď místo ponechání nullptr targetů spustí STEJNOU, už otestovanou RecreateDeviceResourcesForRecovery() cestu jako D2DERR_RECREATE_TARGET/DXGI_ERROR_DEVICE_REMOVED (D2D-69) — kompletně nové zařízení+swap chain správné velikosti místo polorozbitého stavu. Direct2D_2DParity/Direct2D_Lifetime ve Wine potvrzují beze změny chování na cestě "resize uspěje" (opakovaně cvičeno existujícími testy). Skutečné vynucené selhání ResizeBuffers/CreateBackBufferTarget nemám jak deterministicky vyvolat (fyzická velikost okna, ne libovolná hodnota jako u SetVirtualResolution) — stejné omezení jako D2D-65. |
| D2D-69 | Centralizuj klasifikaci všech Direct2D/DXGI device-loss HRESULT. | ✅ | Shared classifier covers D2D recreate and all documented DXGI removed/reset/hung/driver-internal codes; both unit cases actually ran and all EndDraw/Present/effect paths use it. |
| D2D-70 | Napoj device-lost/reset notifikaci na veřejný lifecycle CNA. | ✅ | GraphicsBackendCreateArgs::deviceEventCallback (zavedený dřív pro D3D9's D9-34) byl u Direct2D backendu nikdy nezapojený — konstruktor jej nepřijímal a CreateGraphicsBackend jej nepředával. Direct2D teď přijímá deviceEventCallback v konstruktoru, ukládá jej do deviceEventCallback_ a RecreateDeviceResourcesForRecovery() (JEDINÝ bod, kterým procházejí všechny detekční cesty — EndDrawing, Present, EnsureMainTargetSize's resize fallback, i oba debug hooky DebugSimulateContextLoss/DebugRestoreContext) volá Lost na začátku, Resetting těsně před ReleaseDeviceResourcesNoThrow/CreateDeviceResources, a Reset hned po úspěšném dokončení recreate smyčky (nezávisle na tom, zda následná větev pro activeRenderTarget_ vrátí úspěch nebo hodí výjimku vyžadující explicitní rebind — samotné zařízení už je použitelné). To naplní veřejné GraphicsDevice::DeviceLost/DeviceResetting/DeviceReset eventy a GraphicsDeviceStatus. Direct2D_2DParity ve Wine přidal test podle stejného vzoru jako D3D9's D9-34 (modules/renderers/directx9/examples/directx9_smoke_test.cpp): zaregistruje handlery, zavolá DebugSimulateContextLoss() (Direct2D nemá odděleně pozorovatelný "lost, čeká na reset" stav, takže jedno volání atomicky spustí všechny tři eventy), a ověří eventOrder == [Lost, Resetting, Reset] přesně jednou každý, GraphicsDeviceStatus se vrátí na Normal, a zařízení je použitelné poté. Regrese potvrzena (git stash na obě zdrojové soubory): bez opravy test spolehlivě FAILuje (eventOrder zůstává prázdný), s opravou PASSuje; celá sada 3 Wine testů zůstává zelená. |
| D2D-71 | Otestuj recovery s rozpracovaným SpriteBatch a drženými transient COM grafy. | ✅ | ReleaseDeviceResourcesNoThrow (volané z RecreateDeviceResourcesForRecovery) už dřív bezpečně dokončí otevřený EndDraw a vyprázdní všechny transient kolekce (transientBitmaps_/Effects_/Images_/ImageBrushes_) bez ohledu na to, zda je backend uprostřed kreslení. Direct2D_2DParity ve Wine přidal empirický test: otevři SpriteBatch, nakresli tintovaný sprite (drží transient CPU-fallback bitmapu), vyvolej DebugSimulateContextLoss() UPROSTŘED batch (bez End()), pokračuj v kreslení do stále "otevřeného" batch a zavolej End() — projde bez pádu se správným výsledným pixelem, což potvrzuje, že žádný draw neproběhl ze staré generace. |
| D2D-72 | Zaveď invarianty resource registry a debug assertions. | ✅ | RegisterTexture/RegisterRenderTarget teď mají assert() (aktivní v tomto non-NDEBUG buildu), který odhalí duplicitní registraci stejného ukazatele v recoverableTextures_/recoverableRenderTargets_. UnregisterTexture/UnregisterRenderTarget už dřív používaly std::erase přes celý kontejner, takže odregistrace neregistrovaného/už odregistrovaného ukazatele je bezpečný no-op (idempotentní z podstaty). Register/unregister jsou volané výhradně 1:1 z konstruktoru/destruktoru Direct2DTextureBackend/Direct2DRenderTargetBackend, takže duplicitní registrace normální cestou strukturálně nemůže nastat — nový assert slouží jako obrana proti budoucí regresi tohoto invariantu. Nový stress test v Direct2D_2DParity (4 cykly střídající create 3 textur + 3 RT, dispose každého druhého, DebugSimulateContextLoss()) prošel ve Wine beze spuštění assertu a všechny přeživší textury po opakované recovery kreslí se svým původním, správným pixelem — potvrzuje, že registry nekorumpuje obsah ani při intenzivním střídání create/dispose/recovery. Vynutit reálné dvojí zaregistrování stejného ukazatele zvenčí by vyžadovalo novou public debug-only metodu jen pro tento účel, což by bylo zbytečné rozšíření API pro obranný assert — vynechano. |
| D2D-73 | Při vypnuté context recovery skutečně neukládej backendové CPU shadows. | ✅ | Rozhodnutí o hranici, nikoli implementace — proto nemá vlastní test. Záměr úkolu byl zamítnut jako chybný předpoklad: RGBA shadows nejsou jen recovery cache, ale autorita pro authored mip-linear a compatibility tint/straight-alpha CPU fallback, které pokrývají testy D2D-74 a D2D-83. contextRecoveryEnabled=false vypíná registry/recreation, nikoli data nutná podporovanému kreslení. Lazy GPU readback by byl pouze budoucí performance trade-off. |
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-74 | Implementuj skutečnou interpolaci mezi dvěma mip levely pro všechny MipLinear filtry. |
✅ | Podporovaný authored-Texture2D kontrakt vypočítá fractional LOD a smíchá oba inicializované sousední levely; přesný (0,128,128,255) oracle prošel. Mipmapped RT je odmítnut, takže žádná podporovaná RT cesta tiše nevolí nearest místo MipLinear. |
| D2D-75 | Odděl výpočet spojitého LOD od volby mipů a zpřístupni jej jednotkovým testům. | ✅ | Čisté helpers jsou oddělené a všech 10 mip-policy/LOD unit testů (včetně NPOT, uniform scale, rotation, shear, singularity a presentation scale) skutečně proběhlo v Direct2D_Unit. |
| D2D-76 | Zjišťuj anisotropic podporu na konkrétním zařízení/runtime. | ✅ | D2D1_INTERPOLATION_MODE_ANISOTROPIC je součást požadovaného D2D1.1 device-context API, ne volitelné rozšíření. Destruktivní startup draw probe byl zamítnut, protože pod Wine poškodil pozdější sampler state. Capability je true a obě native DrawImage/ImageBrush cesty jsou runtime testované; fyzický adapter důkaz je externí. |
| D2D-77 | Použij runtime GetMaximumBitmapSize místo konstantního maxima. |
✅ | Override vrací live ID2D1DeviceContext::GetMaximumBitmapSize; creation validates it before CPU allocation. Boundary and limit+1 public tests prošly (Wine reports 16384). |
| D2D-78 | Ověř RT mip downsample vzorovanou NPOT texturou. | ✅ | Oracle prokázal alias/omitted quadrants v historickém 7×5 generatoru. Bezpečný závěr je deliberate unsupported: mipmapped RT creation se odmítne před alokací a flawed algorithm byl odstraněn. Veřejný rejection test prošel. |
| D2D-79 | Rozšiř mip výběr na úplnou matici scale × source rect × transform × presentation. | ✅ | Authored Texture2D matrix pokrývá pět scales × čtyři flip kombinace; unit oracle pokrývá source/presentation transform. RT mipy nejsou součást podporovaného kontraktu. |
| D2D-80 | Ověř a oprav transform ID2D1ImageBrush pro kladně posunutý source rectangle. |
✅ | Skutečný, potvrzený bug: clippedLeft/clippedTop počítané jako std::max(0.0f, -X) byly no-op pro záporné X (-X už tam bylo kladné), ale nesprávně klampovaly na nulu pro X>=0 — kladně posunutý crop (např. atlas X=2) tak vzorkoval od SKUTEČNÉHO počátku obrázku (X=0), ne od požadovaného offsetu. ID2D1ImageBrush's sourceRectangle sama o sobě nezarovná svůj roh s lokálním počátkem brush — bez SetTransform kompenzace brush-local (0,0) pořád mapuje na (0,0) CELÉHO obrázku, pro JAKÝKOLIV posun, ne jen záporný. Oprava: bezpodmínečné clippedLeft = -X/clippedTop = -Y (odstraněn max(0, …) klamp). Nový test — 4-pixelová atlas textura (červená/zelená/modrá/bílá), crop Rectangle(2,0,1,1), Color::White + FlipHorizontally (vynutí ImageBrush cestu pod Wine bez GPU ColorMatrix efektu; 1×1 crop nemá viditelný flip) — prokázal regresi: bez opravy vrací červenou (X=0), s opravou modrou (X=2, správně). git stash potvrdil pád beze změny; celá sada existujících záporný-offset/Wrap/Mirror/Flip testů (které stejnou cestou procházely už dřív) zůstává beze změny zelená — potvrzuje, že max(0,-X) byl pro tyto případy vždy no-op a odstranění klampu nic nerozbilo. |
| D2D-81 | Přidej oracle pro source rectangle částečně uvnitř i vně textury. | 🟨 | Horizontální polovina (dřívější práce) postavila skutečně nezávislý číselný oracle: 8-texelová pruhová textura, source rect (-1,0,10,1) přesahující na obou stranách, šest bodů volených tak, aby očekávaná hodnota nezávisela na nezdokumentované Direct2D bilineární konvenci; ve Wine prošlo všech 36 kontrol s nulovou odchylkou od ručního výpočtu (včetně Wrap seam vah 1/18 → 14 a 241, kde test odhalil chybu v mém vlastním odvození, ne v kódu). Nyní je doplněna vertikální polovina: stejný layout transponovaný na osu Y, source rect (0,-1,1,10), lichá výška 9 aby jeden vzorek padl přesně na v=4.0. Očekávané hodnoty se záměrně nepřepočítávají — jsou to tytéž horizontální. Direct2D bilineární filtr je separabilní a D2D1_IMAGE_BRUSH_PROPERTIES nese extendModeX a extendModeY jako dva nezávislé parametry, takže shoda musí platit; kdyby runtime souhlasil vodorovně a nesouhlasil svisle, je ta asymetrie sama nálezem. Kompilačně ověřeno; běh obou polovin ve Wine zbývá. |
| D2D-82 | Otestuj společnou geometrii crop, origin, flip, rotation, scale a batch transformu. | ✅ | Důvěryhodný asymetrický 4×3 atlas oracle kombinuje positive crop, nonzero origin, 90° rotation, nonuniform scale, batch translation a FlipH. Původní unflipped Y nález byl chybný Color::Green oracle (128 vs 255); flip cesta měla skutečný chybějící bitmap-origin transform a byla opravena. Všech 8 přesných bodů prošlo. |
| D2D-83 | Vytvoř nezávislou sampling oracle pro ImageBrush a CPU fallback. | ✅ | D2D-81 už postavil nezávislý matematický oracle pro ImageBrush (Linear × Clamp/Wrap/Mirror). Tento úkol aplikuje STEJNÝ oracle na CPU fallback cestu (MakeSpritePixels, použitá pro KAŽDÝ tintovaný draw pod Wine, protože GPU ColorMatrix tam není dostupný). MakeSpritePixels sama dělá čistě NEAREST-NEIGHBOR vzorkování (sampleCoordinate vrací vždy celé číslo, žádné frakční mísení) — zplošťuje zdroj do 1:1 RGBA bufferu s per-texel tintem, který je pak nahrán jako transient bitmapa a AŽ NÁSLEDNĚ lineárně interpolován GPU při škálování na destination rect. Tint je per-kanálový násobek (D2D-34), který komutuje s lineární interpolací, takže očekávané hodnoty jsou D2D-81's již ověřené blend váhy prostě vynásobené tint faktorem (200/255) — žádná nová matematika. Všech 18 kontrol (6 bodů × 3 režimy, Color(200,200,200,255) tint) PROŠLO, včetně Wrap-režimu na hranici (dostal (11,189,0)/(189,11,0) proti očekávaným (10,189,0)/(189,10,0) — rozdíl přesně 1 kvůli dvojímu zaokrouhlení CPU-tint-pak-GPU-blend řetězce, v toleranci). |
| D2D-84 | Oprav CPU fallback edge kernel pro lineární Wrap/Mirror podle oracle D2D-83. | ✅ | Premise úkolu ("CPU fallback edge kernel potřebuje opravu") se NEPOTVRDILA — D2D-83's oracle prokázal, že dvoufázový CPU-nearest-tint-pak-GPU-linear-interpolate pipeline je na Wrap/Mirror hranicích UŽ SPRÁVNÝ (transient bitmapa zjevně nese stejný address mode dál do GPU interpolace, ne jen CLAMP). Žádná změna zdrojového kódu nebyla potřeba; D2D-83's test s tolerancí pokrývající přesně pozorovanou ±1 dvojí-zaokrouhlovací odchylku už splňuje akceptační kritérium "hraniční texely v toleranci ~1 LSB" beze změny produkčního kódu. |
| D2D-85 | Definuj RT tint/NonPremultiplied na runtime bez built-in effects. | ✅ | Skutečný, potvrzený bug: requiresCpuBitmap (CPU fallback pro tint/NonPremultiplied bez GPU efektu) je podmíněný ordinaryTexture — pro RenderTarget2D zdroj (renderTargetTexture, žádný CPU shadow k dispozici) byla tato podmínka VŽDY false, takže kód spadl do requiresImageBrush větve a zavolal ThrowIfFailed(CreateEffect(CLSID_D2D1ColorMatrix, ...)), který pod Wine (bez ColorMatrix) neodchyceně hodil std::runtime_error s doslovným surovým HRESULT v textu — terminate called ... Direct2D: ID2D1DeviceContext::CreateEffect(ColorMatrix) failed, hr=0x88990028 (přesně a doslovně to, co úkol zakazuje). Nová explicitní kontrola PŘED touto větví hodí pojmenovanou System::NotSupportedException s jasným popisem místo toho. Test nahradil starý [SKIP] printf (existující už dřív, protože scénář nebyl vůbec zkoušen) skutečným pokusem o tint RT zdroje pod SpriteSortMode::Immediate (ne Deferred — Deferred by odložilo skutečné native volání až do End()'s flushBatch(), mimo try/catch kolem Draw(), což jsem musel objevit empiricky po prvním pádu testu) a ověřil zachycení nové výjimky i použitelnost zařízení poté. git stash potvrdil: bez opravy test spolehlivě PADÁ (neodchycený std::runtime_error s doslovným 0x88990028 v textu), s opravou PROCHÁZÍ čistě. |
| D2D-86 | Odmítej nebo bezpečně kopíruj sampling právě aktivního RT levelu 0. | ✅ | Zvolen kontrakt "odmítni" (bezpečnější a jednodušší než defenzivní kopie). Nová kontrola v DrawSprite na začátku (hned po ownership kontrole): renderTargetTexture == activeRenderTarget_ hodí pojmenovanou System::NotSupportedException PŘED jakýmkoliv Direct2D voláním. Dřív žádná taková kontrola neexistovala — git stash prokázal, že SpriteBatch draw sampling z právě aktivně navázaného RT byl TICHO POVOLEN (žádná výjimka, žádný pád), tedy skutečná nedefinovaná GPU aliasing situace beze zjevné chyby pod tímto WineD3D (nekrachuje, ale výsledek není definovaný). Nový test ověřuje VŠECHNY tři části akceptačního kritéria: (1) pokus o self-sample skutečně hodí novou výjimku, (2) GraphicsDevice::GetRenderTargets() potvrdí, že aktivní target zůstal navázaný beze změny, (3) GetData na targetu potvrdí, že jeho VLASTNÍ obsah zůstal netknutý (nebyl přepsán/poškozen pokusem o self-sample), (4) zařízení je použitelné pro normální draw poté. SpriteSortMode::Immediate použit (ne Deferred) podle D2D-85's poznatku, aby výjimka létala synchronně uvnitř try/catch kolem Draw(). |
| D2D-87 | Definuj SetData do právě navázaného RT. |
✅ | Validate/prepare and generation-check happen before mutation; EndDraw serializes, then CopyFromMemory updates level zero. Evidence: the Direct2D_2DParity active-target case writes the target, reads it back through GetBackBufferData while it is still bound, and draws from it afterwards -- both observe the exact new pixels. |
| D2D-88 | Definuj texture update během outstanding SpriteBatch draw. | ✅ | Texture2D upload nejprve připraví replacement, pak EndDraw commitne všechny příkazy odkazující na starou bitmapu a teprve potom atomicky přepne bitmap/shadow. Immediate public oracle prokazuje old-data první sprite a new-data druhý sprite ve stejném Begin/End. |
| D2D-89 | Doplň úplnou full/partial SetData matici pro každý mip Texture2D i RT. |
🟨 | Direct2D_2DParity má novou samostatnou sekci nad byte-exact přenosovým kontraktem (žádné filtrování, žádný blend, takže platí na každém runtimu). Texture2D 5×3 s authored levelem 2×1 pokrývá: full round trip levelu 0, obdélník na nenulovém offsetu s kontrolou všech ostatních texelů, poslední texel levelu, startIndex okno uvnitř většího bufferu, full i partial zápis authored mipu, důkaz že zápis mipu nezmění level 0, a GPU stranu přes point-sampled draw. Odmítnutí: obdélník mimo level, záporný počátek, elementCount menší než region, neexistující level, GetData mimo level a příliš malý cíl — po všech se plný level porovná a musí být beze změny. Prázdný region je doložen jako no-op, ne chyba (sdílená přenosová vrstva jej bounds-checkem propustí) — původní očekávání výjimky bylo ověřeno proti zdroji Texture2D::SetData a opraveno. RenderTarget2D má full i partial zápis, zachování ostatních texelů a odmítnutí levelu 1 i obdélníku mimo level 0. Kompilačně ověřeno cross-buildem; běh zbývá. |
| D2D-90 | Sjednoť dirty-mip invalidaci pro všechny úspěšné zápisy a žádné neúspěšné/no-op zápisy. | ✅ | RT generated mip/dirty state no longer exists. Texture2D lower levels are authored and transactional replacement updates only the named level. Evidence: Direct2D_2DParity's repeated partial SetData case asserts every untouched texel of a 3x2 asymmetric texture survives, and that a rejected short write leaves the previous data intact. |
| D2D-91 | Urči politiku authored vs automaticky generovaných nižších mipů RT. | ✅ | Jednoznačná politika: Texture2D lower levels jsou authored; RenderTarget2D lower levels neexistují a mipmapped creation/level>0 access jsou odmítnuté. |
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-92 | Zabal Map/Unmap do RAII a kontroluj výsledek Unmap. |
✅ | ScopedBitmapMap kontroluje Map i explicitní Unmap; destructor provádí pouze best-effort cleanup během unwinding. Readback parity/lifetime prošly. Fault-injection Unmap failure zůstává externí důkaz, ne známý defect. |
| D2D-93 | Otestuj readback row pitch, BGRA/RGBA převod a alpha režim na celé matici rozměrů. | ✅ | Unit oracle pokrývá odd width, padded/short pitch, asymmetric RGBA↔BGRA a alpha; public Texture2D, RT a logical readback pokrývají subregion/full paths. |
| D2D-94 | Sjednoť null, zero-size a prázdný readback kontrakt. | ✅ | Null, nonpositive dimensions, out-of-range regions and undersized destinations fail before map/copy; unit/public rejection paths preserve state. |
| D2D-95 | Přidej checked aritmetiku pro všechny pixelové a pitch alokace. | ✅ | CheckedRgbaByteCount first enforces D3D11's hard dimension ceiling even on 64-bit, then checks multiplication. Every production allocation also validates live D2D max before vector/native allocation. Extreme-dimension oracles reject before bad_alloc. |
| D2D-96 | Validuj rozměry proti runtime maximu před CPU i GPU alokací. | ✅ | Constructors and bitmap helpers enforce positive dimensions, D3D11 ceiling, live GetMaximumBitmapSize, and UINT32-safe pitch before allocation; exact max/limit+1 tests passed. |
| D2D-97 | Audituj a obsluž všechny ignorované HRESULT. | ✅ | Supported COM paths check creation, copy, map/unmap, association, description, close, EndDraw and Present. Effect probes treat only D2DERR_EFFECT_IS_NOT_REGISTERED as compatibility absence; device loss recovers and unexpected HRESULT propagates. No-throw teardown intentionally ignores only cleanup failures. |
| D2D-98 | Kontroluj návratové hodnoty použitých SDL API. | ✅ | SDL3 window-properties ID and Win32 HWND pointer are checked with SDL_GetError; Win32 client-rect and DXGI association calls are checked contextually. Evidence: every Direct2D CTest constructs a real HWND-backed device through those calls, so a regression to an unchecked handle fails device creation immediately. Deliberate physical failure injection remains external. |
| D2D-99 | Override ApplyDepthStencilState a odmítej povolené depth/stencil funkce. |
✅ | Backend přijme pouze jednorázový constructor bootstrap DepthStencilState::Default, který CNA nastaví před application code, a potom podporuje jen DepthStencilState::None. Každý další depth-enable/write nebo stencil-enable request se pojmenovaně odmítne; capability/real-buffer flags remain false. |
| D2D-100 | Validuj podporovaný 2D subset RasterizerState. |
✅ | Scissor dál funguje. FillMode.WireFrame a nenulový DepthBias/SlopeScaleDepthBias nyní pojmenovaně selžou. CullMode zůstává bez validace: Direct2D SpriteBatch nemá žádné uživatelem dodané vertex winding k ořezání (každý draw je pevný interně generovaný quad) a GraphicsDevice's konstruktor bezpodmínečně aplikuje RasterizerState.CullCounterClockwise ještě před herním kódem, takže odmítnutí libovolného cull módu by rozbilo každou aplikaci při startu. Výchozí RasterizerState i všechny tři Cull* presety mají FillMode/DepthBias/SlopeScaleDepthBias na neutrálních hodnotách, takže konstruktorový výchozí stav zůstává vždy přijat. Direct2D_2DParity ve Wine ověřil obě odmítnutí i použitelnost zařízení poté. |
| D2D-101 | Definuj a validuj viewport MinDepth/MaxDepth v 2D backendu. |
✅ | Přijímá se pouze finite 0..1 exact pair; jiné/NaN hodnoty se odmítají před clip mutation. |
| D2D-102 | Přidej společnou matici neznámých enumů a nekonečných/NaN vstupů. | ✅ | Presentation/filter/address/blend/rasterizer/viewport/SpriteEffects and transform rejection paths are deterministic. Evidence: the Direct2D_2DParity rejection matrix drives an unknown or non-finite value into each entry point and then issues a marker/control draw proving the device stayed usable and the previous state survived. |
| D2D-103 | Explicitně validuj RT depth format, MSAA a RenderTargetUsage. |
✅ | Only Color, Depth None and non-MSAA 0/1 are accepted; all usages are public-tested and unsupported combinations throw. |
| D2D-104 | Zaveď pravdivý surface/texture/backbuffer format support reporting. | ✅ | Fixed Color/BGRA mapping and Depth None are explicit; non-Color/depth requests are rejected; real depth/stencil report false. Evidence: Direct2D_2DParity constructs the supported SurfaceFormat::Color/DepthFormat::None combination and asserts the named rejection for a non-Color surface format request. |
| D2D-105 | Standardizuj pojmenované výjimky a kontext HRESULT. | ✅ | Supported paths carry operation context; missing runtime effects produce named capability errors, while unexpected/device-loss HRESULTs propagate with recovery context. |
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-106 | Aktualizuj level 0 přes CopyFromMemory místo rekonstrukce všech mip bitmap. |
🟨 | Direct2DRenderer::TryUploadBitmapRgba přepíše existující ID2D1Bitmap1 přes CopyFromMemory, takže upload nealokuje nový COM objekt ani novou D3D11 texturu; create-and-swap zůstává fallbackem pro runtime, který in-place upload nezvládne, a device-loss HRESULT jde rovnou do recovery. Stará bitmapa se přepíše jen tehdy, když patří k aktuální device generation — jinak by přepis oživil zdroj mrtvého zařízení. Zápis jednoho levelu se nedotkne COM identity ostatních. Přesný rozsah (ověřeno čtením volajících v Texture2D.cpp): optimalizace se týká partial SetData(0, rect, …), SetData(level>0, …), SetDataRGBA a full-level uploadu render targetu. Full-level Texture2D::SetData(const Color*, int) na obyčejné textuře sem nevede vůbec — sdílená vrstva tam záměrně staví celý nový renderer (REMED-GFX-223, izolace ContentManager cache), takže tuto cestu backend neovlivní. RGBA→BGRA staging jde do znovupoužívaného uploadScratch_. Kompilačně ověřeno cross-buildem; měřený benchmark alokací a času zbývá jako D2D-108/D2D-116. |
| D2D-107 | Cacheuj nebo pooluj opakované effect/brush konfigurace. | 🟨 | spriteBrushCache_ drží až 32 dekorovaných grafů; 256 shodných tintovaných spritů tak vytvoří jeden ColorMatrix/Premultiply/ID2D1ImageBrush graf, ne 256. Znovupoužití nezapisuje žádnou property na cachovaný objekt — každá hodnota, kterou by zapsalo, je součástí klíče, takže reuse je bezpečný i kdyby Direct2D četl stav brushe až v EndDraw. SpriteBrushCacheKey obsahuje identitu vstupního image, device generation, packed color, přítomnost tint/premultiply stupně i straight-alpha režim, source rect, oba extend módy, interpolaci, flip bity a sprite origin; pozice cíle v klíči záměrně není, protože žije v transformaci kontextu, takže řada spritů z jedné atlas buňky sdílí jednu položku. Cachovaný brush drží vlastní referenci na svůj vstupní image, takže jeho adresu nelze recyklovat pod položkou cache. Vyklizení jde do transientů a celá cache se ruší s device doménou. Unit test pinne rozlišení všech 16 polí klíče. Měřený benchmark „256 spritů = 1 graf“ zbývá jako D2D-108. |
| D2D-108 | Změř a optimalizuj plain i decorated SpriteBatch throughput. | 🟨 | Nový Direct2D_Soak měří čtyři větve odděleně — přímá DrawImage bitmapa, ImageBrush (vynucený flipem, takže nepotřebuje effect), dekorovaná větev (tint: nativně GPU effect, jinde CPU fallback) a RenderTarget2D zdroj — vždy po zahřívacím průchodu, aby se first-touch alokace, effect capability probe a cache nezapočítaly. Každá dávka je ukončena readbackem, takže se měří skutečné provedení, ne jen záznam příkazů. Výstup je ms-per-1000 na větev plus informace o vynuceném WARP. Hardware a WARP baseline se zapisují spuštěním se stejnou proměnnou na obou; samotné naměřené hodnoty zbývají, protože je v tomto prostředí není na čem naměřit. |
| D2D-109 | Recykluj CPU fallback bitmapy a pracovní pixely. | 🟨 | Dvě části. MakeSpritePixels/MakeMipBlendedSpritePixels už nevracejí nový std::vector, ale plní znovupoužívaný spritePixelScratch_ (assign si drží kapacitu), takže dlouhá dávka CPU-fallback spritů alokuje jen jednou. Druhá část je spriteBitmapCache_: klíč SpriteBitmapCacheKey obsahuje každý vstup obou generátorů (identita textury, ContentVersion, device generation, mip level, horní mip a fraction, source rect, packed color, flip bity, oba address módy a straight/premultiplied příznak), takže shodný opakovaný draw znovu použije už nahranou bitmapu a jakýkoliv SetData entry zneplatní. Cache je omezená (32 položek / 16 MB), vyklízí se nejstarší-první a vyklizená bitmapa přechází do transientů, aby ji dřív zaznamenaný příkaz nezrušil pod rukama; ruší se s celou device doménou. Dva nové unit testy pinnou rozlišovací schopnost klíče pole po poli (včetně počtu polí, aby nově přidané pole neprošlo netestované). Měřený „po warm-upu nealokuje“ benchmark zbývá jako D2D-108/D2D-116. |
| D2D-110 | Odstraň zbytečné duplicitní CPU shadows mezi shared Texture2D a backendem. | ✅ | Uzavřeno zamítnutím premisy s konkrétním důkazem, ne implementací (stejný postup jako D2D-73). Mechanismus by existoval — ITextureRenderer::ShareCpuPixels — ale sdílená vrstva ten ukazatel publikuje výhradně při (re)vytvoření rendereru, nikdy při výměně bufferu za ním: Texture2D::storeCpuPixels alokuje nový std::vector, kdykoliv byl cpuPixels_ uvolněn v MaybeFreeCpuPixels (což nastává při vypnuté context recovery), a znovu jej nesdílí. Reprodukovatelný scénář: vytvoř texturu s vypnutou recovery → MaybeFreeCpuPixels uvolní cpuPixels_ → SetDataRGBA vytvoří nový buffer a nahraje z něj → backend držící sdílený ukazatel by měl stale shadow. A ten shadow není jen recovery cache: je autoritou pro authored mip-linear míchání a pro compatibility tint/straight-alpha fallback (D2D-73), takže stale znamená špatné pixely, ne pomalejší recovery. Pro nižší mipy navíc žádný share hook neexistuje vůbec. Deduplikace by tedy vyžadovala změnu sdíleného kontraktu pro všech 41 rendererů, což není Direct2D-local změna. Vlastnictví jediné obnovovací kopie je nově zdokumentované komentářem přímo v Direct2DTextureRenderer::UpdatePixels, a skutečnou paměťovou stopu teď měří working-set oracle v Direct2D_Soak (D2D-117). |
| D2D-111 | Rezervuj a znovu používej transient kontejnery. | ✅ | ReserveTransientCapacity() dá všem čtyřem transient kolekcím i cache jejich ustálenou kapacitu při vzniku device domény; EndDrawing je uvolňuje přes clear(), které kapacitu zachovává, takže ustálený frame neprovádí žádnou realokaci. Cleanup zůstává správný i na chybové cestě: EndDrawing vyprázdní kolekce a vynuluje byte estimate před vyhodnocením HRESULT, takže výjimka z ThrowIfFailed ani device-loss větev nenechají transient objekty naživu. Ověřeno kompilačně a čtením kontraktu std::vector; měřený „frame po warm-upu nealokuje“ benchmark je D2D-108/D2D-116. |
| D2D-112 | Zaveď limit transientů a bezpečný chunk flush. | 🟨 | FlushTransientChunkIfOverBudget() po každém spritu ověří dva rozpočty (1024 transient COM objektů nebo 64 MB CPU-fallback bitmap) a při překročení uzavře chunk EndDrawingem a otevře nový. Znovuotevření obnoví přesně stav, který by nastavil EnsureDrawing — stejný primitive blend a stejné viewport/scissor clipy — ale záměrně nevolá EnsureMainTargetSize, aby resize okna nemohl uprostřed jedné dávky vyměnit cíl. Pořadí je zachované, protože Direct2D provede příkazy uzavřeného chunku před příkazy následujícího, a lifetime také: transienty se uvolní až po jejich provedení. Počet flushů je v diagnostice. Zbývá skutečný extrémně dlouhý běh jako důkaz (D2D-117 soak). |
| D2D-113 | Přeměň live-object report z textu na automatickou nulovou gate. | ✅ | ReportLiveDeviceObjectsNoThrow nyní ke každé debug-layer zprávě emituje severity=/category=/id= a uzavírá report značkou live-object report end, takže je strojově čitelný. scripts/verify-direct2d-debug-log.py selže na jakémkoliv live objektu mimo whitelist (ID3D11Device, ID3D11DeviceContext, ID3D11Debug, ID3D11InfoQueue — jediné objekty, které v okamžiku reportu drží samo CNA), na severity ERROR/CORRUPTION, na chybějící diagnostiku/debug layer i na uříznutý report. Workflow jej volá jako skutečný krok po obou nativních CTest bězích, tedy nejde už jen o upload artefaktu. |
| D2D-114 | Otestuj dispose zdroje po SpriteBatch::End a před Present. |
🟨 | Nejdřív oprava: přímá DrawImage větev jako jediná nedržela referenci na svůj zdrojový bitmap do EndDraw — spoléhala na to, že si ji podrží samo Direct2D. Aplikace přitom smí Texture2D zlikvidovat hned po SpriteBatch::End, takže se teď zdrojová bitmapa přidává do transientů stejně jako brush a command list. Direct2D_Lifetime má nový scénář, který ve stejném snímku nakreslí všechny čtyři zdrojové větve (přímá bitmapa, ImageBrush přes flip, dekorovaná/CPU fallback přes tint a RenderTarget2D zdroj), pak zlikviduje všechny čtyři zdroje a teprve potom čte pixely zpět. Kompilačně ověřeno cross-buildem; samotný běh potřebuje Wine/Windows. |
| D2D-115 | Otestuj update/dispose uprostřed frame na všech draw větvích. | 🟨 | Nový scénář v Direct2D_Lifetime používá SpriteSortMode::Immediate (aby první draw byl skutečné nativní volání, ne přehrání v End()), nakreslí přímou i ImageBrush větev, uprostřed dávky přepíše obě textury SetDataem a nakreslí je znovu. Očekávání je jednotné pro obě větve: už vydaný draw si drží pixely, se kterými byl vydán, a následující draw vidí nové. To pokrývá i interakci s D2D-106 (in-place CopyFromMemory po EndDraw), s cache bitmapy (klíč obsahuje ContentVersion) i s cache brushe (stejný image pointer, takže se znovu použije a vzorkuje nový obsah). Kompilačně ověřeno; běh zbývá. |
| D2D-116 | Nastav stabilní benchmark prahy po warm-up. | 🟨 | Politika je záměrná: benchmark hlásí a gatuje jen tehdy, když CNA_DIRECT2D_BENCH_BASELINE_MS dodá baseline naměřenou na srovnatelném stroji (tolerance přes CNA_DIRECT2D_BENCH_TOLERANCE, výchozí 2×). Pevný práh na sdíleném CI, na WARP nebo pod Wine je buď tak volný, že nic nedokazuje, nebo padá náhodně — proto žádný falešný Wine timing gate nevzniká. Každý řádek výstupu nese větev, driver a počet spritů, takže baseline je reprodukovatelně zaznamenatelná. Skutečné zapsané prahy zbývají na běh na cílovém hardwaru. |
| D2D-117 | Přidej dlouhý soak resize/recovery/target-switch. | 🟨 | Direct2D_Soak cykluje create/dispose textury i render targetu, bind/unbind, draw, readback s ověřením pixelu v každém cyklu (ne pouze „nespadlo“), každých 250 cyklů device recovery a každých 500 cyklů skutečný resize. Oracle paměti je pracovní sada procesu (GetProcessMemoryInfo) porovnaná mezi koncem zahřívání a koncem běhu s velkorysým limitem 64 MB — únik jediné malé bitmapy za cyklus jej překročí během několika set cyklů, zatímco šum alokátoru ne. COM stranu pokrývá odděleně live-object gate D2D-113. Výchozí počet cyklů je 2000; akceptační běh na 10 000 se zapíná CNA_DIRECT2D_SOAK_CYCLES a zbývá jako skutečný běh. |
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-118 | Označ a spouštěj Direct2D unit testy z CnaTests.exe v Direct2D gates. |
✅ | Direct2D_Unit je v labelu Direct2D; filter Direct2D* skutečně spustil 19 testů ze 6 suites a všechny prošly. |
| D2D-119 | Veď Direct2D CnaTests.exe přes Direct2D runner a oddělený prefix. |
✅ | Unit test používá stejný dedicated run-wine-direct2d.sh/Direct2D prefix jako runtime executables, nikoli D3D11/DXVK runner. |
| D2D-120 | Přidej kanonický virtual-display runner přes Xvfb. | ✅ | scripts/run-direct2d-virtual-display.sh je jediné místo, které rozhoduje o displeji: izolovaný Xvfb s explicitní geometrií CNA_DIRECT2D_XVFB_SCREEN (výchozí 1280x800x24), privátní X authority soubor v mktemp -d a úklid na každé cestě ven (trap ... EXIT INT TERM). Je reentrantní, takže ctest už spuštěný pod ním se znovu použije místo vnořeného serveru; run-wine-direct2d.sh i run-proton-direct2d.sh do něj vstupují samy a CNA_DIRECT2D_USE_HOST_DISPLAY=1 je opt-out. Lokálně ověřeno šest chování: čerstvý displej, reuse ve vnořeném volání (outer=:99 inner=:99), opt-out, odmítnutí neplatné geometrie, propagace exit kódu (42) a nulové zbytky v TMPDIR; /proc kontrola potvrdila, že se -screen 0 800x600x16 i -auth skutečně dostanou do Xvfb. |
| D2D-121 | Ověř suite z čerstvého Wine prefixu bez uživatelského stavu. | 🟨 | scripts/run-direct2d-fresh-wine-suite.sh <build-dir> vytvoří prefix v dočasném adresáři, inicializuje jej jediným deklarovaným krokem wineboot --init (žádný winetricks balík, žádný DLL override, žádná instalace DXVK — podporovaný kontrakt je vestavěné Wine d2d1/d3d11/dxgi), spustí celý label Direct2D přes kanonický displej a prefix zase smaže (CNA_DIRECT2D_KEEP_PREFIX=1 jej zachová). Odmítá ~/.wine jako "throwaway" prefix. Lokálně ověřené chybové cesty (chybějící argument = 2, neexistující build dir = 1, chybějící wine = 1) a bash -n; skutečný běh celé sady na čerstvém prefixu vyžaduje Wine, které v tomto sandboxu není — zbývá jako externí spuštění. |
| D2D-122 | Připni Proton runtime a publikuj jeho identitu. | ✅ | scripts/direct2d-proton-pin.txt drží prioritní seznam povolených Steam adresářů; run-proton-direct2d.sh už nehledá Proton - Experimental jako výchozí, ale vybírá první nainstalovaný pinned runtime a pohyblivý Experimental odmítne, dokud jej někdo jednorázově nepovolí CNA_DIRECT2D_PROTON_ALLOW_EXPERIMENTAL=1 (a i pak varuje, že výsledek není evidence). Každý běh publikuje identitu (ProtonDirectory, ProtonVersion, ProtonDistVersion, DxvkD3d11Path, DxvkVersion) na stderr a do CNA_DIRECT2D_PROTON_IDENTITY_FILE. Ověřeno lokálně proti připravenému falešnému Steam stromu: výběr pinned runtimu i s DXVK verzí dxvk-v2.4.1, odmítnutí Experimentalu, povolení s varováním, chybějící pin soubor, žádný nainstalovaný pinned runtime a zápis identity do souboru. |
| D2D-123 | Vynucuj maximálně dva paralelní build/test joby. | ✅ | .github/workflows/d3d-windows-ci.yml's build krok teď používá --parallel 2 (dřív neomezené); nový scripts/verify-direct2d-parallel-jobs.sh prohledá workflow i docs/direct2d-renderer.md/scripts/run-{wine,proton}-direct2d.sh na --parallel/-j bez čísla nebo s číslem >2 a je zapojen jako první krok DIRECT2D matrix jobu (selže dřív, než se cokoliv sestaví). Lokálně ověřeno: čistý běh projde, dočasné vrácení na neomezené --parallel skript správně odmítne. ctest volání ve workflow nepoužívají -j/--parallel vůbec (sekvenční, v rámci limitu). |
| D2D-124 | Nech CI selhat na debug-layer error/corruption a live-object warnings. | ✅ | scripts/verify-direct2d-debug-log.py má šest fixtures v tests/fixtures/direct2d/: dvě pozitivní (hardware, WARP) a čtyři negativní (ERROR zpráva, neočekávaný live ID3D11Texture2D, vypnutý debug layer, uříznutý report). --self-test je vlastní regresní oracle parseru a lokálně klasifikoval všech 6 správně; workflow jej spouští ještě před buildem a teprve pak parserem hodnotí oba nativní logy. |
| D2D-125 | Ověř omezení nativního runneru pro okno, session, DPI a hardware. | ✅ | Workflow nově zapisuje runner-limits.txt do artefaktu: interaktivita session, jméno session, počet a rozměry monitorů a tři explicitní Proves*=false řádky pro physical presentation, DPI scaling a multi-monitor, s odkazem na kritéria 4 a 5 release gate. Fakta se zaznamenávají, netvrdí — runner image, který by získal skutečný interaktivní desktop, se projeví změnou tohoto souboru místo tichého pohlcení. Hlavička workflow i docs/direct2d-runtime-support-matrix.md odděleně uvádějí, že hardware/WARP leg je headless gate, nikoli fyzický desktop lab test. |
| D2D-126 | Implementuj fyzický desktop/swap-chain capture oracle na Windows. | ⬜ | Capture porovnává přesné fyzické pixely všech presentation režimů a zaznamená DPI, monitor, adapter a rozměry. |
| D2D-127 | Přidej mutation testy kritických Direct2D větví. | 🟨 | scripts/direct2d_mutation_check.py má katalog sedmi mutací pokrývajících přesně větve, které úkol jmenuje: blend faktor (Opaque mapovaný jako source-over), Porter-Duff koeficienty (SourceAtop dostane koeficienty DestinationAtop), presentation transform (letterbox počítaný max místo min), volba mip levelu (floor → ceil), pořadí validace (ColorWriteChannels maska a non-white BlendFactor přijaté místo odmítnuté) a readback (BGRA vydávané za RGBA). Ke každé je pojmenován test, který ji musí zachytit. --dry-run ověří, že se každá kotva stále aplikuje právě jednou — to je ta část, která po refaktoru tiše zeschne a nechala by mutaci „přežít“ z důvodu nesouvisejícího s pokrytím — a běží kdekoliv bez buildu; workflow ji spouští. Lokálně ověřeno: všech sedm mutací se aplikuje, zkompiluje cross-buildem (mutace, která nejde přeložit, by nic nedokázala) a čistě se vrátí zpět. Skutečný --run (build + očekávané selhání testu) zbývá na prostředí, kde Direct2D testy běží. |
| D2D-128 | Rozděl monolitický direct2d_2d_parity_test.cpp. |
🟨 | Splněna část „společné helpers bez duplicitní oracle“: direct2d_test_support.hpp drží jedinou definici tolerance a jediný „přečti pixel, vypiš obě barvy, akumuluj verdikt“ krok (Matches, PixelChecker), který teď používá parity i lifetime test; duplicitní kopie zmizely, takže dva testy se už nemohou rozejít v tom, co tolerance znamená. Ověřeno mechanicky, že extrakce nic neztratila: množina 83 check(...) labelů je před i po identická a jediné odstraněné literály jsou "PASS", "FAIL" a přesunutý printf formát. Rozdělení na samostatné cíle jsem záměrně neprovedl: znamená přesun ~2500 řádků do šesti fixtures a v tomto prostředí nelze spustit jediný Direct2D test, takže tiše vypadlá kontrola by se neprojevila (překladač ji nezachytí). Invariant, který takový split musí zachovat, je právě ta množina labelů — je strojově porovnatelná, takže budoucí session má hotové kritérium správnosti. |
| D2D-129 | Přidej EasyGL-vs-Direct2D diferenciální korpus. | 🟨 | modules/graphics/examples/cross_renderer_2d_corpus.cpp je renderer-agnostický zdroj (žádný #ifdef, žádný renderer-specifický include) sestavovaný jednou pro každý renderer — registrovaný jako cna_corpus2d_direct2d i cna_corpus2d_easygl podle už zavedeného precedentu cna_diag_easygl/cna_diag_software. Vysype 64×64 RGBA8 backbuffer ve stejném layoutu, jaký už čte cna_diag_compare, takže žádný druhý komparátor nevzniká. Existující 3D cross_renderer_diagnostic_scene.cpp tuto roli plnit nemůže: kreslí BasicEffect trojúhelník přes VertexBuffer, což Direct2D jako trvale 2D-only renderer odmítá. Korpus má čtyři řady: blit/oba flipy/crop, čtyři tinty, obě alpha konvence nad stejným pozadím (obě poloviny musí dát stejnou barvu) a RenderTarget2D round trip se scissorem. Whitelist známých rozdílů je explicitní a úzký — pět konstruktů vyjmenovaných s důvodem v docs/direct2d-easygl-differential.md je z korpusu záměrně venku, aby tolerance mohla zůstat 4/kanál místo tak široké, že by v ní regrese zmizela. Kompilačně ověřeno; skutečné dva dumpy a jejich porovnání zbývají na prostředí, kde oba renderery běží. |
| D2D-130 | Udržuj runtime support matici Windows/Wine/Proton/WARP/hardware. | ✅ | docs/direct2d-runtime-support-matrix.md má čtyři dispozice (supported / fallback / named reject / open task) a pokrývá drawing a sampling, compositing i presentation a device — každá větev × čtyři runtimy, s pojmenovaným plan rowem u každé otevřené položky. Žádný globální skip neexistuje: existují právě dvě skip proměnné a matice u obou uvádí jedinou větev, kterou zdržují. scripts/validate_direct2d_plan.py navíc mechanicky selže, pokud se ve stromu objeví CNA_DIRECT2D_SKIP_* proměnná bez řádku v matici — ověřeno injektováním přejmenované proměnné. |
| D2D-131 | Oprav README a Direct2D dokumentaci podle skutečné podpory. | ✅ | README, backend doc a mip-spike outcome nyní uvádějí identity 39, D2D1.1-only drawing, fixed Color/depth-none contract, authored Texture2D mips, rejected RT mips/Additive/3D, DPI96 logical-pixel units and presentation mapping, four test gates, Wine skips and external native evidence. |
| D2D-132 | Automaticky kontroluj konzistenci stavů plánu a důkazů. | ✅ | scripts/validate_direct2d_plan.py parsuje všech 131 řádků a selže na: neznámém stavu, duplicitním ID, ✅ bez jakékoliv evidence (test, gate, cesta, delegace na jiný úkol), citované cestě/CTestu/CNA_DIRECT2D_* proměnné, která neexistuje, odkazu na neexistující úkol, nezařazené neúplné položce v závěrečné klasifikaci a na zastaralé souhrnné značce direct2d-plan-status. Jeho první běh našel čtyři skutečné vady: dvě mrtvé cesty po přesunu modulů (na starý D3D9 smoke example a na přejmenovaný Direct2D dokument), devět ✅ řádků bez jmenované evidence a zastaralou klasifikační tabulku; všechny jsou v tomto commitu opravené. Detekce byla ověřena injektováním všech tří tříd nekonzistence (neexistující skript, přepnutý stav proti značce, vymyšlený CTest label) a workflow jej spouští jako první krok DIRECT2D větve. |
| D2D-133 | Definuj závěrečná release kritéria Direct2D backendu. | ✅ | docs/direct2d-release-gate.md definuje pět kritérií a scripts/validate_direct2d_plan.py --release-gate je vyhodnotí a vypíše PASS/BLOCKED; nenulový exit kód je jediná odpověď na otázku „je Direct2D releasable?“. Kritéria: uzavřené všechny řádky plánu, registrované všechny čtyři CTest gate, existující a self-testovaná debug-layer/live-object gate, zaznamenaný nativní x64 Windows běh (D2D-22) a zaznamenaný fyzický presentation/DPI capture (D2D-126). Gate dnes korektně hlásí BLOCKED na třech kritériích a vyjmenuje konkrétní otevřené řádky; není splnitelný přepsáním prózy, protože obě evidence musí být skutečné soubory s předepsaným obsahem. Výkonnostní, soak, mutation a refactor práce release nezdržují, což gate záměrně nekontroluje. |
| # | Úkol | Stav | Akceptace |
|---|---|---|---|
| D2D-134 | Oprav dangling device-event callback v parity testu. | ✅ | Callbacky zachycují eventOrder referencí. V historickém testu lokální vector zanikl dříve než dlouho žijící GraphicsDevice, takže pozdější device-loss callback zapisoval do zrušeného objektu a Wine skončilo page faultem. eventOrder_ je nyní member deklarovaný před GraphicsDeviceManager, takže manager/device se zničí jako první. Celý parity test včetně lost/resetting/reset cyklu a všech následujících kontrol proběhl bez pádu. Jde o test-lifetime opravu, nikoli maskování produkční device-loss chyby. |
| D2D-135 | Zachovej native blend stav při odmítnutém embedded BlendFactor. |
✅ | GraphicsDevice volá ApplyBlendState před SetBlendFactor; Direct2D dříve publikoval první polovinu a při ne-bílém faktoru vyhodil výjimku až ve druhé, takže veřejná cache a native stav se rozešly. Backend nyní drží odvozený blend jako pending a publikuje jej až po úspěšné validaci faktoru. Veřejný cache oracle a přímý backend SpriteBatch pixel dokazují, že odmítnutý AlphaBlend ponechá aktivní předchozí NonPremultiplied. Pending stav se při každém odmítnutí i teardownu zahodí. |
| D2D-136 | Zahrň Direct2D do oracle počtu compile-time backend maker. | ✅ | Sdílený GraphicsBackendCompileDefinitionsTest.ExactlyOneGraphicsBackendIsSelected nepočítal CNA_BACKEND_DIRECT2D, takže skutečný Direct2D build hlásil nula vybraných backendů. Přidán je pouze chybějící arm; Direct2D test nyní hlásí přesně jeden a OPENGLES control zůstává zelený. |
Každá položka, která po adaptaci zůstává 🟨 nebo ⬜, patří výslovně do jedné z následujících
skupin. Žádná z nich není známý defect podporované Direct2D produkční cesty:
| Klasifikace | Položky | Význam pro integraci |
|---|---|---|
| Implementováno; chybí silnější fault/native evidence | D2D-24, D2D-52, D2D-60, D2D-65, D2D-67, D2D-68, D2D-81 | Produkční stav/recovery/sampling kód a dostupné oracle jsou zelené. Zbývá nedostupná fault injection, native effect větev nebo širší oracle, nikoli známé chybné chování. |
| Deliberate unsupported/rejected | žádná neuzavřená; uzavřené D2D-12, D2D-35, D2D-36, D2D-49, D2D-66, D2D-73, D2D-78, D2D-90 a D2D-91 | Additive, mipmapped RT, Present s aktivním RT a superseded generator paths mají explicitní rejection/boundary a ✅. |
| Environment/native external | D2D-1, D2D-4, D2D-20, D2D-22, D2D-23, D2D-27, D2D-28, D2D-33, D2D-34, D2D-38, D2D-39, D2D-40, D2D-41, D2D-45, D2D-55, D2D-57, D2D-58, D2D-126 | Vyžadují skutečné x64 Windows built-in effects/composites, fyzický displej/DPI/desktop capture, adapter/debug layer nebo live-object výstup. Wine/Xvfb je nenahrazuje. D2D-113/D2D-124 sem už nepatří: jejich gate i jeho fixtures existují a běží, chybí jen první nativní běh, který je spustí. |
| Process, coverage, performance nebo refactor; nonblocking | D2D-89, D2D-106 až D2D-109, D2D-112, D2D-114 až D2D-117, D2D-121, D2D-127, D2D-128 a D2D-129 | Rozšiřují dokumentaci, matici testů, benchmarky, soak, CI automatizaci nebo strukturu. Neopravují známý supported-path defect a nesmějí rozšířit scope bez vlastní evidence. Uzavřené položky této skupiny (D2D-44, D2D-111, D2D-113, D2D-120, D2D-122, D2D-124, D2D-125, D2D-130, D2D-132, D2D-133) už zde nejsou vedeny jako zbývající. |
- Neměň deliberate 2D-only hranici bez nové explicitní autorizace: žádná 3D/D3D11 fallback cesta, Additive aproximace ani obnovení flawed RT mip generatoru.
D2D-22zůstává nativní gate pro built-in effects/composites aD2D-126fyzická presentation/DPI gate. Xvfb Wine není důkazem ani jedné.- Stav
✅vyžaduje podporovaný kontrakt s unit/public oracle nebo explicitní tested rejection; compatibility skip nesmí být prezentován jako native pass. - Direct2D build/test commands must keep an explicit numeric maximum of two jobs. Unit, smoke, parity and lifetime run sequentially through the dedicated Direct2D runner.
- Performance, soak, mutation, differential and refactor work remains nonblocking unless it uncovers a concrete supported-path production defect; such a defect immediately reopens the gate.