Diff hidden because this file has more than 800 lines.
| … | 13 unchanged lines hidden | ||
| 14 | 14 | if g_d3d == nil {return} | |
| 15 | 15 | ||
| 16 | 16 | wait_gpu_idle() | |
| 17 | + | drain_deferred_releases_now_d3d12() | |
| 17 | 18 | ||
| 18 | 19 | // Destroy all active samplers | |
| 19 | 20 | for &entry in g_d3d.samplers { | |
| … | 18 unchanged lines hidden | ||
| 38 | 39 | } | |
| 39 | 40 | ||
| 40 | 41 | // Destroy shaders | |
| 41 | - | for &entry in g_d3d.shaders { | |
| 42 | - | if entry.active { | |
| 43 | - | delete(entry.bytecode) | |
| 44 | - | entry.active = false | |
| 45 | - | } | |
| 42 | + | for i in 0 ..< MAX_SHADERS { | |
| 43 | + | if g_d3d.shaders[i].active do destroy_shader_d3d12(bk.Shader_Handle(i)) | |
| 46 | 44 | } | |
| 47 | 45 | ||
| 48 | 46 | // Destroy pipelines | |
| 49 | - | for &entry in g_d3d.pipelines { | |
| 50 | - | if entry.active { | |
| 51 | - | if entry.pso != nil {entry.pso->Release()} | |
| 52 | - | entry.active = false | |
| 53 | - | } | |
| 47 | + | for i in 0 ..< MAX_PIPELINES { | |
| 48 | + | if g_d3d.pipelines[i].active do destroy_graphics_pipeline_d3d12(bk.Pipeline_Handle(i)) | |
| 54 | 49 | } | |
| 55 | 50 | ||
| 56 | 51 | // Destroy textures | |
| 57 | - | for &entry in g_d3d.textures { | |
| 58 | - | if entry.active { | |
| 59 | - | if entry.resource != nil {entry.resource->Release()} | |
| 60 | - | entry.active = false | |
| 61 | - | } | |
| 52 | + | for i in 0 ..< MAX_TEXTURES { | |
| 53 | + | if g_d3d.textures[i].active do destroy_texture_d3d12(bk.Texture_Handle(i)) | |
| 62 | 54 | } | |
| 63 | 55 | ||
| 64 | 56 | // Destroy buffers | |
| 65 | - | for &entry in g_d3d.buffers { | |
| 66 | - | if entry.active { | |
| 67 | - | if entry.resource != nil {entry.resource->Release()} | |
| 68 | - | entry.active = false | |
| 69 | - | } | |
| 57 | + | for i in 0 ..< MAX_BUFFERS { | |
| 58 | + | if g_d3d.buffers[i].active do destroy_buffer_d3d12(bk.Buffer_Handle(i)) | |
| 70 | 59 | } | |
| 71 | 60 | ||
| 61 | + | drain_deferred_releases_now_d3d12() | |
| 62 | + | ||
| 72 | 63 | // Destroy core objects | |
| 73 | 64 | if g_d3d.graphics_root_sig != nil {g_d3d.graphics_root_sig->Release()} | |
| 74 | 65 | if g_d3d.compute_root_sig != nil {g_d3d.compute_root_sig->Release()} | |
| … | 26 unchanged lines hidden | ||
| 101 | 92 | if g_d3d.command_queue != nil {g_d3d.command_queue->Release()} | |
| 102 | 93 | if g_d3d.device != nil {g_d3d.device->Release()} | |
| 103 | 94 | ||
| 95 | + | delete(g_d3d.deferred_releases) | |
| 104 | 96 | free(g_d3d) | |
| 105 | 97 | g_d3d = nil | |
| 106 | 98 | bk.backend_initialized = false | |
| … | 1 unchanged lines hidden | ||
| 108 | 100 | ||
| 109 | 101 | wait_idle_d3d12 :: proc() { | |
| 110 | 102 | wait_gpu_idle() | |
| 103 | + | drain_deferred_releases_now_d3d12() | |
| 111 | 104 | } | |
| 112 | 105 | ||
| 113 | 106 | // --- Frame lifecycle --- | |
| … | 11 unchanged lines hidden | ||
| 125 | 118 | win32.WaitForSingleObject(g_d3d.fence_event, win32.INFINITE) | |
| 126 | 119 | } | |
| 127 | 120 | } | |
| 121 | + | drain_deferred_releases_now_d3d12() | |
| 128 | 122 | ||
| 129 | 123 | // Reset allocator and command list | |
| 130 | 124 | fr.command_allocator->Reset() | |
| … | 47 unchanged lines hidden | ||
| 178 | 172 | fr.fence_value = g_d3d.fence_value | |
| 179 | 173 | g_d3d.command_queue->Signal(g_d3d.fence, g_d3d.fence_value) | |
| 180 | 174 | g_d3d.fence_value += 1 | |
| 175 | + | drain_deferred_releases_now_d3d12() | |
| 181 | 176 | ||
| 182 | 177 | g_d3d.frame_active = false | |
| 183 | 178 | g_d3d.render_pass_active = false | |
| … | 209 unchanged lines hidden | ||
Diff hidden because this file has more than 800 lines.
| … | 43 unchanged lines hidden | ||
| 44 | 44 | ||
| 45 | 45 | Rule: if visibility requires meaning, host handles it. If visibility only requires geometry/render state, `gpu` may handle it. | |
| 46 | 46 | ||
| 47 | + | ## Resource lifetime and synchronization | |
| 48 | + | ||
| 49 | + | Backends must preserve GPU object lifetime until every command list that references the object has completed. Destroying a buffer, texture, shader, pipeline, framebuffer attachment, or temporary upload allocation while frames are in flight retires the backend object behind the completed fence that covers the already-recorded work. `wait_idle` drains all retired objects. | |
| 50 | + | ||
| 51 | + | CPU mapping is only valid for host-visible buffers. GPU-local/default buffers are not mappable through `map_buffer` or `get_buffer_mapped`; backends must report that explicitly and return `nil`. Host-visible upload buffers may stay persistently mapped when the backend memory model allows it. | |
| 52 | + | ||
| 53 | + | `compute_barrier` means storage/UAV visibility between backend dispatch/draw work in one frame. It is not a render-target transition substitute. Backends that lack a complete storage descriptor path must report that separately through capabilities or diagnostics instead of pretending the barrier enables unsupported binding. | |
| 54 | + | ||
| 55 | + | D3D12 runtime fence behavior cannot be executed by Linux CI. D3D12 lifetime and transition work is accepted by static backend checks, pure fence-order tests where available, and explicit Windows runtime verification debt. | |
| 56 | + | ||
| 47 | 57 | ## Public frame shape | |
| 48 | 58 | ||
| 49 | 59 | Target API shape: | |
| … | 184 unchanged lines hidden | ||