diff --git a/.gitignore b/.gitignore index bf82dfb5..2037e125 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ projects/ workdir/cache/ workdir/Assets/ workdir/pso +workdir/assert.txt + *.dat AgilitySDK/ diff --git a/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp b/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp index ae98455e..9ff57e95 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp +++ b/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp @@ -431,12 +431,29 @@ namespace HAL barrier.LayoutAfter = to_native(e.after.get_layout()); barrier.pResource = e.resource->get_dx(); - barrier.Subresources.IndexOrFirstMipLevel = e.resource->get_desc().as_texture().get_mip(e.subres); - barrier.Subresources.NumMipLevels = 1; - barrier.Subresources.FirstArraySlice = e.resource->get_desc().as_texture().get_array(e.subres); - barrier.Subresources.NumArraySlices = 1; - barrier.Subresources.FirstPlane = e.resource->get_desc().as_texture().get_plane(e.subres); - barrier.Subresources.NumPlanes = 1; + if (e.subres == 0xffffffffu) // HAL::ALL_SUBRESOURCES + { + // Single barrier covering every subresource. D3D12 uses + // IndexOrFirstMipLevel == 0xffffffff (== ALL_SUBRESOURCES) + // as the "all subresources" sentinel; the other range + // fields are then ignored. The uniform-state fast path + // emits this instead of one barrier per mip/array/plane. + barrier.Subresources.IndexOrFirstMipLevel = 0xffffffff; + barrier.Subresources.NumMipLevels = 0; + barrier.Subresources.FirstArraySlice = 0; + barrier.Subresources.NumArraySlices = 0; + barrier.Subresources.FirstPlane = 0; + barrier.Subresources.NumPlanes = 0; + } + else + { + barrier.Subresources.IndexOrFirstMipLevel = e.resource->get_desc().as_texture().get_mip(e.subres); + barrier.Subresources.NumMipLevels = 1; + barrier.Subresources.FirstArraySlice = e.resource->get_desc().as_texture().get_array(e.subres); + barrier.Subresources.NumArraySlices = 1; + barrier.Subresources.FirstPlane = e.resource->get_desc().as_texture().get_plane(e.subres); + barrier.Subresources.NumPlanes = 1; + } barrier.Flags = D3D12_TEXTURE_BARRIER_FLAG_NONE; if (check(e.flags & BarrierFlags::DISCARD)) diff --git a/sources/HAL/API/D3D12/HAL.D3D12.Device.cpp b/sources/HAL/API/D3D12/HAL.D3D12.Device.cpp index b9ec24d1..8ca16ec0 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.Device.cpp +++ b/sources/HAL/API/D3D12/HAL.D3D12.Device.cpp @@ -305,6 +305,37 @@ namespace HAL filter.DenyList.pIDList = hide; d3dInfoQueue->AddStorageFilterEntries(&filter); } + + // Mirror debug-layer output into workdir/log.txt. Without this the + // messages only reach the attached debugger's output window, which + // makes barrier work (where the validation layer IS the test) hard + // to iterate on from a plain console run. + ComPtr d3dInfoQueue1; + if (SUCCEEDED(native_device.As(&d3dInfoQueue1))) + { + DWORD cookie = 0; + d3dInfoQueue1->RegisterMessageCallback( + [](D3D12_MESSAGE_CATEGORY, D3D12_MESSAGE_SEVERITY severity, + D3D12_MESSAGE_ID id, LPCSTR description, void*) + { + const char* tag = + severity == D3D12_MESSAGE_SEVERITY_CORRUPTION ? "D3D12 CORRUPTION" : + severity == D3D12_MESSAGE_SEVERITY_ERROR ? "D3D12 ERROR" : + severity == D3D12_MESSAGE_SEVERITY_WARNING ? "D3D12 WARNING" : + nullptr; + + if (!tag) return; // INFO/MESSAGE would drown the log + + Log::get() << tag << " #" << int(id) << ": " << description << Log::endl; + }, + D3D12_MESSAGE_CALLBACK_FLAG_NONE, nullptr, &cookie); + + Log::get() << "D3D12 SETUP: message callback registered, cookie " << int(cookie) << Log::endl; + } + else + { + Log::get() << "D3D12 SETUP: ID3D12InfoQueue1 unavailable — no callback" << Log::endl; + } } DSTORAGE_CONFIGURATION ds_config{}; diff --git a/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp b/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp index ea80bf48..2a4d8aa9 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp +++ b/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp @@ -264,10 +264,14 @@ namespace HAL void Queue::execute(const API::CommandList* list) { PROFILE(L"Queue::execute"); - queued.emplace_back(list->get_native().Get()); - // My framegraph system pushes Sync&Access barrier as NONE at the end of the pass so its not possible to use it later, need to redesign it - flush(); + // Accumulate lists into one ExecuteCommandLists — no per-list flush. + // Legal because TaskBuilder::link_list_groups chains resource state + // across list boundaries within a group: D3D12 forbids, within one + // scope, accessing a resource after a SyncAfter == SYNC_NONE barrier or + // emitting a SyncBefore == SYNC_NONE barrier once it has been accessed + // (#1417). gpu_wait/signal still flush at real sync points. + queued.emplace_back(list->get_native().Get()); } void Queue::flush() diff --git a/sources/HAL/API/D3D12/HAL.D3D12.Resource.cpp b/sources/HAL/API/D3D12/HAL.D3D12.Resource.cpp index 1ecd6ca0..71c2c56e 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.Resource.cpp +++ b/sources/HAL/API/D3D12/HAL.D3D12.Resource.cpp @@ -53,7 +53,7 @@ namespace HAL return ptr; } - void Resource::init(Device& device, const ResourceDesc& _desc, const PlacementAddress& address, TextureLayout initialLayout) + void Resource::init(Device& device, const ResourceDesc& _desc, const PlacementAddress& address, TextureLayout initialLayout, vec4 clear_value) { auto THIS = static_cast(this); auto& desc = THIS->desc; @@ -92,11 +92,15 @@ namespace HAL if (check(desc.Flags & HAL::ResFlags::RenderTarget)) { + // Honour the requested optimized clear value (was hardcoded to + // black, so any ClearRenderTargetView with another colour got + // D3D12 #820 MISMATCHINGCLEARVALUE and took the slow path). + // Defaults to (0,0,0,0), so existing callers are unaffected. value.Format = to_native(texture_desc.Format.to_srv()); - value.Color[0] = 0; - value.Color[1] = 0; - value.Color[2] = 0; - value.Color[3] = 0; + value.Color[0] = clear_value.x; + value.Color[1] = clear_value.y; + value.Color[2] = clear_value.z; + value.Color[3] = clear_value.w; pass_value = &value; } @@ -226,7 +230,10 @@ namespace HAL address = { alloc_handle.get_heap().get(),alloc_handle.get_offset() }; } - init(device, desc, address, initialLayout); + // Forward the optimized clear value — it was being dropped here, so the + // parameter plumbed through TextureResource/Resource never reached + // resource creation. + init(device, desc, address, initialLayout, clear_value); } Resource::Resource(Device& device, const ResourceDesc& desc, HeapType heap_type, TextureLayout initialLayout, vec4 clear_value) :state_manager(this), tiled_manager(this) { diff --git a/sources/HAL/API/D3D12/HAL.D3D12.Resource.ixx b/sources/HAL/API/D3D12/HAL.D3D12.Resource.ixx index c9f3a7fe..0e91979e 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.Resource.ixx +++ b/sources/HAL/API/D3D12/HAL.D3D12.Resource.ixx @@ -33,7 +33,10 @@ export namespace HAL GPUAddressPtr address; public: using ptr = std::shared_ptr; - void init(Device& device, const ResourceDesc& desc, const PlacementAddress& address, TextureLayout initialLayout = TextureLayout::UNDEFINED); + // clear_value: optimized clear value baked into a RenderTarget + // resource. Must match the colour passed to ClearRenderTargetView or + // D3D12 warns (#820) and takes a slower clear path. + void init(Device& device, const ResourceDesc& desc, const PlacementAddress& address, TextureLayout initialLayout = TextureLayout::UNDEFINED, vec4 clear_value = vec4(0, 0, 0, 0)); // Replaces the old D3D::Resource constructor; backend-neutral. void init(const NativeImportHandle& handle, TextureLayout layout, Device& device); diff --git a/sources/HAL/HAL.CommandList.cpp b/sources/HAL/HAL.CommandList.cpp index 7705df6b..303ff351 100644 --- a/sources/HAL/HAL.CommandList.cpp +++ b/sources/HAL/HAL.CommandList.cpp @@ -885,10 +885,67 @@ namespace HAL compiler.func_barrier(point); } + // Open a new batch of `op`, continue the current one if same, or close + // the previous (different class) and open a new one. + void Transitions::begin_op(BarrierSync op) + { + if (HAL::Debug::EnableOpBatching && open_op == op) + return; // same class → keep open + if (open_op != BarrierSync::NONE) + create_usage_point(open_op, false); // close previous batch + create_usage_point(op); // open new batch + open_op = op; + current_batch_id++; // fresh batch id + } + + // Close the currently open batch (list end / flush). + void Transitions::close_op() + { + if (open_op == BarrierSync::NONE) return; + create_usage_point(open_op, false); + open_op = BarrierSync::NONE; + } + + // Split the open batch when this transition would need a real barrier + // against an earlier op in the same batch (different, non-mergeable + // state on the same resource — write-after-write in another state, + // read-after-write, etc.). Same/mergeable state = no barrier = no + // split, exactly matching the un-batched barrier set. Reads the + // resource's per-list state directly (no parallel map). + void Transitions::batch_hazard_check(const HAL::Resource* resource, ResourceState to) + { + if (open_op == BarrierSync::NONE) return; + + auto& cpu = const_cast(resource)->get_state_manager().get_cpu_state(this); + + if (cpu.batch_touch_id == current_batch_id + && !(cpu.batch_touch_state == to) && !merge_state(cpu.batch_touch_state, to)) + { + // Split so this transition (and the ops after it) land in a new + // point positioned after the earlier op — the barrier must run + // between the two conflicting uses. current_batch_id is NOT + // bumped: it is the OP-CLASS batch epoch (advanced only by + // begin_op). A resource touched anywhere in the op-class batch + // must stay detectable across intra-batch splits — otherwise a + // later conflicting use (e.g. SMAA_blend written RT by one draw, + // read SR by the next) would miss its split after an unrelated + // split already occurred, mis-positioning the barrier. + create_usage_point(open_op, false); // close before the hazard + create_usage_point(open_op); // reopen same class after + } + + cpu.batch_touch_id = current_batch_id; + cpu.batch_touch_state = to; + } + void Transitions::compile_transitions() { - if(transitions_compiled) + if(transitions_compiled) return; + + // Close any batch still open at list end so its resources are + // fully recorded before the usage points are walked below. + close_op(); std::set non_tracked_resources; @@ -899,8 +956,16 @@ namespace HAL auto& usage = *u; auto prev_usage = usage.prev_usage; + // A list-boundary marker bypassed by chain_lists: the + // neighbouring list is in this ExecuteCommandLists group, + // so state is handed over directly and this node must not + // publish a SYNC_NONE barrier (D3D12 #1417). It is also not + // an unsynchronized first touch, so keep it out of + // non_tracked_resources below. + if (usage.suppressed) continue; + // ASSERT(!usage.debug); - if(!prev_usage) + if(!prev_usage) { bool is_automatic = check(usage.resource->get_desc().Flags &HAL::ResFlags::DisableStateTracking); // ASSERT(!); @@ -1003,6 +1068,15 @@ namespace HAL if (!subres_cpu.used) return; ASSERT(subres_cpu.used); + // A later list in the same ExecuteCommandLists group still + // uses this subresource, and its state was chained across + // the boundary (chain_lists) — releasing it here to + // NO_ACCESS/SYNC_NONE would make it untouchable for the + // rest of the scope (D3D12 #1417). The group's last user + // releases it. Checked per subresource so that mips which + // were NOT carried forward still decay to initial_layout. + if (subres_cpu.skip_end_decay) return; + auto target = ResourceStates::NO_ACCESS; @@ -1010,6 +1084,19 @@ namespace HAL auto end_point = subres_cpu.last_usage->point->next_point; + // A bare transition-only list (built with the public + // transition() API, e.g. the upload/promote list) never opened + // an op batch, so close_op()'s open_op==NONE guard skipped the + // trailing point — the last usage sits at the final point with + // no next_point. Append one now (usage_points is a deque, so + // existing UsagePoint* stay valid) so the release lands AFTER + // the last usage. Op-batched lists already have this point. + if (!end_point) + { + create_usage_point(BarrierSync::NONE, false); + end_point = subres_cpu.last_usage->point->next_point; + } + if (resource->debug_transitions) Log::get() << "TRANSITION" << subres_cpu.last_usage->wanted_state << " " << target << "subres: " << subres << Log::endl; @@ -1053,6 +1140,7 @@ namespace HAL usage.point = point; usage.last_point = nullptr; usage.debug = false; + usage.suppressed = false; HAL::Debug::BarrierBreakpoints::check_usage(resource->name, subres, state); @@ -1143,6 +1231,7 @@ namespace HAL if (resource->get_heap_type() == HeapType::DEFAULT || resource->get_heap_type() == HeapType::RESERVED) { use_resource(resource); + batch_hazard_check(resource, target_state); visit_subres(info, [&](const HAL::Resource::ptr&, UINT subres) { use_resource(resource); resource->get_state_manager().transition(this, target_state, subres); @@ -1161,44 +1250,6 @@ namespace HAL } -#ifdef PRETRANSITIONS_FIX - std::shared_ptr Transitions::fix_pretransitions() - { - PROFILE(L"fix_pretransitions"); - - HAL::Barriers result(CommandListType::DIRECT); - - CommandListType transition_type = type; - - { - PROFILE(L"processing resources"); - - - for (auto& r : need_check_transitions) - { - auto res_type = r->get_state_manager().process_transitions(result, this); - - transition_type = Merge(transition_type, res_type); - } - - } - if (result) - { - PROFILE(L"creating list"); - auto cmd = static_cast(this); - - - auto transition_list = (static_cast(this)->get_device().get_queue(transition_type)->get_transition_list()); - transition_list->compiler.set_name(L":Transitions"); - transition_list->create_transition_list(*cmd->frame_resources, result); - return transition_list; - } - return nullptr; - } -#endif - - - void Transitions::transition(const Resource::ptr& resource, ResourceState to, UINT subres) { transition(resource.get(), to, subres); @@ -1228,6 +1279,7 @@ namespace HAL if (resource->get_heap_type() == HeapType::DEFAULT || resource->get_heap_type() == HeapType::RESERVED) { use_resource(resource); + batch_hazard_check(resource, to); const_cast(resource)->get_state_manager().transition(this, to, subres); } } @@ -1608,6 +1660,8 @@ namespace HAL { transition_count = 0; pool_used = 0; + open_op = BarrierSync::NONE; + current_batch_id = 0; tracked_resources.reserve(512); used_resources.reserve(256); create_usage_point(BarrierSync::NONE, false); @@ -1620,6 +1674,7 @@ namespace HAL used_resources.clear(); need_check_transitions.clear(); transitions_compiled = false; + open_op = BarrierSync::NONE; } void SignatureDataSetter::commit_tables(BarrierSync operation, UsedSlots* slots) diff --git a/sources/HAL/HAL.CommandList.ixx b/sources/HAL/HAL.CommandList.ixx index df530a86..5d82de2a 100644 --- a/sources/HAL/HAL.CommandList.ixx +++ b/sources/HAL/HAL.CommandList.ixx @@ -80,6 +80,30 @@ export{ std::set need_check_transitions; void create_usage_point(BarrierSync operation, bool end = true); + // --- Operation batching ------------------------------------------- + // Consecutive ops of the same class share ONE usage point (one + // barrier group) instead of each bracketing itself. open_op is the + // currently open batch's class (NONE = none open). current_batch_id + // is the OP-CLASS batch epoch — advanced ONLY by begin_op (op-class + // change), NOT by intra-batch hazard splits, so a resource touched + // anywhere in the op-class batch stays detectable across splits. Each + // resource's per-list state stamps batch_touch_id/state, so the hazard + // check reads the resource directly (no parallel map): if it was + // already touched in THIS epoch with a different, non-mergeable state, + // the batch is split. A different op class closes the batch (begin_op); + // list end closes it (close_op). + BarrierSync open_op = BarrierSync::NONE; + uint current_batch_id = 0; + + void begin_op(BarrierSync op); + void batch_hazard_check(const HAL::Resource* resource, ResourceState to); + public: + // Close the open op-batch. Called at list end (compile_transitions) + // and by the FrameGraph before it appends cross-pass transitions, so + // those land after the last op instead of inside its batch. + void close_op(); + protected: + public://temporarily to allow transition into read mode void transition(const HAL::Resource* resource, ResourceState state, UINT subres = ALL_SUBRESOURCES); void transition(const HAL::Resource::ptr& resource, ResourceState state, UINT subres = ALL_SUBRESOURCES); @@ -95,16 +119,17 @@ export{ HAL::UsagePoint* get_last_usage_point(); void use_resource(const HAL::Resource* resource); + + // Resources this list touched (populated by use_resource). Used by + // link_list_groups to find resources shared between lists that land in + // the same ExecuteCommandLists scope. + const std::vector& get_used_resources() const { return used_resources; } public: void alias_begin(HAL::Resource*); void alias_end(HAL::Resource*); -#ifdef PRETRANSITIONS_FIX - std::shared_ptr fix_pretransitions(); -#endif - void transition_present(const HAL::Resource* resource_ptr); void transition(const ResourceInfo& info, BarrierSync operation = BarrierSync::NONE); @@ -252,7 +277,9 @@ export{ template void pre_command(T& context, BarrierSync operation, UsedSlots* slots = nullptr) { - create_usage_point(operation); + // Operation batching: open (or continue) a batch of this class + // instead of bracketing every op with its own usage point. + begin_op(operation); if constexpr (compute || graphics) { if constexpr (Debug::GfxDebug) setup_debug(&context); @@ -263,7 +290,9 @@ export{ template void post_command(T& context, BarrierSync operation) { - create_usage_point(operation, false); + // Leave the batch OPEN — it is closed lazily by the next op of a + // different class (begin_op) or at list end (close_op). A hazard + // on a shared resource splits it (see batch_hazard_check). if constexpr (Debug::GfxDebug) if constexpr (compute || graphics) print_debug(); } diff --git a/sources/HAL/HAL.CommandListRecorder.ixx b/sources/HAL/HAL.CommandListRecorder.ixx index b61e705d..7476cc9c 100644 --- a/sources/HAL/HAL.CommandListRecorder.ixx +++ b/sources/HAL/HAL.CommandListRecorder.ixx @@ -47,6 +47,11 @@ export namespace HAL HAL::ResourceState after; uint subres = 0; HAL::BarrierFlags flags = HAL::BarrierFlags::NONE; + // Opaque per-instance id (the source Resource address). A recreate() + // makes a NEW Resource object with the SAME name, so the debugger uses + // this to tell old/new instances apart and separate them. Never + // dereferenced — identity comparison only. + uint64 resource_id = 0; }; CommandType type = CommandType::Func; diff --git a/sources/HAL/HAL.Debug.ixx b/sources/HAL/HAL.Debug.ixx index f92b7c41..8034c4fd 100644 --- a/sources/HAL/HAL.Debug.ixx +++ b/sources/HAL/HAL.Debug.ixx @@ -10,6 +10,11 @@ export namespace HAL { constexpr bool RunForPix = false; + // Operation batching. Flip false to fall back to one usage point per op + // (near the pre-batching behavior) — batched vs unbatched output must be + // identical; any difference is a hazard-detection bug. + inline bool EnableOpBatching = true; + #ifdef DEV constexpr bool GfxDebug = !RunForPix&&false; constexpr bool ValidationErrors = !RunForPix&&true; diff --git a/sources/HAL/HAL.Device.cpp b/sources/HAL/HAL.Device.cpp index 4b84aaa5..51141ff8 100644 --- a/sources/HAL/HAL.Device.cpp +++ b/sources/HAL/HAL.Device.cpp @@ -30,6 +30,57 @@ namespace HAL return list; } + void Device::register_promote(std::shared_ptr resource) + { + std::lock_guard g(upload_mutex); + pending_promotes.push_back(std::move(resource)); + } + + void Device::flush_uploads() + { + std::vector> batch; + { + std::lock_guard g(upload_mutex); + if (pending_promotes.empty()) return; + batch.swap(pending_promotes); + } + + auto list = get_upload_list(); + + std::vector> promoted; + promoted.reserve(batch.size()); + + for (auto& r : batch) + { + if (r->get_desc().is_buffer()) continue; // buffers carry no layout + + auto desired = r->get_state_manager().get_desired_state(); + // Only promote resources that actually have a read state. + if (!check(desired.layout & (TextureLayout::SHADER_RESOURCE | TextureLayout::UNORDERED_ACCESS))) + continue; + + // The resource is physically in COMMON (its data upload already waited on + // the DStorage fence). Record COMMON -> read: the seed reads gpu_state + // (still COMMON) so a REAL transition is emitted. Set initial_layout to + // the read layout NOW so this list's own end-of-list decay is a no-op + // (it decays to initial_layout). gpu_state is pinned to the read state + // AFTER execute, once the seed has been consumed by compile. + list->transition(r.get(), desired); + r->get_state_manager().initial_layout = desired.layout; + promoted.push_back(r); + } + + // Submit on the DIRECT queue and wait. This runs on the MAIN thread at + // frame-begin (unlike the old worker-thread finish_upload that deadlocked), + // and only when there are pending promotes — a one-time cost after loads. + // Waiting keeps the list alive until the GPU is done and orders the + // transition ahead of this frame's rendering. + list->execute_and_wait(); + + for (auto& r : promoted) + r->get_state_manager().set_resting_state(r->get_state_manager().get_desired_state().layout); + } + HAL::Queue::ptr& Device::get_queue(CommandListType type) { return queues[type]; diff --git a/sources/HAL/HAL.Device.ixx b/sources/HAL/HAL.Device.ixx index f51430bc..125e94c3 100644 --- a/sources/HAL/HAL.Device.ixx +++ b/sources/HAL/HAL.Device.ixx @@ -22,6 +22,7 @@ export namespace HAL class GPUTimeProfiler; class GPUEntityStorageInterface; class CommandAllocator; + class Resource; class Device : public API::Device, public TypedObject { friend class API::Device; @@ -53,7 +54,23 @@ export namespace HAL enum_array>> command_allocators; + // Deferred upload-promote batch. Freshly-uploaded resources register here + // (thread-safe, from asset-loading worker threads); flush_uploads() drains + // the batch on the main thread at frame-begin, firing ONE direct-queue + // transition list that moves them from COPY_QUEUE/COMMON into their + // canonical read state. Batching avoids a per-asset execute_and_wait, which + // deadlocks when called from worker threads. + std::mutex upload_mutex; + std::vector> pending_promotes; + public: + // Register a just-uploaded resource for promotion to its read state. + // Thread-safe; records only — no GPU work here. + void register_promote(std::shared_ptr resource); + // Drain the promote batch on the main thread (call at frame-begin, before + // passes record). Submits one transition list on the DIRECT queue. + void flush_uploads(); + Device(HAL::DeviceDesc desc); virtual ~Device(); diff --git a/sources/HAL/HAL.Queue.cpp b/sources/HAL/HAL.Queue.cpp index 50ef7b20..a8d4efdd 100644 --- a/sources/HAL/HAL.Queue.cpp +++ b/sources/HAL/HAL.Queue.cpp @@ -160,63 +160,24 @@ namespace HAL device.get_ds_queue().flush(); } - std::list transition_lists; for (auto& list : lists) { - -#ifdef PRETRANSITIONS_FIX - auto transition_list = (list)->fix_pretransitions(); - ASSERT(!transition_list); - if (transition_list) - { - transition_lists.emplace_back(transition_list); - PROFILE(L"execute_transitions"); - if (transition_list->get_type() == (list)->get_type()) - { - API::Queue::execute(&transition_list->get_compiled()); - API::Queue::execute(&list->compiler.get_list()); - } - else - { - // Need to request other queue to make a proper transition. - // It's OK, but better to avoid this - auto queue = device.get_queue(transition_list->get_type()); - auto waiter = queue->run_transition_list(last_executed_fence, transition_list.get()); - - API::Queue::gpu_wait(waiter); - API::Queue::execute(&list->compiler.get_list()); - } - - } - else -#endif - { - PROFILE(L"execute_simple"); - API::Queue::execute(&list->compiler.get_list()); - } - - - + PROFILE(L"execute_simple"); + API::Queue::execute(&list->compiler.get_list()); } API::Queue::signal(commandListCounter, fence_value); FenceWaiter execution_fence{ &commandListCounter, fence_value, type}; - gpu_wait_thread.enqueue([lists, transition_lists, execution_fence, this]() + gpu_wait_thread.enqueue([lists, execution_fence, this]() { if (!device.alive) return; PROFILE(L"on_execute wait"); execution_fence.wait(); - - - PROFILE(L"on_execute process"); - for (auto& transition_list : transition_lists) - transition_list->on_execute(); - for (auto& list : lists) { auto& updates = (list)->tile_updates; diff --git a/sources/HAL/HAL.Resource.Texture.ixx b/sources/HAL/HAL.Resource.Texture.ixx index d613c932..bc118fbd 100644 --- a/sources/HAL/HAL.Resource.Texture.ixx +++ b/sources/HAL/HAL.Resource.Texture.ixx @@ -61,6 +61,12 @@ export{ Resource::write(load_subresources[i]); } + + // Strict promote (deferred): register this freshly-uploaded + // texture so the UploadManager transitions it out of COMMON + // into its canonical read state at the next frame-begin. + if (!desc.is_virtual()) + device.register_promote(get_ptr()); diff --git a/sources/HAL/HAL.ResourceStates.cpp b/sources/HAL/HAL.ResourceStates.cpp index 553a833e..23c23c37 100644 --- a/sources/HAL/HAL.ResourceStates.cpp +++ b/sources/HAL/HAL.ResourceStates.cpp @@ -83,19 +83,6 @@ namespace HAL return barriers; } - void Barriers::validate() - { -#ifdef _DEV - for (int j = 0; j < native.size() - 1; j++) - { - if (native.back().Type == native[j].Type) - if (native.back().ResourceUsage.pResource == native[j].ResourceUsage.pResource) - if (native.back().ResourceUsage.Subresource == native[j].ResourceUsage.Subresource) - ASSERT(false); - } -#endif - } - void Barriers::transition(const Resource* resource, ResourceState before, ResourceState after, UINT subres, BarrierFlags flags) { ASSERT(resource); @@ -107,8 +94,6 @@ namespace HAL ASSERT(check(resource->get_desc().Flags & ResFlags::Raytracing)); barriers.emplace_back(Barrier{ const_cast(resource), before, after, subres, flags }); - - validate(); } @@ -135,6 +120,7 @@ namespace HAL { need_discard = false; used = false; + skip_end_decay = false; first_usage = nullptr; last_usage = nullptr; } @@ -184,9 +170,37 @@ namespace HAL // --- SubResourcesCPU --- + ResourceListStateCPU& SubResourcesCPU::slot(UINT id) + { + return (uniform || id == ALL_SUBRESOURCES) ? uniform_state : subres[id]; + } + + const ResourceListStateCPU& SubResourcesCPU::slot(UINT id) const + { + return (uniform || id == ALL_SUBRESOURCES) ? uniform_state : subres[id]; + } + + void SubResourcesCPU::expand(UINT count) + { + if (!uniform) return; + + // Materialize per-subres slots from the shared uniform chain: each slot + // copies uniform_state's first/last_usage (sharing the history nodes, + // which carry subres == ALL_SUBRESOURCES). The next per-subres + // transition forks its own node after that shared history — correct, at + // the cost of one extra barrier for that subresource on the frame it + // first diverges (design A). + if (subres.size() < count) subres.resize(count); + for (UINT i = 0; i < count; i++) subres[i] = uniform_state; + uniform = false; + } + void SubResourcesCPU::reset() { used = false; + uniform = true; + batch_touch_id = 0; // stale batch id from a prior frame must not match + uniform_state.reset(); for (auto& s : subres) s.reset(); } @@ -195,12 +209,13 @@ namespace HAL { CommandListType type = CommandListType::COPY; - for (auto& cpu : subres) - { - if (!cpu.used) continue; - auto state = cpu.last_usage->wanted_state; - type = Merge(type, state.get_best_cmd_type()); - } + auto one = [&](const ResourceListStateCPU& cpu) { + if (!cpu.used) return; + type = Merge(type, cpu.last_usage->wanted_state.get_best_cmd_type()); + }; + + if (uniform) one(uniform_state); + else for (auto& cpu : subres) one(cpu); return type; } @@ -209,34 +224,35 @@ namespace HAL { CommandListType type = CommandListType::COPY; - for (auto& cpu : subres) - { - if (!cpu.used) continue; - auto state = cpu.first_usage->wanted_state; - type = Merge(type, state.get_best_cmd_type()); - } + auto one = [&](const ResourceListStateCPU& cpu) { + if (!cpu.used) return; + type = Merge(type, cpu.first_usage->wanted_state.get_best_cmd_type()); + }; + + if (uniform) one(uniform_state); + else for (auto& cpu : subres) one(cpu); return type; } const ResourceListStateCPU& SubResourcesCPU::get_subres_state(UINT id) const { - return subres[id]; + return slot(id); } ResourceListStateCPU& SubResourcesCPU::get_subres_state(UINT id) { - return subres[id]; + return slot(id); } ResourceUsage* SubResourcesCPU::get_first_usage(UINT id) const { - return subres[id].first_usage; + return slot(id).first_usage; } ResourceUsage* SubResourcesCPU::get_last_usage(UINT id) const { - return subres[id].last_usage; + return slot(id).last_usage; } ResourceState SubResourcesCPU::get_first_state(UINT id) const @@ -332,12 +348,14 @@ namespace HAL const ResourceListStateGPU& SubResourcesGPU::get_subres_state(UINT id) const { - return subres[id]; + // ALL_SUBRESOURCES resolves to subresource 0 as the representative — used + // by the uniform CPU fast path, where the GPU layout is uniform anyway. + return subres[id == ALL_SUBRESOURCES ? 0 : id]; } ResourceListStateGPU& SubResourcesGPU::get_subres_state(UINT id) { - return subres[id]; + return subres[id == ALL_SUBRESOURCES ? 0 : id]; } void SubResourcesGPU::merge(SubResourcesCPU& other) @@ -372,6 +390,23 @@ namespace HAL return gpu_state; } + ResourceState ResourceStateManager::get_desired_state() const + { + auto flags = resource->get_desc().Flags; + if (check(flags & ResFlags::ShaderResource)) return ResourceStates::SHADER_RESOURCE; + if (check(flags & ResFlags::UnorderedAccess)) return ResourceStates::UNORDERED_ACCESS; + // No read usage declared — leave it in the copy-destination state. + return ResourceStates::COPY_DEST; + } + + void ResourceStateManager::set_resting_state(TextureLayout layout) + { + initial_layout = layout; + for (auto& e : gpu_state.subres) + e.layout = layout; + virgin = false; + } + void ResourceStateManager::init_subres(int count, TextureLayout layout) { initial_layout = layout; @@ -386,9 +421,15 @@ namespace HAL states.set_init_func([count](SubResourcesCPU& state) { state.used = false; + state.uniform = true; // start each list in the uniform fast path + state.batch_touch_id = 0; + state.uniform_state.reset(); state.subres.resize(count); for (auto& e : state.subres) + { e.used = false; // prevents set_cpu_state_first from using stale layout + e.skip_end_decay = false; + } }); } @@ -405,49 +446,6 @@ namespace HAL } -#ifdef PRETRANSITIONS_FIX - CommandListType ResourceStateManager::process_transitions(Barriers& target, Transitions* list) const - { - CommandListType cmd_type = CommandListType::COPY; - if (!resource) return cmd_type; - - ASSERT(!check(resource->get_desc().Flags & ResFlags::DisableStateTracking)); - - if (resource->get_desc().is_buffer()) - return cmd_type; - - auto& cpu_state = get_state((list)); - - for (int i = 0; i < gpu_state.subres.size(); i++) - { - auto& gpu = gpu_state.get_subres_state(i); - auto& cpu = cpu_state.get_subres_state(i); - - if (!cpu.used) continue; - - auto first_usage = cpu_state.get_first_usage(i); - auto first_state = first_usage->wanted_state; - - if (first_state != ResourceStates::UNKNOWN) - if (gpu.layout != first_state.layout) - { - auto from = ResourceStates::NO_ACCESS; - from.layout = gpu.layout; - - target.transition(resource, from, first_state, i); - - cmd_type = Merge(cmd_type, from.get_best_cmd_type()); - cmd_type = Merge(cmd_type, first_state.get_best_cmd_type()); - } - - cpu.check_valid(resource); - } - - gpu_state.set_cpu_state(cpu_state); - - return cmd_type; - } -#endif void ResourceStateManager::transition(Transitions* list, ResourceState state, unsigned int s) const { @@ -474,15 +472,41 @@ namespace HAL ASSERT(state.is_valid(resource->get_type())); + // Captured before the per-subres loop so every subresource of this + // transition sees the same virginity (the flag flips once, below). + const bool was_virgin = virgin; + + // `subres` here is the value stamped onto the emitted usage node: a real + // subresource index in expanded mode, or ALL_SUBRESOURCES in uniform mode + // (one node → one all-subresources barrier). auto transition_one = [&](UINT subres) { - auto& subres_cpu = cpu_state.get_subres_state(subres); + auto& subres_cpu = cpu_state.slot(subres); if (!subres_cpu.used) { if (!check(resource->get_desc().Flags & ResFlags::DisableStateTracking) && resource->get_desc().is_texture()) { + // Seed node = the state the resource is IN at this list's first + // touch. Non-FG virgin resource whose first-ever use is a WRITE + // (incl. COPY_DEST upload) → UNKNOWN so compile_transitions + // emits a DISCARD activation (R4): contents are meaningless and + // about to be overwritten. A read-first virgin resource is + // initialized out-of-band, so must not be discarded. + // + // Otherwise seed from the TRACKED layout (gpu_state), not the + // creation-time initial_layout. initial_layout is only a guess + // and is wrong for any resource whose layout has since moved — + // e.g. an adopted history-prev, which carries the layout last + // frame's `current` ended in. Seeding the guess emitted barriers + // whose LayoutBefore disagreed with the real layout + // (D3D12 #1334 INCOMPATIBLE_BARRIER_LAYOUT). gpu_state equals + // initial_layout for a freshly created resource, so this is a + // strict improvement. auto target = ResourceStates::NO_ACCESS; - target.layout = initial_layout; + if (!resource->frame_graph_managed && was_virgin && state.has_write_bits()) + target = ResourceStates::UNKNOWN; + else + target.layout = gpu_state.get_subres_state(subres).layout; subres_cpu.used = true; subres_cpu.first_usage = subres_cpu.last_usage = (list)->add_usage((resource), subres, target); @@ -518,13 +542,29 @@ namespace HAL }; - if(s!=ALL_SUBRESOURCES) + if (s != ALL_SUBRESOURCES) { + // Subresource-scoped touch: leave the uniform mode (if any) and track + // this subresource independently from here on. + if (cpu_state.uniform) + cpu_state.expand((UINT)gpu_state.subres.size()); transition_one(s); } + else if (cpu_state.uniform) + { + // Whole-resource touch while still uniform: one shared node. + transition_one(ALL_SUBRESOURCES); + } else - for (int i = 0; i < gpu_state.subres.size(); i++) - transition_one(i); + { + for (int i = 0; i < gpu_state.subres.size(); i++) + transition_one(i); + } + + // First use has now been recorded — later uses preserve contents + // (no more DISCARD). Re-discarding would only ever be safe anyway, but + // non-FG resources persist data across frames, so this must flip once. + virgin = false; } @@ -578,8 +618,17 @@ namespace HAL } }; - for (int i = 0; i < gpu_state.subres.size(); i++) merge_one(i); - for (int i = 0; i < gpu_state.subres.size(); i++) transition_one(i); + // Uniform CPU state → one pass stamping ALL_SUBRESOURCES; else per-subres. + if (cpu_state.uniform) + { + merge_one(ALL_SUBRESOURCES); + transition_one(ALL_SUBRESOURCES); + } + else + { + for (int i = 0; i < gpu_state.subres.size(); i++) merge_one(i); + for (int i = 0; i < gpu_state.subres.size(); i++) transition_one(i); + } if (updated) { @@ -651,8 +700,17 @@ namespace HAL cpu.check_valid(resource); }; - for (int i = 0; i < gpu_state.subres.size(); i++) merge_one(i); - for (int i = 0; i < gpu_state.subres.size(); i++) transition_one(i); + // Uniform CPU state → one pass stamping ALL_SUBRESOURCES; else per-subres. + if (cpu_state.uniform) + { + merge_one(ALL_SUBRESOURCES); + transition_one(ALL_SUBRESOURCES); + } + else + { + for (int i = 0; i < gpu_state.subres.size(); i++) merge_one(i); + for (int i = 0; i < gpu_state.subres.size(); i++) transition_one(i); + } if (updated) { @@ -680,13 +738,27 @@ namespace HAL auto& cpu_state = get_state((from)); - for (int i = 0; i < gpu_state.subres.size(); i++) + if (cpu_state.uniform) { - if (gpu_state.subres[i].layout == TextureLayout::NONE) continue; + // Uniform CPU state ⟺ uniform GPU handoff: subresource 0 represents + // all. One ALL_SUBRESOURCES transition keeps the resource uniform. + if (gpu_state.subres[0].layout != TextureLayout::NONE) + { + auto target = ResourceStates::NO_ACCESS; + target.layout = gpu_state.subres[0].layout; + transition(from, target, ALL_SUBRESOURCES); + } + } + else + { + for (int i = 0; i < gpu_state.subres.size(); i++) + { + if (gpu_state.subres[i].layout == TextureLayout::NONE) continue; - auto target = ResourceStates::NO_ACCESS; - target.layout = gpu_state.subres[i].layout; - transition(from, target, i); + auto target = ResourceStates::NO_ACCESS; + target.layout = gpu_state.subres[i].layout; + transition(from, target, i); + } } if (!cpu_state.used) @@ -713,6 +785,109 @@ namespace HAL } } + void ResourceStateManager::chain_lists(Transitions* from, Transitions* to) const + { + if (resource->get_desc().is_buffer()) return; + + auto& prev = get_state(from); + auto& next = get_state(to); + + if (!prev.used || !next.used) return; + + // A SYNC_NONE / NO_ACCESS node: the first-touch seed planted by + // transition_one, a zero-transition prepended by prepare_state, or a + // release appended by prepare_after_state. A list can carry several of + // them at either end, around its real usages. + auto is_none = [](const ResourceUsage* u) + { + return u->wanted_state.operation == BarrierSync::NONE + && u->wanted_state.access == BarrierAccess::NO_ACCESS; + }; + + auto one = [&](UINT subres) + { + auto* last = prev.slot(subres).last_usage; + auto* first = next.slot(subres).first_usage; + + if (!last || !first) return; + + // Walk forward over `to`'s SYNC_NONE prefix to its first REAL usage. + auto* target = first; + while (is_none(target) && target->next_usage) + target = target->next_usage; + + // `to` has NO real usage — only SYNC_NONE reconciliation nodes + // (redundant seed + release from prepare_state/prepare_after_state). If + // `from` really accessed the resource, `to`'s first node re-declares the + // state with SyncBefore=NONE AFTER that access → #1417. Walk `from` back + // to its last REAL usage and hand it to `to`'s first node so the barrier + // carries a real SyncBefore; suppress `from`'s own end release (the + // resource stays live for `to`). The remaining `to` nodes now chain off a + // real predecessor instead of a SYNC_NONE seed. + if (is_none(target)) + { + while (is_none(last) && last->prev_usage) + last = last->prev_usage; + + if (!is_none(last)) + { + // `to`'s LAST node is its exit release; the earlier none nodes are + // redundant re-seeds. Suppress the seeds (so none emit a bare + // SyncBefore=NONE after the access) and hand the release `from`'s + // last real usage as predecessor so it carries real sync. Suppress + // `from`'s own end release — the resource stays live until here. + auto* lastnode = first; + while (lastnode->next_usage) lastnode = lastnode->next_usage; + + for (auto* u = first; u != lastnode; u = u->next_usage) + u->suppressed = true; + + if (!lastnode->prev_usage || is_none(lastnode->prev_usage)) + lastnode->prev_usage = last; + + prev.slot(subres).skip_end_decay = true; + } + return; + } + + // Walk back over `from`'s SYNC_NONE suffix to its last REAL usage. + // Chaining onto a release node would emit `SyncBefore == NONE`, which + // is precisely the thing being avoided. + while (is_none(last) && last->prev_usage) + last = last->prev_usage; + + if (is_none(last)) return; + + // Replace only a missing or SYNC_NONE predecessor; never clobber a + // real one (already chained, or a genuine in-list transition). + if (target->prev_usage && !is_none(target->prev_usage)) return; + + // Everything bypassed on both sides must emit nothing: a suffix node + // would publish SyncAfter == NONE (making the resource untouchable for + // the rest of the scope) and a non-leading prefix node would publish + // SyncBefore == NONE after it had already been touched. + for (auto* u = first; u != target; u = u->next_usage) + u->suppressed = true; + + for (auto* u = prev.slot(subres).last_usage; u != last; u = u->prev_usage) + u->suppressed = true; + + target->prev_usage = last; + + // `from` must not run the generic end-of-list release for THIS + // subresource either — a later list in the same ECL scope still + // touches it. Subresources that were not carried forward keep their + // release, so they still return to `initial_layout`. + prev.slot(subres).skip_end_decay = true; + }; + + if (prev.uniform && next.uniform) + one(ALL_SUBRESOURCES); + else + for (UINT i = 0; i < gpu_state.subres.size(); i++) + one(i); + } + void ResourceStateManager::connect(Transitions* from, Transitions* to) { if (resource->get_desc().is_buffer()) diff --git a/sources/HAL/HAL.ResourceStates.ixx b/sources/HAL/HAL.ResourceStates.ixx index 2f895478..31b18562 100644 --- a/sources/HAL/HAL.ResourceStates.ixx +++ b/sources/HAL/HAL.ResourceStates.ixx @@ -53,7 +53,6 @@ export { std::vector barriers; - void validate(); CommandListType type; public: @@ -81,6 +80,15 @@ export UsagePoint* last_point = nullptr; bool debug = false; + + // Set by ResourceStateManager::chain_lists when this usage is a + // SYNC_NONE list-boundary marker (a seed, a prepare_state zero + // transition, or a prepare_after_state release) that got bypassed + // because the neighbouring list is in the same ExecuteCommandLists + // group. compile_transitions must not emit a barrier for it: inside + // one ECL scope a SyncBefore/SyncAfter of NONE is illegal once the + // resource has been touched (#1417). + bool suppressed = false; }; struct UsagePoint @@ -104,6 +112,17 @@ export bool used = false; bool need_discard = false; + // A LATER list in the same ExecuteCommandLists group also uses THIS + // subresource, so its state was handed over directly (chain_lists) and + // this list must not emit the end-of-list release to + // NO_ACCESS/SYNC_NONE — inside one ECL + // scope that makes it untouchable afterwards (D3D12 #1417). Tracked + // per subresource: chaining is per subresource, and a resource whose + // mips are only partially carried forward must still release the rest, + // or it never returns to `initial_layout` and the next group's seed + // mismatches (D3D12 #1334). + bool skip_end_decay = false; + ResourceState get_first_usage(); ResourceState get_usage(); void reset(); @@ -123,6 +142,29 @@ export bool used = false; + // Operation batching: the id of the batch this resource was last + // touched in, and the state it was transitioned to. The command + // list compares batch_touch_id against its monotonic current_batch_id + // to detect a same-batch reuse that needs a split. Reset per frame. + uint batch_touch_id = 0; + ResourceState batch_touch_state; + + // Uniform fast path: while `uniform`, all subresources are tracked as + // one chain (`uniform_state`) whose usage nodes carry subres == + // ALL_SUBRESOURCES, so a full-resource transition costs one node/one + // barrier instead of one per mip/array/plane. The first subresource- + // scoped transition calls expand(): each per-subres slot is seeded + // from uniform_state (sharing the chain history) and the resource is + // per-subres for the rest of the list. slot() resolves either mode, so + // read helpers work unchanged; only mutating loops must run once in + // uniform mode (see transition/prepare_state). + bool uniform = true; + ResourceListStateCPU uniform_state; + + ResourceListStateCPU& slot(UINT id); + const ResourceListStateCPU& slot(UINT id) const; + void expand(UINT count); + void reset(); CommandListType get_best_list_type_last(); CommandListType get_best_list_type_first(); @@ -165,6 +207,14 @@ export mutable SubResourcesGPU gpu_state; + protected: + // Non-FG resources: true until their first-ever use. The first + // activation of a virgin resource discards (UNDEFINED->state) since + // its contents are meaningless (fresh/placed memory) — point R4. + // Flips false permanently after the first transition; later uses + // preserve contents. FG resources ignore this (own activation path). + mutable bool virgin = true; + public: virtual ~ResourceStateManager() = default; TextureLayout initial_layout; @@ -174,6 +224,19 @@ export SubResourcesGPU copy_gpu() const; void init_subres(int count, TextureLayout layout); + // The canonical READ state this resource should rest in after upload, + // derived from its declared usage flags (ShaderResource -> SHADER_RESOURCE, + // etc.). Used by the UploadManager to transition a freshly-uploaded + // resource out of COPY_DEST/COMMON into a strictly-defined read state. + ResourceState get_desired_state() const; + + // Pin the persistent resting state to `layout`. Called once by the + // UploadManager's post-upload transition so every later command list + // seeds this resource in its read state (a no-op) and never decays it + // back to COMMON. This is the strict replacement for the old + // gpu_state-as-first-list-link behaviour. + void set_resting_state(TextureLayout layout); + SubResourcesCPU& get_cpu_state(Transitions* list) const; void stop_using(Transitions* list, UINT subres) const; @@ -181,10 +244,6 @@ export bool is_used(Transitions* list) const; -#ifdef PRETRANSITIONS_FIX - CommandListType process_transitions(Barriers& target, Transitions* list) const; -#endif - void transition(Transitions* list, ResourceState state, unsigned int subres) const; void prepare_state(Transitions* from, const SubResourcesGPU& subres) const; @@ -197,6 +256,15 @@ export void connect(Transitions* from, Transitions* to); + // `from` and `to` are two lists that + // will be submitted inside ONE ExecuteCommandLists scope, `from` + // first. Gives `to`'s first real usage `from`'s last usage as its + // predecessor — so the emitted barrier carries a real SyncBefore + // instead of the SYNC_NONE seed — and marks `from` to skip its + // end-of-list release. Result: one SYNC_NONE entry and one + // SYNC_NONE exit per resource per group instead of one per list. + void chain_lists(Transitions* from, Transitions* to) const; + }; diff --git a/sources/Modules/d3d12/d3d12.ixx b/sources/Modules/d3d12/d3d12.ixx index c59a4035..b302d7a2 100644 --- a/sources/Modules/d3d12/d3d12.ixx +++ b/sources/Modules/d3d12/d3d12.ixx @@ -172,6 +172,7 @@ export using ID3D12Debug = ::ID3D12Debug; using ID3D12Debug1 = ::ID3D12Debug1; using ID3D12InfoQueue = ::ID3D12InfoQueue; + using ID3D12InfoQueue1 = ::ID3D12InfoQueue1; // RegisterMessageCallback using ID3D12DeviceRemovedExtendedDataSettings = ::ID3D12DeviceRemovedExtendedDataSettings; using ID3D12DeviceRemovedExtendedData = ::ID3D12DeviceRemovedExtendedData; using ID3D12WorkGraphProperties = ::ID3D12WorkGraphProperties; diff --git a/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp b/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp index 7bf81bbd..30ff3da0 100644 --- a/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp +++ b/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp @@ -611,14 +611,22 @@ VoxelGI::VoxelGI(Scene::ptr& scene) :scene(scene), VariableContext(L"VoxelGI") // Only the *Prev is uninitialized on the first frame (no history yet); on // resize it adopts a valid old-size resource (is_new == false) and the shader // remaps via GetDimensions. The current is written fresh each frame. - if (data.VoxelIndirectFiltered.is_new()) - command_list->clear_uav(gi_filtered.rwTexture2D, vec4(0, 0, 0, 0)); - if (data.VoxelIndirectFilteredPrev.is_new()) - command_list->clear_uav(gi_prev.rwTexture2D, vec4(0, 0, 0, 0)); - if (data.VoxelIndirectNoise.is_new()) - command_list->clear_uav(noisy_output.rwTexture2D, vec4(0, 0, 0, 0)); - if (data.VoxelFramesCount.is_new()) - command_list->clear_uav(frames_count.rwTexture2D, vec4(0, 0, 0, 0)); + { + PROFILE_GPU(L"clear"); + if (data.VoxelIndirectFiltered.is_new()) + command_list->clear_uav(gi_filtered.rwTexture2D, vec4(0, 0, 0, 0)); + // NOTE: the prev is a READ-ONLY history resource — clearing it here via + // clear_uav was an illegal UAV write on a read-declared resource (the + // FrameGraph folds it into the merged read state, clobbering the clear's + // UAV transition -> D3D12 #1334). A fresh prev (first frame, no carried + // history) is instead rejected shader-side in get_history(), which + // zeroes the accumulation speed when the stored frame count is not a + // valid normalized value. + if (data.VoxelIndirectNoise.is_new()) + command_list->clear_uav(noisy_output.rwTexture2D, vec4(0, 0, 0, 0)); + if (data.VoxelFramesCount.is_new()) + command_list->clear_uav(frames_count.rwTexture2D, vec4(0, 0, 0, 0)); + } auto& compute = command_list->get_compute(); @@ -650,7 +658,10 @@ VoxelGI::VoxelGI(Scene::ptr& scene) :scene(scene), VariableContext(L"VoxelGI") } if (use_rtx_flag) + { + PROFILE_GPU(L"noise_rtx"); RTX::get().render(compute, sceneinfo.scene->raytrace_scene, noisy_output.get_size()); + } else { PROFILE_GPU(L"noise"); @@ -829,6 +840,8 @@ VoxelGI::VoxelGI(Scene::ptr& scene) :scene(scene), VariableContext(L"VoxelGI") { auto& command_list = context.get_list(); + bool use_rtx_flag = RenderSystem::get().device().is_rtx_supported() && (bool)use_rtx; + MeshRenderContext::ptr mesh_ctx(new MeshRenderContext()); GBuffer gbuffer = GBufferViewDesc::actualize(data.gbuffer); auto sky_cubemap_filtered = *data.sky_cubemap_filtered; @@ -838,7 +851,7 @@ VoxelGI::VoxelGI(Scene::ptr& scene) :scene(scene), VariableContext(L"VoxelGI") auto& caminfo = context.graph->get_context(); auto& sceneinfo = context.graph->get_context(); - if (use_rtx) + if (use_rtx_flag) command_list->get_compute().set_signature(RTX::get().rtx.m_root_sig); mesh_ctx->current_time = 0; @@ -864,8 +877,9 @@ VoxelGI::VoxelGI(Scene::ptr& scene) :scene(scene), VariableContext(L"VoxelGI") context.graph->set_slot(SlotID::VoxelInfo, compute); - if (use_rtx) + if (use_rtx_flag) { + PROFILE_GPU(L"reflection_rtx"); { Slots::VoxelOutput output; output.GetNoise() = noisy_output.rwTexture2D; diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx b/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx index acc64e65..a5d2bd47 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx +++ b/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx @@ -695,11 +695,6 @@ public: }; std::vector history_links; - // Lifetime tracing (DEV): resources whose alloc/free/adopt/carry events are - // logged with heap ranges and pass call ids. Seed from a pass setup, e.g. - // builder.debug_lifetime.insert(data.VoxelIndirectFiltered.id); - std::set debug_lifetime; - // Register `prev` as the previous-frame alias of `current`. Idempotent; // safe to call every frame from a pass setup. void link_history(ResourceID current, ResourceID prev); @@ -876,6 +871,13 @@ public: void create_resources(); void process_transitions(); void process_fences(); + + // Mirror commit_command_lists' batching to find the sets of lists that + // will be submitted in ONE ExecuteCommandLists, and chain + // resource state across the boundaries inside each set. Must run after + // process_transitions (all usages recorded) and process_fences (which + // decides where batches are flushed), and before compile_lists. + void link_list_groups(); void compile_lists(); void reset(); diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp index 140eaeed..7e2efda1 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp @@ -38,6 +38,10 @@ class FrameGraphTimelineCanvas : public dock_base bool put_fence = false; bool disabled = false; bool runs_alone = false; + // Which ExecuteCommandLists this pass is packed into (unique across queues, + // -1 for disabled). commit_command_lists submits one ECL per queue whenever + // a pass waits (flush before) or has put_fence (flush after). + int ecl_group = -1; // Cross-queue deps resolved at rebuild time — no raw pointers. std::vector> cross_queue_deps; // Copied from Pass::debug_commands during rebuild(); barrier_point == nullptr. @@ -373,6 +377,20 @@ class FrameGraphTimelineCanvas : public dock_base owner.dr(c, pc, px + PAD, py + PAD, owner.col_w() - 2.0f * PAD, PASS_H - 2.0f * PAD); if (!pass.disabled) { + // ExecuteCommandLists packing: a colour strip along the top, + // shared by all passes submitted in the same ECL. The strip is + // drawn edge-to-edge (no inter-column pad) so consecutive + // same-ECL passes merge into one continuous bar. + if (pass.ecl_group >= 0) + { + static const float4 ecl_pal[] = { + { 0.36f, 0.62f, 0.86f, 1.0f }, { 0.86f, 0.58f, 0.30f, 1.0f }, + { 0.45f, 0.76f, 0.48f, 1.0f }, { 0.78f, 0.47f, 0.78f, 1.0f }, + { 0.83f, 0.75f, 0.36f, 1.0f }, { 0.42f, 0.74f, 0.74f, 1.0f }, + }; + const float4& gc = ecl_pal[pass.ecl_group % (int)std::size(ecl_pal)]; + owner.dr(c, gc, px, py + PAD, owner.col_w(), 4.0f); + } if (pass.runs_alone) owner.dr(c, C_RUNS_ALONE, px + PAD, py + PASS_H - PAD - 3.0f, owner.col_w() - 2.0f * PAD, 3.0f); @@ -650,6 +668,7 @@ class FrameGraphTimelineCanvas : public dock_base info.queue == HAL::CommandListType::DIRECT ? "Direct" : "Compute")); add_row("ID: #" + std::to_string(info.call_id)); add_row(std::string("Fence: ") + (info.put_fence ? "yes" : "no")); + add_row("ECL: group " + std::to_string(info.ecl_group)); auto queue_name = [](HAL::CommandListType t) -> const char* { @@ -797,40 +816,90 @@ class FrameGraphTimelineCanvas : public dock_base } } + // Group by instance. A recreate() makes a new Resource under the same + // name, and instances INTERLEAVE in pass order — instance 1's alias_end + // can run in a later pass than instance 2's create. Stable-sort by each + // instance's first appearance so its barriers form one contiguous block + // (creation order preserved, pass order kept within the block), instead + // of the timeline flip-flopping between instances. + { + std::unordered_map id_order; + int next_order = 0; + for (auto& e : entries) + if (id_order.emplace(e.detail->resource_id, next_order).second) ++next_order; + std::stable_sort(entries.begin(), entries.end(), + [&](const BarrierEntry& a, const BarrierEntry& b) + { return id_order[a.detail->resource_id] < id_order[b.detail->resource_id]; }); + } + // Validate per-subresource continuity and cross-frame state. - // No ALL_SUBRESOURCES slot — the engine always emits individual barriers. + // + // Subresource granularity is NOT uniform across barriers: the engine + // tracks state per command list, and a resource can be uniform on one + // list (emitting a single ALL_SUBRESOURCES barrier — e.g. alias_end on + // a list that only frees it) yet expanded on another (per-subres + // barriers, sub=N). So ALL_SUBRESOURCES must reconcile with the + // per-subres slots rather than be treated as its own separate lane. { const bool is_persistent = track.is_static || track.is_passed; - std::unordered_map sub_state; - std::unordered_map first_idx; // subres -> first entry index + std::unordered_map sub_state; // per-subres current + std::optional state_all; // last ALL barrier's after (default for untracked subres) + bool any_seen = false; + uint64 cur_id = 0; // instance being validated for (size_t i = 0; i < entries.size(); ++i) { auto& e = entries[i]; const auto& bd = *e.detail; const uint sr = bd.subres; + const bool is_all = (sr == HAL::ALL_SUBRESOURCES); const bool has_discard = (static_cast(bd.flags) & static_cast(HAL::BarrierFlags::DISCARD)) != 0; - const bool is_first = (first_idx.find(sr) == first_idx.end()); - if (is_first) - first_idx[sr] = i; + // A recreate() makes a new Resource instance under the same name. + // Its lifetime is independent — reset continuity so the new + // instance validates from scratch (its first barrier is a fresh + // DISCARD, not a continuation of the old one). + if (bd.resource_id != cur_id) + { + cur_id = bd.resource_id; + sub_state.clear(); + state_all.reset(); + any_seen = false; + } + + // Current tracked state for this barrier's target. A specific + // subres uses its own slot, else the last ALL default. An ALL + // barrier takes any tracked slot as representative (they are + // uniform when an ALL barrier is legitimately emitted). + auto current = [&]() -> std::optional + { + if (!is_all) + { + auto it = sub_state.find(sr); + if (it != sub_state.end()) return it->second; + } + else if (!sub_state.empty()) + return sub_state.begin()->second; + return state_all; + }; + + const bool is_first = is_all ? !any_seen + : (sub_state.find(sr) == sub_state.end() && !state_all); if (is_first) { if (is_persistent) { // Persistent: must DISCARD or declare a real before-layout. - // Only layout is validated — sync/access are not tracked state. if (!has_discard && bd.before.layout == HAL::TextureLayout::UNDEFINED) e.mismatch = true; } else { - // Non-persistent: first barrier must always begin with - // UNDEFINED layout and carry the DISCARD flag. + // Non-persistent: first barrier must DISCARD from UNDEFINED. if (!has_discard || bd.before.layout != HAL::TextureLayout::UNDEFINED) e.mismatch = true; @@ -838,17 +907,31 @@ class FrameGraphTimelineCanvas : public dock_base } else { - // Non-first occurrence: only layout must carry over — sync/access - // in "before" describe synchronization scope, not previous state. - auto it = sub_state.find(sr); - if (it != sub_state.end()) - { - if (bd.before.layout != it->second.layout) - e.mismatch = true; - } + // Only layout must carry over — sync/access in "before" + // describe synchronization scope, not previous state. + // + // EXCEPTION — a release to UNDEFINED (alias_end) validly ends + // the lifetime from whatever layout it holds; with cross-list + // chaining (link_list_groups) its before is handed off from + // another list, not the previous barrier in this view. D3D12 + // validates the real before-layout, so skip it here. + const bool is_release = (bd.after.layout == HAL::TextureLayout::UNDEFINED); + auto cur = current(); + if (!is_release && cur && bd.before.layout != cur->layout) + e.mismatch = true; } - sub_state[sr] = bd.after; + // Update tracked state. + any_seen = true; + if (is_all) + { + sub_state.clear(); // ALL supersedes any per-subres slots + state_all = bd.after; + } + else + { + sub_state[sr] = bd.after; + } } } @@ -866,8 +949,20 @@ class FrameGraphTimelineCanvas : public dock_base else { const PassInfo* last_pass = nullptr; + uint64 last_id = entries.front().detail->resource_id; for (auto& e : entries) { + // A recreate() swaps in a new resource instance under the same + // name. Draw a separator so the old and new instances' barriers + // aren't read as one continuous timeline. + if (e.detail->resource_id != last_id) + { + last_id = e.detail->resource_id; + last_pass = nullptr; // force the pass header to reprint + y += 6.0f; + add_row("------------ recreated (new instance) ------------", 0.0f, col_dim); + } + // Print pass header whenever the pass changes. if (e.pass != last_pass) { @@ -1303,6 +1398,28 @@ class FrameGraphTimelineCanvas : public dock_base } } + // ECL packing: replicate commit_command_lists. Per queue, a fresh + // ExecuteCommandLists starts at the first pass, or when a pass waits on + // another queue (gpu_wait flushes the batch first); a pass with put_fence + // closes the batch (flush after), so the next same-queue pass starts a new + // ECL. Ids are unique across queues so colours don't collide. m_passes is + // in execution order for the enabled section. + { + std::unordered_map cur; // queue -> current ECL id (-1 = closed) + int next = 0; + for (auto& pass : m_passes) + { + if (pass.disabled) continue; + const int q = static_cast(pass.queue); + auto it = cur.find(q); + if (it == cur.end() || it->second < 0 || !pass.cross_queue_deps.empty()) + cur[q] = next++; + pass.ecl_group = cur[q]; + if (pass.put_fence) + cur[q] = -1; + } + } + // Compute runs_alone: enabled passes with no concurrent pass on any other queue. for (auto& pass : m_passes) { @@ -1417,16 +1534,22 @@ class FrameGraphTimelineCanvas : public dock_base for (auto& tr : m_resources) { - // Per-subresource last-known after-state. No ALL_SUBRESOURCES key — - // their system always emits individual per-subresource barriers. - std::unordered_map sub_state; + // State is tracked PER INSTANCE (keyed by resource_id): a recreate() + // makes a new Resource under the same name, and instances interleave + // in pass order, so a single running state would false-flag the + // hand-off. Within an instance, ALL_SUBRESOURCES reconciles with the + // per-subres slots (granularity is not uniform across lists — a + // resource can be uniform on one, expanded on another). + struct InstState + { + std::unordered_map sub_state; + std::optional state_all; + bool any_seen = false; + }; + std::unordered_map insts; const bool is_persistent = tr.is_static || tr.is_passed; - // First-occurrence info per subresource, for the cross-frame check. - struct FirstOcc { HAL::BarrierAccess access; HAL::TextureLayout layout; bool has_discard; }; - std::unordered_map first_occ; - for (auto* pass : sorted) { if (tr.has_mismatch) break; @@ -1438,20 +1561,32 @@ class FrameGraphTimelineCanvas : public dock_base { if (bd.resource_name != tr.name) continue; + InstState& st = insts[bd.resource_id]; + auto bf = static_cast(bd.flags); bool has_discard = (bf & static_cast(HAL::BarrierFlags::DISCARD)) != 0; + const bool is_all = (bd.subres == HAL::ALL_SUBRESOURCES); - bool is_first = !first_occ.count(bd.subres); - if (is_first) + auto current = [&]() -> std::optional { - first_occ[bd.subres] = { bd.before.access, bd.before.layout, has_discard }; + if (!is_all) + { + auto it = st.sub_state.find(bd.subres); + if (it != st.sub_state.end()) return it->second; + } + else if (!st.sub_state.empty()) + return st.sub_state.begin()->second; + return st.state_all; + }; + + const bool is_first = is_all ? !st.any_seen + : (st.sub_state.find(bd.subres) == st.sub_state.end() && !st.state_all); + if (is_first) + { if (is_persistent) { // Persistent: must DISCARD or declare a real before-layout. - // Only layout is validated — sync/access are not tracked state. - // UNDEFINED without DISCARD means D3D12 will validate against - // its tracked layout from the previous frame and error. if (!has_discard && bd.before.layout == HAL::TextureLayout::UNDEFINED) { @@ -1461,8 +1596,7 @@ class FrameGraphTimelineCanvas : public dock_base } else { - // Non-persistent: first barrier must always begin with - // UNDEFINED layout and carry the DISCARD flag. + // Non-persistent: first barrier must DISCARD from UNDEFINED. if (!has_discard || bd.before.layout != HAL::TextureLayout::UNDEFINED) { @@ -1473,18 +1607,22 @@ class FrameGraphTimelineCanvas : public dock_base } else { - // Continuity: only layout must carry over between passes. - // Sync/access in "before" describe synchronization scope, - // not the resource's previous tracked state. - auto it = sub_state.find(bd.subres); - if (it != sub_state.end() && - bd.before.layout != it->second.layout) + // Continuity: only layout must carry over. A release to + // UNDEFINED (alias_end) validly ends the lifetime from any + // layout — its before may be a cross-list hand-off, and + // D3D12 validates the real layout, so skip it here. + const bool is_release = (bd.after.layout == HAL::TextureLayout::UNDEFINED); + auto cur = current(); + if (!is_release && cur && bd.before.layout != cur->layout) { tr.has_mismatch = true; break; } } - sub_state[bd.subres] = bd.after; + + st.any_seen = true; + if (is_all) { st.sub_state.clear(); st.state_all = bd.after; } + else { st.sub_state[bd.subres] = bd.after; } } } } diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.cpp index 06fbe9bb..f2102b66 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.cpp @@ -323,6 +323,11 @@ namespace FrameGraph if (!check(flags & WRITEABLE_FLAGS) && !info->is_history_prev) continue; info->process_debug_resource(pass, this); } + + // Close the pass's open op-batch before deactivating freed resources, + // so alias_end lands after the last op instead of inside its batch. + list->close_op(); + for (auto info : pass->used.resource_deletions_after) { if (!info->alloc_ptr.handle) continue; @@ -378,6 +383,12 @@ namespace FrameGraph { PROFILE(L"begin_frame"); + // Promote any freshly-uploaded resources into their canonical read state + // before passes record (so seeds see the read state) and before this + // frame's lists are submitted, so uploaded assets rest in a read state + // rather than COMMON and never rely on implicit promotion (#1334). + RenderSystem::get().device().flush_uploads(); + builder.current_frame = builder.frames.begin_frame(); for (auto& chain : builder.alloc_resources) chain.reset_frame(); } @@ -644,8 +655,23 @@ namespace FrameGraph } + // Close every pass list's open op-batch before the FrameGraph appends + // its cross-pass transitions (decay-to-resting, prepare_after_state). + // Those are recorded at the list's back point; without this they would + // fall inside the last op's still-open batch and be mis-positioned. + for (auto& pass : builder.enabled_passes) + if (pass->context.list) + pass->context.list->close_op(); + builder.process_transitions(); builder.process_fences(); + + // Chain resource state across command-list boundaries so multiple lists + // share one ExecuteCommandLists scope. Runs after process_transitions + // (all usages recorded) and process_fences (batch flush points known), + // before compile_lists. + builder.link_list_groups(); + builder.compile_lists(); @@ -700,8 +726,11 @@ namespace FrameGraph { if (!sync_pass) continue; - RenderSystem::get().device().get_queue(list_type)->execute(queued_lists[list_type]); - queued_lists[list_type].clear(); + if(!queued_lists[list_type].empty()) + { + RenderSystem::get().device().get_queue(list_type)->execute(queued_lists[list_type]); + queued_lists[list_type].clear(); + } RenderSystem::get().device().get_queue(list_type)->gpu_wait(sync_pass->fence_end); } @@ -945,6 +974,64 @@ namespace FrameGraph } } + void TaskBuilder::link_list_groups() + { + PROFILE(L"link_list_groups"); + + // Lists that will be submitted together in one ExecuteCommandLists, per + // queue. Mirrors commit_command_lists: a pass that must gpu_wait flushes + // the pending batch before it, and a pass with put_fence closes one after. + enum_array> pending; + + auto close_group = [&](HAL::CommandListType type) + { + auto& lists = pending[type]; + + // Within a group, chain each resource from the list that used it last + // to the one using it next. That replaces the SYNC_NONE release + + // SYNC_NONE re-seed pair at the boundary with a direct state hand-off, + // leaving exactly one SYNC_NONE entry (first user) and one SYNC_NONE + // exit (last user) per resource per group. + if (lists.size() > 1) + { + std::map last_user; + + for (auto* cmd : lists) + for (auto* res : cmd->get_used_resources()) + { + auto it = last_user.find(res); + if (it != last_user.end() && it->second != cmd) + res->get_state_manager().chain_lists(it->second, cmd); + + last_user[res] = cmd; + } + } + + lists.clear(); + }; + + for (auto& pass : enabled_passes) + { + auto commandList = pass->context.list; + if (!commandList) continue; + + HAL::CommandListType list_type = pass->get_type(); + + bool waits = false; + for (auto sync_pass : pass->sync_state.values) + if (sync_pass) waits = true; + + if (waits) close_group(list_type); // gpu_wait flushes the batch + + pending[list_type].push_back(commandList.get()); + + if (pass->put_fence) close_group(list_type); // fence closes the batch + } + + for (auto type : magic_enum::enum_values()) + close_group(type); + } + void TaskBuilder::compile_lists() { PROFILE(L"compile"); @@ -1017,6 +1104,7 @@ namespace FrameGraph detail.after = b.after; detail.subres = b.subres; detail.flags = b.flags; + detail.resource_id = reinterpret_cast(b.resource); // opaque instance id rec.barrier_details.push_back(std::move(detail)); } rec.barrier_point = nullptr; @@ -1241,15 +1329,9 @@ namespace FrameGraph } - //last state is a write state - if ((i == info.states.size() - 1) && info.states[i].write && (info.is_static() || info.passed)) - { - auto layout = info.creation_state.get_subres_state(0).layout; - auto target = ResourceStates::NO_ACCESS; - - target.layout = layout; - info.resource->get_state_manager().transition(commandList.get(), target, ALL_SUBRESOURCES); - } + // (Last-touch decay to the resting state now happens once, + // after the states loop — see below — so it also covers a + // read-last touch, not just write-last.) } @@ -1259,6 +1341,27 @@ namespace FrameGraph + // Canonical resting-state contract for passed/static resources: + // at the LAST pass that touches the resource (read OR write), decay + // it back to creation_state.layout — its per-resource CanonicalRead + // (SHADER_RESOURCE for read resources, PRESENT for the swapchain). + // Within the graph they're FG-scheduled like transients; this is the + // single point where they return to a state any queue/list can pick + // up. A no-op when the last touch already left it there (merge_state + // collapses it). Must run before the end-state capture below so + // last_state carries the resting layout into next frame. + if (!info.states.empty() && (info.is_static() || info.passed)) + { + Pass* last_pass = info.states.back().passes.back(); + auto commandList = last_pass->context.list; + if (commandList) + { + auto target = ResourceStates::NO_ACCESS; + target.layout = info.creation_state.get_subres_state(0).layout; + info.resource->get_state_manager().transition(commandList.get(), target, ALL_SUBRESOURCES); + } + } + // link end to start transition if (!info.states.empty() && (info.is_static() || info.passed)) { @@ -1357,6 +1460,24 @@ namespace FrameGraph Pass* best_creation_pass = info->states.front().passes.front(); Pass* best_deletion_pass = nullptr; + // The creating pass must actually execute. A pass whose setup() + // returned false (renderable == false — e.g. ResultCreation, which + // only declares ResultTexture and records nothing) never runs + // FrameContext::begin, so its alias_begin — the DISCARD that + // initializes a placed/aliased resource — would never be emitted. + // The first real use then hits an uninitialized resource carrying a + // stale tracked layout (D3D12 #1422 + INCOMPATIBLE_BARRIER_LAYOUT). + // Fall forward to the first pass that actually records commands. + if (!best_creation_pass->active()) + { + for (auto& st : info->states) + { + for (auto p : st.passes) + if (p->active()) { best_creation_pass = p; break; } + + if (best_creation_pass->active()) break; + } + } // create - easy events[best_creation_pass->call_id].create.insert(info); @@ -1416,20 +1537,10 @@ namespace FrameGraph PROFILE(L"allocate memory"); - auto log_lifetime = [this](const char* what, ResourceAllocInfo* info, int call_id) - { - if (!debug_lifetime.contains(info->id)) return; - Log::get() << "[Lifetime] " << what << " '" << info->name() - << "' @call " << call_id - << " off=" << info->alloc_ptr.get_offset() - << " size=" << info->alloc_ptr.get_size() << Log::endl; - }; - for (auto [id, e] : events) { for (auto info : e.free_before) { - log_lifetime("free ", info, id); info->alloc_ptr.handle.Free(); } @@ -1441,13 +1552,11 @@ namespace FrameGraph HeapIndex index = { HAL::MemoryType::COMMITED , info->heap_type }; info->alloc_ptr = allocator.alloc(creation_info.size, creation_info.alignment, index); - log_lifetime("alloc", info, id); } for (auto info : e.free_after) { - log_lifetime("free ", info, id); info->alloc_ptr.handle.Free(); } } diff --git a/sources/RenderSystem/GUI/Debugging/TimerGraph.cpp b/sources/RenderSystem/GUI/Debugging/TimerGraph.cpp index 6c78d61c..d7ef480f 100644 --- a/sources/RenderSystem/GUI/Debugging/TimerGraph.cpp +++ b/sources/RenderSystem/GUI/Debugging/TimerGraph.cpp @@ -120,7 +120,7 @@ namespace GUI } // ── Color from name hash (HSV→RGB) ──────────────────────────────────── - static float4 name_color(const std::wstring& name) + static float4 name_color(std::wstring_view name) { size_t h = 0; for (wchar_t c : name) h = h * 131 + static_cast(c); @@ -149,7 +149,7 @@ namespace GUI // ── GraphElement ────────────────────────────────────────────────────── - static void setup_block(GraphElement* e, const std::wstring& block_name) + static void setup_block(GraphElement* e, std::wstring_view block_name) { label::ptr lbl(new label); lbl->docking = dock::FILL; @@ -706,7 +706,7 @@ namespace GUI auto my_id = data.gpu_block_id.fetch_add(1); ASSERT(my_id < 4096 * 256); auto& d = data.gpu_blocks[my_id]; - d.name = p.first->get_name(); + d.name = p.first->name; d.depth = p.first->level; d.start_time = static_cast(timer.get_start()) / clock_info[timer.queue_type].frequency; d.end_time = static_cast(timer.get_end()) / clock_info[timer.queue_type].frequency; @@ -725,7 +725,7 @@ void GUI::Elements::Debug::TimeGraph::on_cpu_start(TimedBlock* block) auto my_id = data.block_id.fetch_add(1); ASSERT(my_id < 4096 * 128); auto& d = data.blocks[my_id]; - d.name = block->get_name(); + d.name = block->name; d.depth = block->level; d.native_id = std::this_thread::get_id(); block->id = my_id; diff --git a/sources/RenderSystem/GUI/Debugging/TimerGraph.ixx b/sources/RenderSystem/GUI/Debugging/TimerGraph.ixx index 7e74ea3e..88e69fb9 100644 --- a/sources/RenderSystem/GUI/Debugging/TimerGraph.ixx +++ b/sources/RenderSystem/GUI/Debugging/TimerGraph.ixx @@ -19,7 +19,7 @@ export namespace GUI size_t thread_id; uint depth; - std::wstring name; + const wchar_t* name = nullptr; // points at the TimedBlock's LiteralWStr (static storage) - no copy }; @@ -30,7 +30,7 @@ export namespace GUI double start_time; double end_time; HAL::CommandListType queue_type; - std::wstring name; + const wchar_t* name = nullptr; // points at the TimedBlock's LiteralWStr (static storage) - no copy }; struct collected_data { @@ -55,7 +55,7 @@ export namespace GUI class GraphElement :public image { // stack_trace trace; - std::wstring name; + const wchar_t* name = nullptr; float4 block_color; base::ptr info; diff --git a/sources/RenderSystem/GUI/Elements/StatGraph.cpp b/sources/RenderSystem/GUI/Elements/StatGraph.cpp index 73fa0ea9..90132845 100644 --- a/sources/RenderSystem/GUI/Elements/StatGraph.cpp +++ b/sources/RenderSystem/GUI/Elements/StatGraph.cpp @@ -18,6 +18,9 @@ namespace GUI std::shared_ptr output_owner; // keeps HAL::Texture alive HAL::Texture2DView output_view; // SRV + UAV handles ivec2 size = {0, 0}; + // Optimized clear value baked into the texture. Recreate if the + // graph's background colour changes, or the clear would mismatch. + float4 clear_color = {-1, -1, -1, -1}; }; // ── Construction / destruction ───────────────────────────────────────── @@ -261,17 +264,27 @@ namespace GUI if (!gpu) gpu = std::make_unique(); - if (gpu->size != tex_size) + bool clear_changed = gpu->clear_color.x != background_color.x + || gpu->clear_color.y != background_color.y + || gpu->clear_color.z != background_color.z + || gpu->clear_color.w != background_color.w; + + if (gpu->size != tex_size || clear_changed) { + // Bake background_color as the optimized clear value — the lines + // path clears this RT to exactly that colour, and a mismatch + // costs a slow clear (D3D12 #820). auto tex = std::make_shared(device, HAL::ResourceDesc::Tex2D( HAL::Format::R8G8B8A8_UNORM, {(UINT)tex_size.x, (UINT)tex_size.y}, 1, 1, - HAL::ResFlags::ShaderResource | HAL::ResFlags::UnorderedAccess | HAL::ResFlags::RenderTarget)); + HAL::ResFlags::ShaderResource | HAL::ResFlags::UnorderedAccess | HAL::ResFlags::RenderTarget), + HAL::TextureLayout::UNDEFINED, background_color); tex->resource->set_name("stat_graph::output"); gpu->output_view = tex->texture_2d(); gpu->output_owner = tex; gpu->size = tex_size; + gpu->clear_color = background_color; } // ── Upload samples into transient frame memory ───────────────────── diff --git a/sources/RenderSystem/Helpers/Texture.cpp b/sources/RenderSystem/Helpers/Texture.cpp index b4f1343a..65df8284 100644 --- a/sources/RenderSystem/Helpers/Texture.cpp +++ b/sources/RenderSystem/Helpers/Texture.cpp @@ -62,11 +62,11 @@ namespace HAL init(); } - Texture::Texture(Device& device, HAL::ResourceDesc desc, TextureLayout initialLayout) + Texture::Texture(Device& device, HAL::ResourceDesc desc, TextureLayout initialLayout, vec4 clear_value) { m_device = &device; - resource = std::make_shared(device, desc, desc.is_virtual()?HeapType::RESERVED:HeapType::DEFAULT, initialLayout); + resource = std::make_shared(device, desc, desc.is_virtual()?HeapType::RESERVED:HeapType::DEFAULT, initialLayout, clear_value); init(); } @@ -141,6 +141,16 @@ namespace HAL } } + // Strict promote: transition out of COPY_DEST into the resource's canonical + // read state on this (DIRECT) upload list, and pin the persistent resting + // state there. Every later command list then seeds it in the read state as + // a no-op and never decays it to COMMON — removing the implicit-promotion / + // #1334 path for uploaded assets. set_resting_state runs before execute so + // this list's own end-of-list decay is a no-op too. + auto desired = resource->get_state_manager().get_desired_state(); + list->transition(resource.get(), desired); + resource->get_state_manager().set_resting_state(desired.layout); + list->execute_and_wait(); init(); } diff --git a/sources/RenderSystem/Helpers/Texture.ixx b/sources/RenderSystem/Helpers/Texture.ixx index c82523d8..20937b65 100644 --- a/sources/RenderSystem/Helpers/Texture.ixx +++ b/sources/RenderSystem/Helpers/Texture.ixx @@ -72,7 +72,10 @@ export static const ptr null; - Texture(Device& device, HAL::ResourceDesc desc, TextureLayout initialLayout = TextureLayout::UNDEFINED); + // clear_value: the optimized clear value baked into the resource. Must + // match the colour actually passed to ClearRenderTargetView or D3D12 + // warns (#820) and takes a slower clear path. + Texture(Device& device, HAL::ResourceDesc desc, TextureLayout initialLayout = TextureLayout::UNDEFINED, vec4 clear_value = vec4(0, 0, 0, 0)); static Texture::ptr create(Device& device, HAL::texture_data::ptr& data); diff --git a/sources/RenderSystem/Lighting/PSSM.cpp b/sources/RenderSystem/Lighting/PSSM.cpp index 66cfbac6..e6607eca 100644 --- a/sources/RenderSystem/Lighting/PSSM.cpp +++ b/sources/RenderSystem/Lighting/PSSM.cpp @@ -158,7 +158,6 @@ PSSM::PSSM() command_list->transition((*data.PSSM_Depths).resource, ResourceStates::DEPTH_STENCIL); } - if(GetAsyncKeyState('4')) return; MeshRenderContext::ptr mesh_ctx(new MeshRenderContext()); mesh_ctx->priority = TaskPriority::HIGH; mesh_ctx->list = command_list; diff --git a/sources/SIGParser/sigs/voxel.sig b/sources/SIGParser/sigs/voxel.sig index 3ce7730d..726215e0 100644 --- a/sources/SIGParser/sigs/voxel.sig +++ b/sources/SIGParser/sigs/voxel.sig @@ -485,7 +485,11 @@ PassNode VoxelScreen [Write] Texture VoxelFramesCount; [Write] Texture VoxelIndirectNoise; [Write] Texture VoxelIndirectFiltered; - # Previous-frame view of VoxelIndirectFiltered (history-prev, adopted resource). + # Previous-frame view of VoxelIndirectFiltered (history-prev, adopted + # resource). READ-ONLY by design — the FrameGraph skips alloc/free + # scheduling for a history prev and assumes it is never written. A fresh + # prev (first frame, no carried history) is handled shader-side, not by + # clearing it here. Texture VoxelIndirectFilteredPrev; TextureCube sky_cubemap_filtered; Texture BlueNoise; diff --git a/sources/Test/Tests/Test.FrameGraph.ixx b/sources/Test/Tests/Test.FrameGraph.ixx index 00495ad7..a69c9ee6 100644 --- a/sources/Test/Tests/Test.FrameGraph.ixx +++ b/sources/Test/Tests/Test.FrameGraph.ixx @@ -72,9 +72,10 @@ export namespace Test Pipelines::UIPipeline pipeline; graph.start_new_frame(); + + pipeline.add_passes(graph); graph.builder.pass_texture(FrameGraph::ResourceID::swapchain, rt); - pipeline.add_passes(graph); ui.create_graph(graph); // populates UIContext::draw_infos graph.setup(); diff --git a/sources/Test/Tests/Test.HAL.SIG.ixx b/sources/Test/Tests/Test.HAL.SIG.ixx index eec5b5ac..733950d4 100644 --- a/sources/Test/Tests/Test.HAL.SIG.ixx +++ b/sources/Test/Tests/Test.HAL.SIG.ixx @@ -26,8 +26,11 @@ export namespace Test HAL::HeapType::DEFAULT); // Full-screen triangle that reads the Color CBV and outputs it. - // Uses the same indirect-CBV boilerplate as the autogen Color.h header - // so the binding matches DefaultLayout::Instance0 (slot ID 4). + // Uses the same indirect-CBV boilerplate as the autogen Color.h header so + // the binding matches the Color slot. NOTE: the register/push index must + // stay in sync with workdir/shaders/autogen/Color.h — the SIG layout can + // shift slot indices when .sig files change. Color currently lives at + // slot 8 → b8/space8 (D3D12) / _hal_push.s8 (SPIR-V). static constexpr const char* kSigColorHLSL = R"hlsl( struct CB { uint offset; }; struct Color { float4 color; }; @@ -38,9 +41,9 @@ struct _HALPush { uint s0,s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15; }; [[vk::push_constant]] ConstantBuffer<_HALPush> _hal_push; #endif struct _CB_Color { uint offset; }; -static _CB_Color pass_Color = { _hal_push.s4 }; +static _CB_Color pass_Color = { _hal_push.s8 }; #else -ConstantBuffer pass_Color : register(b4, space4); +ConstantBuffer pass_Color : register(b8, space8); #endif ConstantBuffer CreateColor() { return ResourceDescriptorHeap[pass_Color.offset]; } diff --git a/sources/Test/Tests/Test.Profiling.ixx b/sources/Test/Tests/Test.Profiling.ixx index 8035f7dc..f3b25031 100644 --- a/sources/Test/Tests/Test.Profiling.ixx +++ b/sources/Test/Tests/Test.Profiling.ixx @@ -212,7 +212,11 @@ export namespace Test ASSERT_TRUE(grand_level == child_level + 1); } auto t3 = Profiler::get().start(L"child2"); - ASSERT_TRUE(Profiler::current_block->level == child_level); + auto block = Profiler::current_block; + + // child2 starts while child1 (t1) is still the current block, so it + // nests UNDER child1 — a sibling of grandchild, one level below child1. + ASSERT_TRUE(block->level == child_level + 1); } ASSERT_TRUE(root_level < Profiler::current_block->level + 1); } diff --git a/sources/Test/Tests/Test.RTX.ixx b/sources/Test/Tests/Test.RTX.ixx index 714cc476..75371588 100644 --- a/sources/Test/Tests/Test.RTX.ixx +++ b/sources/Test/Tests/Test.RTX.ixx @@ -82,10 +82,6 @@ export namespace Test scene->update_transforms(); scene->update(*graph.builder.current_frame); - // Register the output texture as the FrameGraph resource "ColorOutput". - graph.builder.pass_texture(FrameGraph::ResourceID::ColorOutput, output->resource, - {}, FrameGraph::ResourceFlags::Required); - // FrameInfo slot: camera, sun, BRDF LUT. graph.add_slot_generator([](FrameGraph::Graph& g) { auto& sky = g.get_context(); @@ -147,6 +143,10 @@ export namespace Test }, FrameGraph::PassFlags::Compute); + // Register the output texture as the FrameGraph resource "ColorOutput". + graph.builder.pass_texture(FrameGraph::ResourceID::ColorOutput, output->resource, + {}, FrameGraph::ResourceFlags::Required); + graph.setup(); graph.compile(frame_idx++); graph.render(); diff --git a/workdir/assert.txt b/workdir/assert.txt deleted file mode 100644 index 2b044935..00000000 --- a/workdir/assert.txt +++ /dev/null @@ -1,110 +0,0 @@ -ASSERT FAILED: (i < storage.size()) at C:\github\Spectrum\sources\Core\Data\Data.ixx:390 -ASSERT FAILED: (i < storage.size()) at C:\github\Spectrum\sources\Core\Data\Data.ixx:390 -ASSERT FAILED: (!passes.empty()) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:918 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:728 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\github\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (declared_write == actual_write) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:788 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:280 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (exists(result)) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.Base.ixx:863 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:269 diff --git a/workdir/shaders/UniversalMaterial.hlsl b/workdir/shaders/UniversalMaterial.hlsl index 0d19b98d..a7fb9513 100644 --- a/workdir/shaders/UniversalMaterial.hlsl +++ b/workdir/shaders/UniversalMaterial.hlsl @@ -228,12 +228,13 @@ vertex_output2 HS(InputPatch inputPatch, #ifdef BUILD_FUNC_PS #define sample(tex, s, tc, lod) get_texture(tex).Sample(s, tc); - -//tex.Sample(s, tc); - +#elif defined(BUILD_FUNC_PS_VOXEL) + + +#define sample(tex, s, tc, lod) get_texture(tex).Sample(s, tc); #else -#define sample(tex, s, tc, lod) get_texture(tex).SampleLevel(s, tc, lod); +#define sample(tex, s, tc, lod) get_texture(tex).SampleLevel(s, tc, lod); #endif diff --git a/workdir/shaders/UniversalMaterialRaytracing.hlsl b/workdir/shaders/UniversalMaterialRaytracing.hlsl index a6e461d8..70b1ddff 100644 --- a/workdir/shaders/UniversalMaterialRaytracing.hlsl +++ b/workdir/shaders/UniversalMaterialRaytracing.hlsl @@ -171,7 +171,7 @@ void MyClosestHitShader(inout RayPayload payload, in MyAttributes attr) float3 s_dir = normalize(frame.GetSunDir().xyz); [loop] - for (int si = 0; si < 16; si++) + for (int si = 0; si < 1; si++) { [raypayload] ColorShadowPayload sp; sp.transmittance = sun_vis; diff --git a/workdir/shaders/raytracing.hlsl b/workdir/shaders/raytracing.hlsl index 2ec7855b..248a7ce7 100644 --- a/workdir/shaders/raytracing.hlsl +++ b/workdir/shaders/raytracing.hlsl @@ -352,7 +352,7 @@ void ColorPass() //TraceRay(raytracing.GetScene(), RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH, ~0, 0, 0, 0, ray, payload_shadow); - tex_noise[itc] = payload_gi.color;// erp(tex_noise[itc], shadow, 0.1);// !payload_shadow.hit; + tex_noise[itc] = float4(payload_gi.color.rgb, 1); // erp(tex_noise[itc], shadow, 0.1);// !payload_shadow.hit; } @@ -521,7 +521,7 @@ void MyRaygenShaderReflection() ray.Origin = pos; ray.Direction = dir; ray.TMin = 0.05; - ray.TMax = 0.5 * length(oneVoxelSize) / (tan(roughness) + 0.001); + ray.TMax = 0.5 * length(oneVoxelSize) / (tan(roughness + 0.001) + 0.001); ColorPass(raytracing.GetScene(), ray, RAY_FLAG_NONE, payload_gi); if (payload_gi.dist > 100000 - 5) diff --git a/workdir/shaders/voxel_lighting.hlsl b/workdir/shaders/voxel_lighting.hlsl index 49e57854..6b8b6504 100644 --- a/workdir/shaders/voxel_lighting.hlsl +++ b/workdir/shaders/voxel_lighting.hlsl @@ -111,7 +111,7 @@ float4 get_direction(float3 pos, float3 normal, float3 dir, float k, float a) } float4 getGI(float3 Pos, float3 Normal) { - float a = 0.4; + float a = 0.7; float4 Color = 0; float t = 1; @@ -172,10 +172,11 @@ void CS( #endif - float3 pos = index * voxel_size / dims + voxel_min + oneVoxelSize * normals ; + float3 pos = index * voxel_size / dims + voxel_min;// + // +oneVoxelSize * normals; float shadow = saturate(dot(normals, dir)) * get_shadow(pos); float3 lighting = 2 * albedo.xyz * gi.xyz + 1 * albedo.xyz* shadow; - output[index] = float4(lighting, 1); + output[index] = lerp(output[index], float4(lighting, 1), 1); } diff --git a/workdir/shaders/voxel_screen.hlsl b/workdir/shaders/voxel_screen.hlsl index e2f8ae9f..6ada7203 100644 --- a/workdir/shaders/voxel_screen.hlsl +++ b/workdir/shaders/voxel_screen.hlsl @@ -389,7 +389,16 @@ upscale_result get_history(float3 pos, float2 tc, float2 prev_tc, float2 dims, f float4 s11 = tex_gi_prev2.SampleLevel(pointBorderSampler, gatherUv + float2(offset[3]) / dims, 0); float4 accumSpeedPrev = float4(s00.w, s01.w,s10.w,s11.w); - + + // A fresh history texture (first frame — nothing carried yet) holds garbage. + // .w stores a NORMALIZED frame count, so anything outside [0,1] — including + // NaN, which fails both comparisons — means "no history here": zero the + // accumulation so speed becomes 1 and this frame's value is used directly. + // (Replaces the old clear_uav on the prev, which was an illegal UAV write + // on a read-only history resource.) + accumSpeedPrev = select(and(accumSpeedPrev >= 0.0, accumSpeedPrev <= 1.0), accumSpeedPrev, 0.0); + + // ... read s00, s10, s01, s11 using point filtering float4 history = ApplyBilinearCustomWeights(s00, s10, s01, s11, weights, true); diff --git a/workdir/test_references/rtx_material_tester.png b/workdir/test_references/rtx_material_tester.png index 2f06e190..9055db4a 100644 Binary files a/workdir/test_references/rtx_material_tester.png and b/workdir/test_references/rtx_material_tester.png differ