From 47fc8e4c9cd87c497926866e649f158d50838d4e Mon Sep 17 00:00:00 2001 From: cheater Date: Mon, 20 Jul 2026 01:17:53 +0300 Subject: [PATCH 01/10] wip --- .../HAL/API/D3D12/HAL.D3D12.CommandList.cpp | 29 +++- sources/HAL/HAL.ResourceStates.cpp | 162 ++++++++++++++---- sources/HAL/HAL.ResourceStates.ixx | 24 +++ .../FrameGraph/FrameGraph.Base.ixx | 5 - .../RenderSystem/FrameGraph/FrameGraph.cpp | 45 ++--- workdir/assert.txt | 33 ++++ 6 files changed, 234 insertions(+), 64 deletions(-) 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/HAL.ResourceStates.cpp b/sources/HAL/HAL.ResourceStates.cpp index 553a833e..12eac26d 100644 --- a/sources/HAL/HAL.ResourceStates.cpp +++ b/sources/HAL/HAL.ResourceStates.cpp @@ -184,9 +184,36 @@ 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; + uniform_state.reset(); for (auto& s : subres) s.reset(); } @@ -195,12 +222,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 +237,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 +361,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) @@ -386,6 +417,8 @@ namespace HAL states.set_init_func([count](SubResourcesCPU& state) { state.used = false; + state.uniform = true; // start each list in the uniform fast path + 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 @@ -474,15 +507,32 @@ 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 keeps + // initial_layout — it was initialized out-of-band, so must not + // be discarded. (2b replaces initial_layout with CanonicalRead.) 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 = initial_layout; subres_cpu.used = true; subres_cpu.first_usage = subres_cpu.last_usage = (list)->add_usage((resource), subres, target); @@ -518,13 +568,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 +644,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 +726,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 +764,27 @@ namespace HAL auto& cpu_state = get_state((from)); - for (int i = 0; i < gpu_state.subres.size(); i++) + if (cpu_state.uniform) + { + // 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 { - if (gpu_state.subres[i].layout == TextureLayout::NONE) continue; + 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) diff --git a/sources/HAL/HAL.ResourceStates.ixx b/sources/HAL/HAL.ResourceStates.ixx index 2f895478..29ef6450 100644 --- a/sources/HAL/HAL.ResourceStates.ixx +++ b/sources/HAL/HAL.ResourceStates.ixx @@ -123,6 +123,22 @@ export bool used = false; + // 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 +181,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; diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx b/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx index acc64e65..58ba17e7 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); diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.cpp index 06fbe9bb..e9c93200 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.cpp @@ -1241,15 +1241,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 +1253,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)) { @@ -1416,20 +1431,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 +1446,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/workdir/assert.txt b/workdir/assert.txt index 2b044935..efef3ee4 100644 --- a/workdir/assert.txt +++ b/workdir/assert.txt @@ -108,3 +108,36 @@ ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL 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:298 +ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 +ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:203 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 +ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 +ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 +ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 From 01d9caca219a8ffe1a2c1d44faa49206d512a0cd Mon Sep 17 00:00:00 2001 From: cheater Date: Mon, 20 Jul 2026 13:27:01 +0300 Subject: [PATCH 02/10] validations fixed --- sources/HAL/API/D3D12/HAL.D3D12.Resource.cpp | 19 ++++-- sources/HAL/API/D3D12/HAL.D3D12.Resource.ixx | 5 +- sources/HAL/HAL.CommandList.cpp | 64 ++++++++++++++++++- sources/HAL/HAL.CommandList.ixx | 32 +++++++++- sources/HAL/HAL.Debug.ixx | 5 ++ sources/HAL/HAL.ResourceStates.cpp | 19 ++++-- sources/HAL/HAL.ResourceStates.ixx | 7 ++ .../Effects/VoxelGI/VoxelGIGraph.cpp | 9 ++- .../RenderSystem/FrameGraph/FrameGraph.cpp | 31 +++++++++ .../RenderSystem/GUI/Elements/StatGraph.cpp | 17 ++++- sources/RenderSystem/Helpers/Texture.cpp | 4 +- sources/RenderSystem/Helpers/Texture.ixx | 5 +- sources/SIGParser/sigs/voxel.sig | 6 +- workdir/assert.txt | 3 + workdir/shaders/voxel_screen.hlsl | 11 +++- 15 files changed, 214 insertions(+), 23 deletions(-) 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..fbbd1a7a 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; @@ -1143,6 +1200,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); @@ -1228,6 +1286,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 +1667,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 +1681,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..4da48843 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 (phase 3) --------------------------------- + // 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); @@ -252,7 +276,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 +289,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.Debug.ixx b/sources/HAL/HAL.Debug.ixx index f92b7c41..57bb2bbb 100644 --- a/sources/HAL/HAL.Debug.ixx +++ b/sources/HAL/HAL.Debug.ixx @@ -10,6 +10,11 @@ export namespace HAL { constexpr bool RunForPix = false; + // Phase-3 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.ResourceStates.cpp b/sources/HAL/HAL.ResourceStates.cpp index 12eac26d..45981570 100644 --- a/sources/HAL/HAL.ResourceStates.cpp +++ b/sources/HAL/HAL.ResourceStates.cpp @@ -213,6 +213,7 @@ namespace HAL { 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(); @@ -418,6 +419,7 @@ namespace HAL { 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) @@ -525,14 +527,23 @@ namespace HAL // 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 keeps - // initial_layout — it was initialized out-of-band, so must not - // be discarded. (2b replaces initial_layout with CanonicalRead.) + // 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; if (!resource->frame_graph_managed && was_virgin && state.has_write_bits()) target = ResourceStates::UNKNOWN; else - target.layout = initial_layout; + 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); diff --git a/sources/HAL/HAL.ResourceStates.ixx b/sources/HAL/HAL.ResourceStates.ixx index 29ef6450..46eaa9b5 100644 --- a/sources/HAL/HAL.ResourceStates.ixx +++ b/sources/HAL/HAL.ResourceStates.ixx @@ -123,6 +123,13 @@ export bool used = false; + // Operation batching (phase 3): 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 diff --git a/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp b/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp index 7bf81bbd..f7a78428 100644 --- a/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp +++ b/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp @@ -613,8 +613,13 @@ VoxelGI::VoxelGI(Scene::ptr& scene) :scene(scene), VariableContext(L"VoxelGI") // 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)); + // 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()) diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.cpp index e9c93200..477e32f4 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; @@ -644,6 +649,14 @@ 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(); builder.compile_lists(); @@ -1372,6 +1385,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); 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..b6cb1c14 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(); } 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/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/workdir/assert.txt b/workdir/assert.txt index efef3ee4..8b4d5976 100644 --- a/workdir/assert.txt +++ b/workdir/assert.txt @@ -141,3 +141,6 @@ ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\so ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 +ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:299 +ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:299 +ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:299 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); From 860b63ca6351b26b504f32878393e6fed1e0ea67 Mon Sep 17 00:00:00 2001 From: cheater Date: Tue, 21 Jul 2026 00:57:47 +0300 Subject: [PATCH 03/10] wip --- .gitignore | 2 + sources/HAL/API/D3D12/HAL.D3D12.Device.cpp | 31 ++++ sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp | 12 +- sources/HAL/HAL.CommandList.cpp | 58 +++----- sources/HAL/HAL.CommandList.ixx | 9 +- sources/HAL/HAL.Queue.cpp | 45 +----- sources/HAL/HAL.ResourceStates.cpp | 135 ++++++++++-------- sources/HAL/HAL.ResourceStates.ixx | 34 ++++- sources/Modules/d3d12/d3d12.ixx | 1 + .../FrameGraph/FrameGraph.Base.ixx | 7 + .../RenderSystem/FrameGraph/FrameGraph.cpp | 73 ++++++++++ 11 files changed, 257 insertions(+), 150 deletions(-) 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.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..cb8f60b7 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp +++ b/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp @@ -264,9 +264,17 @@ 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 + // One ExecuteCommandLists per list. + // + // Batching several lists into one ECL scope is phase 4 step 2, and it + // requires TaskBuilder::link_list_groups to chain resource state + // across list boundaries first: 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). Each list is currently self-contained (SYNC_NONE + // seed in, SYNC_NONE release out), so it must get its own scope. + queued.emplace_back(list->get_native().Get()); flush(); } diff --git a/sources/HAL/HAL.CommandList.cpp b/sources/HAL/HAL.CommandList.cpp index fbbd1a7a..14e98e60 100644 --- a/sources/HAL/HAL.CommandList.cpp +++ b/sources/HAL/HAL.CommandList.cpp @@ -956,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(!); @@ -1060,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; @@ -1110,6 +1127,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); @@ -1219,44 +1237,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); diff --git a/sources/HAL/HAL.CommandList.ixx b/sources/HAL/HAL.CommandList.ixx index 4da48843..ce477698 100644 --- a/sources/HAL/HAL.CommandList.ixx +++ b/sources/HAL/HAL.CommandList.ixx @@ -119,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 + // list-group compilation 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); 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.ResourceStates.cpp b/sources/HAL/HAL.ResourceStates.cpp index 45981570..e260620d 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; } @@ -423,7 +409,10 @@ namespace HAL 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; + } }); } @@ -440,49 +429,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 { @@ -822,6 +768,79 @@ 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; + + // Nothing but boundary markers in this list — there is no real usage + // to hand a predecessor to. + if (is_none(target)) 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 46eaa9b5..ae6a83f6 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; + // List-group compilation (phase 4): 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(); @@ -212,10 +231,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; @@ -228,6 +243,15 @@ export void connect(Transitions* from, Transitions* to); + // List-group chaining (phase 4). `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/FrameGraph/FrameGraph.Base.ixx b/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx index 58ba17e7..faec13a6 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx +++ b/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx @@ -871,6 +871,13 @@ public: void create_resources(); void process_transitions(); void process_fences(); + + // Phase 4: 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.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.cpp index 477e32f4..9003e994 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.cpp @@ -659,6 +659,21 @@ namespace FrameGraph builder.process_transitions(); builder.process_fences(); + + // DISABLED — phase 4 step 2 (multiple command lists per ExecuteCommandLists). + // + // link_list_groups() runs AFTER process_transitions(), which has already + // finalized the persistent GPU layout via set_cpu_state (gpu.layout = + // last usage's layout). Chaining then retroactively suppresses barriers + // that computation depended on, so the tracked layout and the real layout + // drift apart and the next frame's seed mismatches (D3D12 #1334). + // + // The fix is not another suppression rule: group membership has to be + // known BEFORE transitions are generated, so releases are never emitted + // for intra-group hand-offs in the first place and set_cpu_state sees the + // truth. Kept compiled but unreferenced until that reordering is done. + // builder.link_list_groups(); + builder.compile_lists(); @@ -958,6 +973,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"); From 7351e0e18405a9d0f6639331eac3347b9f447e6e Mon Sep 17 00:00:00 2001 From: cheater Date: Tue, 21 Jul 2026 12:59:49 +0300 Subject: [PATCH 04/10] wip --- sources/HAL/HAL.ResourceStates.cpp | 17 +++ sources/HAL/HAL.ResourceStates.ixx | 13 ++ sources/RenderSystem/Helpers/Texture.cpp | 10 ++ workdir/assert.txt | 146 ----------------------- 4 files changed, 40 insertions(+), 146 deletions(-) delete mode 100644 workdir/assert.txt diff --git a/sources/HAL/HAL.ResourceStates.cpp b/sources/HAL/HAL.ResourceStates.cpp index e260620d..24e639bf 100644 --- a/sources/HAL/HAL.ResourceStates.cpp +++ b/sources/HAL/HAL.ResourceStates.cpp @@ -390,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; diff --git a/sources/HAL/HAL.ResourceStates.ixx b/sources/HAL/HAL.ResourceStates.ixx index ae6a83f6..c48eecd4 100644 --- a/sources/HAL/HAL.ResourceStates.ixx +++ b/sources/HAL/HAL.ResourceStates.ixx @@ -224,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; diff --git a/sources/RenderSystem/Helpers/Texture.cpp b/sources/RenderSystem/Helpers/Texture.cpp index b6cb1c14..65df8284 100644 --- a/sources/RenderSystem/Helpers/Texture.cpp +++ b/sources/RenderSystem/Helpers/Texture.cpp @@ -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/workdir/assert.txt b/workdir/assert.txt deleted file mode 100644 index 8b4d5976..00000000 --- a/workdir/assert.txt +++ /dev/null @@ -1,146 +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 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:203 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (prev==f.second) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\Core\Utils\Allocators\Allocators.cpp:243 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:298 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:299 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:299 -ASSERT FAILED: (false) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:299 From b5de27040e449770af585ed33d3c4ba2370d0650 Mon Sep 17 00:00:00 2001 From: cheater Date: Tue, 21 Jul 2026 23:35:30 +0300 Subject: [PATCH 05/10] wip --- sources/HAL/HAL.CommandList.cpp | 13 +++++ sources/HAL/HAL.Device.cpp | 51 +++++++++++++++++++ sources/HAL/HAL.Device.ixx | 17 +++++++ sources/HAL/HAL.Resource.Texture.ixx | 6 +++ .../RenderSystem/FrameGraph/FrameGraph.cpp | 9 +++- 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/sources/HAL/HAL.CommandList.cpp b/sources/HAL/HAL.CommandList.cpp index 14e98e60..303ff351 100644 --- a/sources/HAL/HAL.CommandList.cpp +++ b/sources/HAL/HAL.CommandList.cpp @@ -1084,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; 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.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/RenderSystem/FrameGraph/FrameGraph.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.cpp index 9003e994..5a13c5b7 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.cpp @@ -383,6 +383,13 @@ 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. This is what removes the class-1 #1334 + // flood (uploaded assets no longer rest in COMMON / rely on implicit + // promotion) — validated 2026-07-21. + RenderSystem::get().device().flush_uploads(); + builder.current_frame = builder.frames.begin_frame(); for (auto& chain : builder.alloc_resources) chain.reset_frame(); } @@ -672,7 +679,7 @@ namespace FrameGraph // known BEFORE transitions are generated, so releases are never emitted // for intra-group hand-offs in the first place and set_cpu_state sees the // truth. Kept compiled but unreferenced until that reordering is done. - // builder.link_list_groups(); + // builder.link_list_groups(); // enable once class-2 (#1417 FG transients) is chained builder.compile_lists(); From c1dc20612f7b0f8d26afc60321126efaec39b1bc Mon Sep 17 00:00:00 2001 From: cheater Date: Wed, 22 Jul 2026 00:10:53 +0300 Subject: [PATCH 06/10] wip --- sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp | 5 ++- sources/HAL/HAL.ResourceStates.cpp | 36 +++++++++++++++++-- .../RenderSystem/FrameGraph/FrameGraph.cpp | 8 ++++- sources/RenderSystem/Lighting/PSSM.cpp | 1 - 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp b/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp index cb8f60b7..fb14912a 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp +++ b/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp @@ -274,8 +274,11 @@ namespace HAL // emitting a SyncBefore == SYNC_NONE barrier once it has been // accessed (#1417). Each list is currently self-contained (SYNC_NONE // seed in, SYNC_NONE release out), so it must get its own scope. + // Accumulate into one ExecuteCommandLists (requirement 6). Legal now + // that TaskBuilder::link_list_groups chains resource state across list + // boundaries within a group — no per-list flush. gpu_wait/signal still + // flush at real sync points. queued.emplace_back(list->get_native().Get()); - flush(); } void Queue::flush() diff --git a/sources/HAL/HAL.ResourceStates.cpp b/sources/HAL/HAL.ResourceStates.cpp index 24e639bf..23c23c37 100644 --- a/sources/HAL/HAL.ResourceStates.cpp +++ b/sources/HAL/HAL.ResourceStates.cpp @@ -816,9 +816,39 @@ namespace HAL while (is_none(target) && target->next_usage) target = target->next_usage; - // Nothing but boundary markers in this list — there is no real usage - // to hand a predecessor to. - if (is_none(target)) return; + // `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 diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.cpp index 5a13c5b7..50e4ce9d 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.cpp @@ -679,7 +679,13 @@ namespace FrameGraph // known BEFORE transitions are generated, so releases are never emitted // for intra-group hand-offs in the first place and set_cpu_state sees the // truth. Kept compiled but unreferenced until that reordering is done. - // builder.link_list_groups(); // enable once class-2 (#1417 FG transients) is chained + // Requirement 6: chain resource state across command-list boundaries so + // multiple lists share one ExecuteCommandLists scope. Depends on the + // UploadManager (class-1 assets rest in a read state) and chain_lists' + // all-none handling (class-2 FG transients). #1417/#1334 eliminated + // 2026-07-22 (2 frame-1-transient #1334 on a pre-promote deserialize + // texture remain). + builder.link_list_groups(); builder.compile_lists(); 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; From 0eff0e47e44d72282096a8495528b1a2775ccd82 Mon Sep 17 00:00:00 2001 From: cheater Date: Wed, 22 Jul 2026 02:42:17 +0300 Subject: [PATCH 07/10] almost done --- sources/HAL/HAL.CommandListRecorder.ixx | 5 + .../FrameGraph/FrameGraph.Debug.Timeline.cpp | 177 ++++++++++++++---- .../RenderSystem/FrameGraph/FrameGraph.cpp | 1 + workdir/shaders/UniversalMaterial.hlsl | 9 +- workdir/shaders/raytracing.hlsl | 2 +- workdir/shaders/voxel_lighting.hlsl | 7 +- 6 files changed, 153 insertions(+), 48 deletions(-) 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/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp index 140eaeed..f2b4ff90 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp @@ -797,40 +797,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 +888,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 +930,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) { @@ -1417,16 +1493,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 +1520,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 +1555,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 +1566,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 50e4ce9d..4e725ee3 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.cpp @@ -1116,6 +1116,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; 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/raytracing.hlsl b/workdir/shaders/raytracing.hlsl index 2ec7855b..99069721 100644 --- a/workdir/shaders/raytracing.hlsl +++ b/workdir/shaders/raytracing.hlsl @@ -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); } From a4abb754a67c1888bd10f714af16417c5088429f Mon Sep 17 00:00:00 2001 From: cheater Date: Wed, 22 Jul 2026 17:17:45 +0300 Subject: [PATCH 08/10] small improvements --- .../Effects/VoxelGI/VoxelGIGraph.cpp | 39 +++++++++++------- .../FrameGraph/FrameGraph.Debug.Timeline.cpp | 41 +++++++++++++++++++ .../RenderSystem/FrameGraph/FrameGraph.cpp | 7 +++- .../RenderSystem/GUI/Debugging/TimerGraph.cpp | 8 ++-- .../RenderSystem/GUI/Debugging/TimerGraph.ixx | 6 +-- 5 files changed, 77 insertions(+), 24 deletions(-) diff --git a/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp b/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp index f7a78428..30ff3da0 100644 --- a/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp +++ b/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp @@ -611,19 +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)); - // 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)); + { + 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(); @@ -655,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"); @@ -834,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; @@ -843,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; @@ -869,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.Debug.Timeline.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp index f2b4ff90..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* { @@ -1379,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) { diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.cpp index 4e725ee3..14fad9b2 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.cpp @@ -741,8 +741,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); } 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; From 5b8d55385f1f7bc31bb618072ed9e4bbeb205db9 Mon Sep 17 00:00:00 2001 From: cheater Date: Thu, 23 Jul 2026 01:21:33 +0300 Subject: [PATCH 09/10] test fixes --- sources/Test/Tests/Test.FrameGraph.ixx | 3 ++- sources/Test/Tests/Test.HAL.SIG.ixx | 11 +++++++---- sources/Test/Tests/Test.Profiling.ixx | 6 +++++- sources/Test/Tests/Test.RTX.ixx | 8 ++++---- .../shaders/UniversalMaterialRaytracing.hlsl | 2 +- workdir/shaders/raytracing.hlsl | 2 +- .../test_references/rtx_material_tester.png | Bin 69036 -> 74470 bytes 7 files changed, 20 insertions(+), 12 deletions(-) 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/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 99069721..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; } diff --git a/workdir/test_references/rtx_material_tester.png b/workdir/test_references/rtx_material_tester.png index 2f06e190dea8fcdf64ea2e70a70f4b71b371e1c2..9055db4a983c02127d2273fdd1771ad48acf341c 100644 GIT binary patch literal 74470 zcmeEt)89U>(H4xIwh(sj@8 zuDkAk@Vt9oz#LfToY`mZv-kH?J5g6#g#ect7X<}{KuuLq9|Z*!I7CIk!2&*@-c=63 z2Zp1ZmK+Ld+^3g+vMqtXpLwd9`k%_1}kp4|a3SJI|g{2t8&Tvj?B0$&Jtuk@SJ;Bv&NV zUd=Zb?9t!(=}aztshpS<2tCdk?2Lk!Kp?nmM5<_uoIcyKA4;Ic<&!5~_qj5c(rio_ zXubu|!8^&^bYiMu_r}ZLuHCNOm9bXt2AW(?*aWkOj(9d+QC0~5z@D!uxSdE z(0^W_QeKqXKbsI`OO$`p!+P%!D8RX;l%c3oDhw(wZ_z#hcd+^23;zc@M>oQ0FFur3 z>N<%xj7)2ON{CKtUKjW^d!0GX`}%fH`Vj>fDiz|#i|}haUkjC4zf%sjZdSJL3g@bh zz&SwzqpUBd!CITY&nHcTeoKd?0%wP>T`%=Kiu{R7siW!6>`_7Sjgh^wn-?TA$_lU9 z&v`o+cNtQ`)Q zl9!zoaf2Ze8NOD2l^le5|G1wl>tJhMy` z-KX6TEXPJh_M6TOra{5LMJ2~r_fo_An>uUV4EKD9W8!PX^|L6E`xrS{${3B6tH+)z zA!dB}5+cxluT>>Rdz!UEG0@{YlXv|I#P;hC@T^xoC6W@yB(q}T0|HS zaI9i$vYd*ZQ6JyHITVT<;?vX)Iu2gEJ|*Ps-j)tinF0n3g_4rm!O8bMR9Z(o6X>0< zpFwfXta-iq5)pw6o#;X!BPAn{p;kX1enY!titTwL!z01f>g`=rv zB+`vXg*E3z!KhUpISjrg&Xa6LwtBVbxh(|sJIa;X{j(W>9!djr2KkPIvk;AFO7wwv zU|sy~v-}9&a2q7hpE1SP#KE@-koUn4x?QdM&YI&J+m`oH{n*dBx}gmOQ+{;IQxaxz zZ$7~}tch$|%>?)To*%9b8|@a3W>*%O2jxkJVgL6$V9~Y!UjLfd1JS_3q~o{Oh*rC^ z@p-;9($|`ge8O9!K4jx8JC2TCbom~V7Xulbj8}K^nSA&!ZwUHjXN7b@fBo~7yOsUC zBKP{^QQJ2D>YPsDty%`brNhSm8w)V-y|l=oO!?je60|Tbj6Pr}!-qM0?)5;i5VZOF zdiUTx78LQ!RgiyFU{W!hy3Tfdv}fe|dVkmL25;u(W2H<-?r`|O#zLQqQ)DdGeP=nt z<>_6bL$@|-j)Dlj^@4#R$mqTb7F7Z{6Hp2%45^TvUxmZj8jGTau6~zlg^dhLfnD#S78IcQ@g_dyGh)1E9yP! z#>uznCUY$)lKge#0_2b1UsS~WHr+|EX*6P2?~8HygzQ&rTk~(}E4&ZSq9Q!P?~ocE zHP|{*4L*A+|F0pRs<^nO-n7QP>dnIXcElidllf|o5hn^|^{O7p{jYxe3Cwt6d_Dej z@MLu-vTCOK?^GuhfpU$mWv)3Dj^QqX}}tuNO1z@$*E-OSl>Mj z>%~lBSXmJShSQ(6TNEC)=YFwv(8*wNtH;n|{olI8SHVg+<09-uM>Bc&8w-Puk?6~< znw2gk(QQ5Xl*7hPLd4!cc;jSe_zJG&bE0mFiP!ww}P)NvR>6{y_R|bP~AH!ZFBq*5o{QmpDnbg~Bf?{5j6cRxf zAvw|%chqGw*&HeG3i*KiH@}aq1=k*I7%R_ZZr`lZoFL^bQD|8{gW2eC8en#n;$XiHr>k7nmXDJN ziCFQqq3i#YdK@fBV?*Ovrq=WCAzeCnC)Y81WsGLZMJOli=dcG#D5~fC^|_qa@697$ zGo~rx`7ff&w@Wz*QA<(xrnBB4AG+u&*x3I6xuB9|m&97xS0kREQGGj~p^suG9HK1C zEX%K{_F3&Ydc;fd_B8z~l@3GyuRTP~>Pp_|9hvdII*%*YD3d;`{!zT4J^ywOPogPS z@`M(KWe9+gfDH64an&j-uo9+YFy_i@YCiI0f8t$=RGC4^*1m5gLGy^GD100A-)o9B zMTq@KuwJ6!(RZa3@eafVHR7-ig_xW;+~MQ-I*ZPKXCA$m9wGUiJ%JL1yXOnht-KEb3{Mz9 z>%&oa7DCWl4!pL98YwAhkDSLKFekzNt#eG2$J=ad2zdjv>&Q*N8l<>^gq5s`x^oE( zRuFi|s6E!7rUW38G^Kl(Jt7jF%}lBV+d~QyvuaKp2-!%bwbAKyPRcW%ROPK6ZZ9-= zAF$j)FU|jU1#|k~2}uVmD}#)Vo18I-iDwPssc7<1FyD0FG3L4F@A|K{{xb}{&jZ@_ z!fLp`6D8KgKGMcnRyaKh68_@xe#0FJ+2V)#?I}q``<|_Z!M6GLe6b?<@E6Y3GOOTy zz$pLpBTK|_tJLDia+tkc?LskW@g|G)&easw;3yb^HDa5RSNrBc5xosroL&yk*+^x= zEW~d**&b(lMBXg5{hN`x-pV__xqrI$cu6CK@Vyf@G}>;Qx6lOnoq>e+g}BC|D~h} z6TU))BLz+$(JG;{1kck(YF&m(;^@;{Cp|NaNziEP=d59tymZoL8x(PaRstEg3dQf= znr17sckz$ZmmZdH))vm}I}a&=Z2TUdFLusT?X8Bph5mVI=AHw_U+g|?VF|d;|F%!6 z4Yq`2{IF9kpZUP9XTqGNojF1A!K9tU!ymV_24^HaY(*Wf?Rq0A^C0wOv(jhhhT`7> zP6=dd)3I=Hlkl${LjQ$(VhOW5QqG7>c0F&LN8Rov!vf=YCQQvSLi&eA<}xFdiPy}tp;a~ zW~{n{VrPypqO%m+SEu_2chCA0_;B0z_LG-_t`zW}gL2KQWSPAWWjYRjm8V|Od|Nxg zLGSOk^LaayD`?D-oTaWt0G?cE*v3fm@qnY=i_5RgXz*gLC7A~#Bq8<*fj@qz90-aRMuT${q*d#@&)^X(jHkuN>aqKEY_=@Vtuzo-}LlIonV5tGk` z>o%IZv4ei!Wp^^zd^NN}=(E7m5_Q#v66>55iI+U*GQ3D)>;%|ZhvF6+bv()XAR8aZ zHoMMlBs%Q;>cl1MQhwS3HZ0?Ry2*e;G4fz(;}+dnSgmg$!XnDQ@jp)2%<+(so`AJx%G znaQ?-|EypEXrdp)GL|jRHAZI<8t4n^Z%XJ(Gpc^G`j)*!;Q`W*ySRmhAatChOpv@h@+R)5iHEO?EOF!exxd zUM>e%V8m+8M3;qBJ(z}=VmX-2|l)4*HgADZ85w@mq{#nGDW(O;#qw3`xZ%j zYu&j>EPX5sSIbaVkS6k~u0x*Q|LJ{1TSm_zCb8cHgBU;!hLp3(zy7 zU$nS|zgvvu*n6K?e1QKR_p`*)cRLc1e7}ZVsQ1>Uj?83nwP_c)_X-_4xoa8r$dQ*U zo}DE>d$F}5xBi2hkTi~QQ8RCPUG8!9>Hav==Rx@E?KSBL1#Vfn)PL~phaDv){Z1RH zj?E3+*6F8fVBMd5(b~ZKXf)V$R+H_0B_Drc1g0^u+pp` zn5m$l&F1{33XTgg*adTH1vO&-mt@?o#`0$Imfc+mVEE z_2K^Mf%A%G@0S5Psl{yD(V3q&el>v1i}Odn-f1D(AN4SLZ}Pu5x&@|HFJf{T4Esy2 z_-)j8{Tq92s0Dfll7G3e<0?#ijT+z)3L3ru7LX8O1?l{qlkaG9lxb>}x+uug>r$G5 zmD69r{u>?i?QSra?pQeFGgO%4i}RFIGZhUr%G&qY)!`)GWBZ^MdFdSMH}$ZCAV>rQ zM#<*B7FupA&o7iCBqD$P8~+`lb}n!uD*rpcUneD)yI@# z|9OtIi)V&Lo9WzyUfY8dpPZ>%m%5`9rx{rZCqGr;T2sE{H?b);Q<*o6(serrtqIJ# z?{uy{5zB<0`Ux5h5~6XRwu-=cD{!G6?l#BsK*aB?mOno!}orSZQtRydiz{aL+p9ry+z343)!vDD93c*T^x_TLaKR`_VV#cR|E^P}J2Cg$Iim{sd?WuZ6;(Ub`+{%9N+9uGar z#%9=sP3)FB|Mr01GCY%FA1v4VYQm$DAK^)6Rt?Jg%=&o*)(7XfyoIxl$JcBe7tUXI z=KVH!BdM)YSdTPe=WM4ah7TVrmWJ+K9)|4vPS$?`koh(^?qSL68eK^C#*zIz>14yv zz0TFYSCAU;pe+kNx?y#$^v!p?&ti8?b%>)S%~q`aj^`D*QIL)Q8eicmeEEa*ts7KI z_FCuZ1K`b#b@1MX&d5mDC|0K9(FTA`Up$u8*^36f zmbad9kAH%GCygVZowak#nMDJu!{0@bMCU)(awAsN z5zbUxT*mF*jcGBK-3M|BOt4tm^DOFEw>Ssn5I=$2aCzFeRVRJ6wdc*wIWXgbTaJ{C zjEptkHC(M(_krJE*<_Aa09C$N9q)0sZguHxp*;EUg<_K8##{ARNurlMCk~S@nv&F; zd5roLDC4xjLin?MCRK**-shN39fguQ)oHeM`Y(zmruKshLwNcxJ9`$85KoK5|gi=$2K7$QOe!goy#_X zc3fh@xGz9P>*E1#!xtffT1zVIMVtRkk}CD#Yb>c6FQT}Ry-W9N>x2r9cLaoe&)!b!WzG|8 z8ZU^7ih(VMH*L(W%u&{uQrxE9%aqHIoZt`#m5!SjrQu{mKQisUR-lX>0{M&Vj5ivl z&0o?1!HjtD(T`R-XeO@)f2qG=lnFj`XosuJ*e{q;mEy1x{@t$jfHm1xIO}q3wK+|& z$wlUbUoag94v$)JhI*SK0`G)VOvQdYjym`8Z3xW2At|I=Rhey~Cum-DN}Jfe*>E@a z3N4c7QRtrDpc}4R>uS^)e$%R|CSlx4ZiQ?y>1vTGT|!6RvhDhR(8srsh!yUBp+)dJ&e5)%ru_EOOeWgsnWf&cI4hkMNb3|;;?TB_l|q- z%%{uqRkyl6Y{JVJGu=5|ckin8Qe?#&e$a}0dkGoBPAMi$McFawBuk>T6po8v zC|SDd{RPP)`7HGI)#&)I0-c!*WTeCR5Fy%+r++F`baZm_ezI!t$qIe;KEAToHT9wM zjh`awoPnMoih;J@KyhPjauTv^YHNre&uYxSYDSZ$n0_mK$o!L+O{tO=@>XlMTuL54 zC8&92-5|kpl+7aFq1K+QNCqjX#qNV@Nl<#9}i%C^J2v=UyG{)QEiA8zPxtR#t4*w;w** zby|G>B3cPxhk6)A-EVw$&8-J^k`IDX?%iz$_iaaV)V(>=zNrsx4|3^|ED5eC;tbS0 zr8h;^amzDd_!c4`{O-ZMf&|iM2vV`j1zU;WzZf=u0WKwlWEkex!cxcFq2O$C&pF`f zL|NvWF00*Ni|ocveP?S*F1L0*HqLwc(6b!PO97)4v1n$@h1zfpjn{=FGd-SV-%Sf< zO&9ZL!PnA(GCz@+Zm?9dD5ipugy$o9xiC-1Yc6FXtqv_IOO z%iwP7?yFdBGn11GZSvFdb6k#4LL zutdGr$N(aR*|tlVAGn*me*7%u&c6hL8w<^rmmdGtFVI#Pzlj^i&|4I`<2}un`1_YN zky(Y@7{4>}c}Ly)9co7%F$UwjMSbC3eJP z-ZSlp&)gq8(cb;o@A=6y#f@Kvk}&r)9Dw83@?nw$gAs-nSKRov*6N>FOL7)T-&#$; zAvi~EJxJOe$%y7_RBrg)fQQ@(DNIo(hHfo&>1PATsL4IvcWk2}dG|uojoD}Vryts$v)&dtK#d}x zJmU!T_;vtRZJdLN@QUhuCec$#Vi;d2*hA+t@sh1Xg^|MU$nDfGtC}1Iz^n@zEj*x}9$iHoCrZ_#S!Nu4b^BeS@9I5kqdwinBq-V&#aF!ajq{+y>@is)e~Kj0d?rgkyv<6Rh7bv(hL?z z7n(6g!2m90oL5Y3#1hthK}^IC1Hy2v1uMzaE5rd^*VS@~Of2kS=Ya|}+;98=KnuDa zO}a+g^T(6OR#N3CVzXM2o9}Z6sy#~K!Y)EE`)^`xwn>z&dZ2Y|R1qj+Q~^lM{3w>P zH52>td_W`&;;!ZK^bv7fe4rBeqrx+~XU+!;5&!$)qqQolyPkW~wcET8$E&3iN4;`m zV7}jj(?0rr)hr0%88W+N?!DNaI>yWe0AIA02J9=w|JHhPN-^G>ve~+^IPGiM1O?vUGj!>5a+i0$*iyv zposJ7a_|d^_Wt`zJ3H&smf-_-<0MYP!;`%H9x&>l|NX}+#m}(}YpTb(-HWV$_(g*e zlg&9_FhTSy5BQDeWT3s7@iK!ZxBP%2U&-K0dS)C2kd^1Bfk=f};4S!ic`x8B?6P#) zyE$5tsb&gva<;Q>t;nduj381N;O$8OC`xF%%}A2fYui4;&J-i12`5u3hJLrrWo-`1 z^seq*-!pyNb5J$!YBxi56l5i0`yyIXdjchG(xK{?((}PUd`5SBJ&@N#IYn_5E*{YC z>pzWu^5S-}%K#M!-?Ta8<3pwa$lLTiokT#QUM+LarzDqWL`sYoF0t}zMDDU?KW7(3 zRLA*1xpoAznm-*^ugbtgmar?0g2SHcC9mHm#vT1)X+sQ*6HSUz`-sV)ZVrVv5+r>1 zQ6hi+HlE%V#@8K;b`Lr=?|TbT+*R{Iy;D9k$E^|IKPQM5w8D~0!*{x|zxgfp%g+|> zH2*NZo0NKEv4+}3Wy}zcc4L$7%bG&{-Ymj-%w3iXL)@&FCRR3w?vnic zl~OU-$eP7@*u`E?*?!1{-;kUFl4YyliLFN&RWh=^gnX)Vwap_3v-T{%xd@CMxHP2Q zY=6B`L{Z38?|1kve?|HdimfHG#eDp)epifA`MY~UYWim4k4vHFif9<6y8Lv(DWur{ zQW%-`oAW-PYZT6V{Vq9deqP%>+k!Usd;CpGiSB!^+dtUaI|Xb-32-=|zCBMdZGT5$ zxu_vrQ1W4tUrZ|A`A>{>Or0x$>Pi7Sw4xg7vuCCY3j4QNbT(6iJ>oD6zG8*L0a5lH z^A6-)GQKxZ*dR>xw;VJ10Z1vrbZyfnI&3D@}V_(`kG1Lo?(bQ17B*16&nW;+QYf9}iY^J&pd zk}? zXU7Aipn1)hmD8!$1y*f43(3qBS#aH&yiaL$@m0oLG*cWn>?&c zgT~Ph6At>ctu7x4Lr+49ltNA~Yn{Alb2IqZ+%SpbnEQ(XC&$?(nYCEZ1nvHwg7@tA zN7MQpMz%G0lat~vQbKv1`2+NplMr12z_aJ&JhJiUZ; zZRT6}(-=9wGwJ*VAhsn=%k58)AN&eI>laij*N4FRw!WcrnIq8tc%ry8b07nxI7e_{ zQFYG17^Li;e|fs54zUz$E2%1wvd%Gl+%G^*m&*whG$fSZ%%3lIU2^eL5i|dEHz6T4 zt6S3>$g~WP%xxbmBi4weWqec-IV%|IB26nY~NPtEN1t z-yy<64Uqb(5kmcKL^n6LT?t=db6;cg<*#X3#X`*HahxhJgR-L49JLm{1;w5Fu>DuV zHRvd*rv*0hjR6FQp+k}-3Ge(pc3j&gq98ErBZD^7a8@xYK5!#9`@{V*4qB=|(&6sK zWg~>9ZlsT?x#mGwDJWYl_@Maf25-fyVFc8NQ$I7Sp?S}8W-}N-K z^`;CXVx9j)1+30KIqGxdthPM09*4lxyAz9Dw|uCe^QDF(J}Y8HD81)Uvs%_c8?p9F z^UEIJ#3ZBZ+Mynu&SeK=zs{^1@4S!{h?sj@)r9`zfS3E1QYRInko^vZ#F7 zpOT)dc#ql#&*)8?jg))m#IiYqMhjnVlj@Q+rFch$o@)~zDFgbzF*X+sjf0EB^ZE1k zBbZ-AU-n_=%ktB5GglI2ro3r9G(j~xeuCoc_Cg(HmlwmI^VXVDfq)53Sit>b&vV!| zYMhG7=uT%Vngs@9zif^$>WdXzRmM+)KdsqO9yeC+56UZ#zqQlU*)A>+W!s;P9bk`^ zaS4v%CRaC!STsIMX!99=@g7%O2=)@`!@D%{i^fBPq z@TgtGQv0^`Hv(O&z^|H(bJ{fP}m6nH;rw1BH?Tar0zxIH>f3?y%xDMTMXWc`&1 z`~WcG^H6+?;d3Pf>hJ|ds6`yMc|~qp)R*oM?v}%oe?F^&UQqZ2E5dFQE8`Z*vBUE4 zsxnPu)@gBz1$i=5BmBvkgHVbC|Ks>@{9@x1-?mK(Nt>AN$=z#qK2oNZx3VAZ;CltAUkbJZ?2qtXfo`PY4T}pm$D-cUbPf>fhBe& zp&ZY5l3P$<@9BwSA#x(wYHN}xPrr1557@hUARfs(Tm0wG{hUqcvIxu8Uq090$Hi|y zTq&4wwes2<656QIQ;fk`KcuAZobvlE(IWZ%#ENS2O6;(|l3$VwUkl);Ti$Qm=l&p) zRbzUGiblbNO1NE|Jkl}^ET-|p{c^X>8 zyLthRtGmLfJ@UfVtu?}lj*zcUIKKIi&C;N`hlOOgp}tT~;={k`Wgqt04_=-1@uZBY z%3E+?dgfC1qNT0g>omL5-zqOI!Eu3ksNDN>E*vCQm&$;ls5iiVEE$=ZVkuWmTO%@^ z@ppZruE+Nd@PxCI3B68ExD>4@sf)MVSAn2_ud{5Thl?}+I~Dry6r0zohuwE<+Z^m0 zE%DgnP|qK1c8(ky*1_Vh|E{+jg=9DwVBDcBq(mPEg3e7PW!5q-95)x-@%vS|5QV!o zluW6z-LJQchY`7dJ7G6mNpo1T4I9u7FM_-ZhRAI;`p*p})_~;kmF|^p28)Dl`maBH zu&qm#6vKgsfTG>2QF4^8Ar~(}Fr{<}aMqCKvpB0h!a!0Wk@WayYXerRCcMAa_y{Zfk0QXkj2;-pp zoNoB+G_@-gg8K~3a!Ds7G%*&a3Sk>>OZb>F)rhee3y(`b_uLEUA^j;iC{Clnr@XJ* zoe6H_=~u6=$1I{;zp#zcf^)Po8@TlKn;YEY2VIiE??l%>kO8`QAHT2g7fbZs!g<0o zAVLu6e#~6jZ{Npf_oi4`F{aqH%)nTFQqz>RqptW9jz&BFL-*lI{K#UN*c%A?&E($Fv32&)&(^o*Wuh!Mo|IX#}6U*O^srLQf$|My)_*K*xitW4Vozy&F>Tp8vk@CmoBoxm;lW$ zo{Ea@1&rxUrz4v+^W|z@g+9*?V6tRPR@7VQ;T!3Jy!Cs3=5qr!48K5YFMu-@yNzwH z=3n>vD{f?dsHf^bN9XtMq^>N|sMHLR8Q}M$8pQrjZ3Y=x2g)kO|KK)egaNSqJ?>k0 zGa%T$B9$;`UIF9F*C8~ya3;gfJGqPxs$d`RqPRI*$fHd6Gt??6sW=R3g%sb{8S?NT zJ)c+DPdf=N+@8z=?(~Ul;-HJgz(mYTQlff(A!;G-c6pTuwM23Z=}&8-!kvZJGts`vuK|x=$mS{KxNM$GkI=<~9%&1`VMfz;5 zM7B6Sdc|`s{`wFW%eF&QP_8?0?s`dLO#0bePWYhOamcGa`-FCktU*lNL6W@hsFjeD zSo2mAROfLp&Wkr4E}`DEia!s8&cfsU*@2){(~bdfL?tVC6I_nj^LHr!?Qy)crn&=T z%CzY_kf7G)x2&F3;li5l3({2xslvY$g*TM4raL;wB zhf@v^cX2!@rR-o-b@J|_mHe8z&1vi>UhdeHK#tQN0f=Mocn|1!4ttUUI%a6BOVKiO2@XIA-`5&)D8HI7kyy!6zc2AZ^*JK3ax-2Cbj? zFYPefV9~&iha^K{8X2jYvgY%MXNJ6X;+7{6C&8fFxC~|hd_j&2EIF{WdAAKd5GC-O zpaLH<#8G7ips0cM?5uRx?j*mSvqypZyf87v-q*3{iXLYkJBHX$<-Pf5RzNBr_jd`h zQM^3))uj6Lk4FTi30Kz52%F64$)CWDUjkaBv+AeEk2gg;54XsT>r8Fd{PE2Fb!;4S zSvSg`z6HUEf3fB9tfG7Gug)l*7=Ph+I1qfjC}ykrMPow}M;yJ4cHxi@gdgyJkcBFN z>cdy*jq9Xf1$51OfO5^r8Eh z=Qxfys?1=oG*t`BS3`ZLwE3N+qr$)L0U7Wf3Y+M*2o`hPEB-Yi$H}3fMJww z+DBmvPps`oR8Sh8-m74)#hoRu)gOwbdVu^m&FHX0PDMw(w$!MdiQG-F?7qfZ@tWz} zs`X{gJlku2rNTzn`Z4FV1O(e(7NFfwl0*#6{!b<&*11^0mkjD$`L+t^*K)F@bIS9A z`s|rHp??j-&XVSxkk6AHG|HS)Vx%|5r^bzuLtabFOd3^x8T-+rDh}>YbKE8KcMG40 zJD82}2|&4)pVx2tVDvGJDF*;>L@*>kJ@R04S7g5 zxyz=4K31QyJza*s)gXINrKF}SmlRbLR)cVCVs3o3ws5?&2;h?9`lj;Znt5|vn;Bn$ ze-8viArcZ441Br{$~HwkPx!L!H%1YpqZaT#d9YnPWELE5XU~+(hyV78o$ah<{!&1i zf1oflQ}Z1`Qb=+bV4x^w^vf9X@VA<<;3&KkP4cDG8%e?+a0J4=lIQoS&aFzlX?3oG zX2RLlK+=gRUrj)&RknM2;7Vbm%;`lm3&$K*w!J=phfZOBOF^ss5mELqSK8JfvEVBP zG)zqe$L)rfuU8r^-(~XI09KJTNU!=AJGpn0necANnL@}NwoE^v{BL&}UK|r?Va?iU zPd{{iN&*&(?^fQ*Xbz?5v_-eN2>^>gQ~w)HVT2rS!# zlT>cL6Xm2F*_t_FB8Yqr)7&Ulr=aPzKbS1h0%W?#h37^~l}lKRtzSJUtdY4wp0Za4WO1idPSGlwuJ? z57RHT{5yE2NSw{17*WDpT$i#5Y-R{p=9O<1GU~rjlvHhIeb=mqAp&JzYGASh=rnIz zA!na2%YXGUjL7N_b<1XPRn#Gc3PStcd$Fs(I;?RU+LeAYi(+*OYj^$Vh71`|l0+kPoqDC&E?RY1!M!u6GP{cpl86er!>g z*Z6m`2=FE1tH12$$KT9@Q7aMyuw*qlwbYe5=Z#5b)o*JMF_v`DUpVHUhR*>H>L=F* zlK=z8Klt$=I-bpaaH~DfL1d1|L?nCCVvjck-QEGYv9$3r(|{GKX9Z%?Img%-h-hc~ z>;Z5>8)@;N3sGbV{6Er&Qn%1l;VQv(#Eyg32TrTUU1q%OqbJL(OJIUd+9UfdZEbwh z1Y}O9?cNNWqK^#$0h&RV_l$GsMr8j?EzeeWIsG&hHUZOe{fA`=H*eTyx006evsNT=HiAQ(&9<@roSMt6no*isgqlkQ#w8 z@na53{|+b!q?HLiHIfcq9*nV81z`mO?%4teR|x?Gu4dSifW=PC(+QVJ??a=$t3D5I zQg__}AQ=X_Te6`(i&1MS;lt0(FW5Q#0&I&Y4qVB&)PJw&VxB#EZ*zFO^twNqWrv&t zVD`j^_7XA0PG91WpWw78PyeoKxRKYhoY{5IaFLImXA<=@&S8(G3_K4MD7kLg=OV9P z)b5h|BpGR1?C!XVB+BwDV5At;kij&N8b+}~<}LGQ^~B_Gq2CsVgI}Z&HqWIfGYnR# zEIE#)_e8^P$6qNXHt>O7GS+bpze@bFbOGLbKR9M@YZJnFxjtu>8L3Zi$MtPW)^sax zN~o^u8Vv*lvrjPo1MUua!$lVSh_Zn1{)(FNtSlM9_uWX>PXkLfv4Hi$W$e=A{wlDS zr)z5nQZzC02{k=H3SR+0vn2=OyvK_wJTETE<}K@A;|FyP_NRuE&hj}4n*YgTeW;ON zPVN`cQeZaZBJ<|@Mg8F{@*cc4DdVHmkhjvZt>>#{`}Jex9JwM`Dbl+h40Rhsn=_;9 z=c}5d{z2*UCX= zV;KrfRSoZe?(AaC{PDVe4+_g@=nmc8)SSfYH0H^#SadVsN!+Nd_5ZI5?zPyoZVLvs=ele*OtaUp2gB)J~0y zYl)5s1r7bgP?_G$y^9bT*?G*TPah^<3RudC1@t$kTmLbskn21=$#oTBa|tE7hQK+Z zEK6jkNR84L=dPsMar2hyx2>bVn)tzTlpBH4MUM;YA!`qnBub2j4w=5f`BkFwOn5k2 zCRO_3=lw-r9iQG4adoIVSy5xvwoF{Sg5g8zU05%5H)snF)UzPQs~AA~r^T*;3{T8E zR}cto2x^hRTYOi604#iuzf*l-(ZjF$ok2Y7;^RW=lpvYHYzD0tAU&sgbQ$hsj`K?h zOiZ05e0@a(g!k(8oq@d?7iL**JgFs+CBG*g$<^WW*zC>m?>venU|ixRGXnm_rDHlh zC-yky;VHj@?V48td?B_ z>DPi~!!N_!!k0I+Faqe*FI!EvLyyG^uL{q8#E5aAM|sg<_rFs}>?}U%XLw9Fe)?yW zKqRNFH=x@-@^1;PsPoZg@08!fNx6FiUEP*9 ztHgWDagBBHd&-~vUvVaZU@Uvu#H$ZA=0_^?vE(O{G65_Iwl}z#ru=FHRslaaFOhY- zR@BDxw4}k`I*%x-`xKeyFn`kcVcGp{ZR50-Ox&{5Gdx{Jzq@J(y6{TGPuLnj*pBlE zYAMMPmzCHZ(*;9<&as$$E{74z3nrvARINagyXA1BjsBHo2+s{O1l~H4jr(Ht9FX2@ zLgbktn1Ix7cUR|l8XfWFu#5BNo)B|${1r3HGVUN#Wl`vJhMNI4_V;6=@oel_8ly2m zn+v3e6ahKQwP6&e+n}AtXF!7EgR3QqbpSAL}^7$sf@Vu5q4q2<)=jI zpC5?<+HY3pujrt#)tXyq*z^)KviC3UOMV4q7Py<5bvO;wlFsz84)f0|UY{(hOM z>Z|p_K9^s_baI(lH1Grr7{F+Nbg6XTgr6;T+Li_2$O+?omr^yN7|nOj|Lc?2ONs1% zKL3)3-G=pYUrD|<{{!T+nY1znt)$=t%-&X0Yw{((A?rJ)9}?z@fe$?8>$eJWb-J=ygR(QZ z_|Y#)p-*h#DHR8O?m6S>6BN3Ju@aB_!oJSQspz#SF+j15Jb25mJ~@0LsSCaUOvzs&h4o#JfKxWT zr}G37WVxulBooS*CV%`mDJ|;m19E352;R%{ieg*pmp+CjJL7|rxte)81&_r8<{%{? zfyI=iKCbrJ6R_dcD!H?hQ%}ZR@055nmE;V_nAA)?kaeFfIdCaHUJwrrv!#5}Ls9&1 z&x#2@Z?X4uxrmYidf%rEey#uN8Wa1o*#>ou<`ukss@?#}e3 zlMdegM-;vw!gN_J@!J92yo!I4zPGfe6q7R5p^Tr3YnJI1gbxvd8Ge4aD@PLE&`x_J zA88@i)X#k!DQm~4Xn5c4; zdLB)Hf1qC4lE-|FqoBua@@_aGXnf~AkT=-v`8`XOx(7`XR1RH?J`lbfFBmmL|bR&i5xC zmKA0me4@HGLH_jf7bhY0+lfrpq;efU33BaNq`&Y0Ntp*(IHg?R|sIpFkd{iJm)cnnLC@^79JvNso3&ZFcxe>h$R|W4Zvz)$qrBsiH69M}f*i!!J7<@ba?<2Vhwr8^h2Y zYWp_&(6+y=+=tUy25P1x;@FYbXt_?Jnl>OC^b5W*D8=aY{{1Id9*O$7ZAW%;>(7tp zEGx&$0cq3gsO!jMFK(Fa&hAb|JCip$j9dF-q-a_@qfI)HxM~y-0CIc&F{vd}PCr#m zv$tL*1j7V~ZV)=%#)ljU3TFk^(#XS>syHdh-_;BY=X5U#rCw!x1Z^i%%YtA;i;hK4 z#DKc1ISXE4O85uua}3(>8W%f_nFHanNUkbmVlqmkrE-3uQTjh5odsKz-4=!wP>_xx zMPlgg?ijj;1_?n@0cj~gy1PNThY}E^k#1=LK{}+QB!-Z)zw7(~V&;v#)_U%H5!AMy zQE=U@?LrqL5dZX9YCqilaz3d3N(dtmTJ<}h4!Hl>ycs7pwC!r*=R*1KXY|Z>QIKY( zyZptA4(l|u>diVQ3^V18+qT^H#*G*M2SQ2tKkSB~f{sz%-RI7f=>^?{)WX|dMvP{< zk!C6+DrUt=Ypu91sr+O^wnsul{v*JQ7@O$GDum2B0=Y3JaT4(v1tA(5e{3x+GyS-9 z;$Z|_9e-6j?~4q;ElShJk7H!4QMH^)?L45Ux&C61IvcIXgK*iC&K`|sW81)NGjiAe zBZHfJoU%|U*OT}Iq~D*`=r)qqnTm3HaHs2{0OI~PLu1QOaAFsWGaWv-WvZCX`rYb< zAxEm`&ufo0e<2e%Rq)tmm$b#}$jGVz`WC;wXHPEv1FF@6bH@}knWQ3?s`ihcJxn$Bh@s;J&t~^ck)%wPFDqil ztD!aa0c`o= z8z*?Oe?DHbIQ>}v=;&1ivr6yq-<76K?ez6=I0T%%W$<4aJ$(+Q+PbWvw0k?TKEZ8G z5@ChR*lCz!Tt9}aVVLf6r9G%m)OEBVdMUc;{i;vK4Slc#&5(q_$9IuSKMcffh7LRV#47iOVuu}vbJL?aw<&S01ZE709op3$V6(K~eA{ZorNY4-(m z=Euf3v^k^5ZowS{2AVYo3Eo^PQ^3rp zfVoGGnD))pwp-7mmUr5p)z#&8vqpa~X4P&kIC!5ZC!*%BI)68)3X&rIh*I%UdXsme@pz_Ky!sJSYGZ2L=mZq%-*l^at2 zwaS+SPLJYq$&IdnZGVL@6^EnRn=Za<&9BS3YMmLQwES|R0p9m>C-MAyiB7yT{B`UH zI7D5(XM@LGK35vLdR0F_Ct8e3fv^Mj9N8k0$*bWv>>>YTKd}I?$(XP})a04pyggik zB)PX0@skcej5;k#zPwk>YvI=5An)CHxRPqlRv0Dd=Od2)_v1weg~8h zivg2c271KmGgd0x-3~-ev^=yM$^pe)u%RYyG|!kp`pt0<2ke=o+kJUfdVCVMSkKBp z`x3ja#z1xA)Uiujv)mr$E~*L%19M_`M!AM5nq*?9zFeMP3|7(w_1v0uCV!TbDHmcD zlIO3~T{liETO*u0uwL!H+aS+tkF{!A%h+L4{3^=rA-lL@X!d(d+L`;Qd~s!SQuFI@Fc`8QWSg3_#iz(m*%^_1Fe7#KnUP z%c0B)1Wa~~6qE~X>2rfG!mUf%`eMC?m=B{cQUr#w9mBU@%YoObB7ChvN;+-nnD^iO z!`bQz_c}3y+39BFszkzC!0pV$gE0zC?&K-jaqbp^{vUhJ+VDmE$s*~o7XTxt9U`^Q zEFdoDbI+%&jwA#k@sDz^H#$BqydQcc^ZWc-XSMG_j)df`a%Uu<2*Le^;(WnCO0OU| z;kIylbC9CKV4r&YNlelfN(Q+ed4abv(hfneMko}hZGoqXkU1UZY<|R6K7yX0e`3Yn z+2tRo_(!&7w9p()#e=IFY2#T)&p~1QU22!854-Q)*L*kQb^NjM$4$5QXqm@IdHLU2 z-%arjd(L4AE3zeNTi>Oi0H_ona4iuN6GMMR8J2UR4+xWk ze~N^fG9|`kQ-l?fD4Ewi{O%MS&tdvPA1nv6nZkZ=QpY`POWhklU^UIIbKzd~r)em! zpd9R*?*!owol?Jc_y65KAV*M(t)^1+$Ch;=;6Mvn3f$NgvlLtPmk@0^5t+hl- zg2+7a_8;ft`~y+BpHkn8Z6i;9``02^btBim@sSQu+!#Y!XZ+b5{uld_aQfME9`LX(%@V z$p?#2DbH=;ngd>em$cc+GfgkBO>OH~M?8$`eLh=6_yyKYW+$FI=4X_xIOwo$X<7fZ zCsTm9+6U2WfsWvL=u2vvRKiB|JDRO)J?Om5@}E$@G|wfHmmgECwTE`gb>DvS7zigP zV=g!dW7lTpy^QEdLy@{gav{It1w^tkxvDil2y4^ay?D4T4P*76tMBZazh7ZeF3}mA z7zYK(_Sr}Lm#zj=UrFz7nvv#rY>AQc{6y(SV}n|{iEeQD9uLh)1{CSeVm|Vyz_pAA zQK>r$3fa3C{E2ZmYXDuNOX%lRlMpP7)OB)cDsMU>R3KnWk6ja{0;~kC`ndDrz}GSZe6B_ zd#ibFv;824Df(Xw0W3%O$txe5v~cPN5Egen9C5KoiNy+*u99*;YADuN_vQ>6`<_%z z&Wo`T{nb%UFLWe%ycooE!Whp+K^{GW z%;YX1d-tv64bRhX#UWDps6Wji@1c;bN(7?(v=Z8g0<(Jx1Rd@3{=M}7+Wy(u7`uzm ze_&(OA-$a;B~E?4xj5g2G&TEhPeb3YgNkjsd;MD9`DSp1OM@6M)i6HQ5OH~l&GbZk zVzUOFsw86q4q`Y~W?c?|Qf@YBQf{SQb;a!ql16)oIDTePn13?=)v-S%sLF&D1SlF? z%QJ{ciH>M8#ZBk;P57eZ6W>bnWb#uu?vWgdYAy>Z zQ?qBj z>vZF{C!>448Ka?=zy=w%YRIV2c*fOHsT7WQ!~U`*Nsl41Y;D{ho2@ravFm$kdPFhV zk-n;aZY19yj04Y?3JtUdc`J33#5!G8gbPO`@ly9F&*yV-w6!pB{jg7emFSvwryaB_ z;^u@R$)ASPgJHaO^?YV#?s~|kn8oJl!8!;DXtKZLb=o|K>9R&!9`+Cndfqvhw|{sZ z;I~w1 ze1NO(zWPwJ>?c|68SNk#e%#-<+~&*Q8DQV6JGcR&SdnyZ2fC{?sc~Im@$nbJ(f-MB#*V&Yp^%|QrrL^&7|J`e!BKPxNdV@F?pE$ zS~7J6IuY=&dku&;OodOo$p73T*|mUhSZk$9cMvwvEuNCc_Alf5O)lrY{UOF`YF444 zyN0PNbQcMY!ZkV)#Aex&xA-|Kl*1PMv5?9lnE#PJTlCf$)HkL1Q@a^j?y<}2<)DiR z-pBSYIA0vgW~!rlJkrhSlfyl4S`FNcM4KLowqSNTFX=;u{i=VwY~KYVXMO>>jzxu3 zh5@HeeIJaZ8TTMpEqU$dtLO3?pK6+14+Y~kPjb4DPr^%W;8qo`7VGn%R}uC!3g}zb zJIX%ASo}r{6mHU~&A;s_yceIB^LE~?DW?k&dqBIMezA0V|Itzspr`{0b8fns!6wbN zXzuD(uK%SJHLM&wB)BS6HPDVcqk+Xk(tPc|ufHdf={!~JeLf=SS?Yw%N$|&(P|U1S zizQAOGHuo^dE7v0?5IH3zQMaMDxK#hpuflhcTPsykkaFGCQYlITS*j~V&g|`zrN4?DQ$74~ zlhpOjL$yoPjbY5K$jf_+gbC|7aj8R+>YePdc!#VY_V+?xY0g}}wB6D{$XmdX1r-%` zNi7`n@b+)G>t*ZP!vDzmPWM4icT*6RQrnYdAw2`2==2_>_E?HuH5jJ+PpWrv*qRSI zn5m{AEYT(n2~CN9!ms`s+$wqh&WHug-5HlWCFJPercZF`5{>SKuRb`HmhOT)gH~ci zu7|oDJj7k!inHr|o}V~ZSCgipAoweVO*>5`kSuftcyg}Tna3fGMOKA{0; zDx*>*d_tJ+J`Q$^_Ck%KsP)tvEaTFGw<)Sno{$41b{ZCTnyT9PSTt+53^)$_XJi?w zzWO6SuM5b5`3q;$M8wYvw9OG06LE8V{+~^9qK;E`KMhM0zJra#>Bp22M4-7Hk~o7`ti)#zTT_89HCN|pst-`j~H2&eKLPV&Q1*4 z-=}p1X1^NptY1S-=h%Gl7(&f-^}0R91*zHreeR6dveA1GuET49+vF98Xm^ESedsL@BYIl;Q7PkP!a z-bkdI%@-GBb$d}uiaX_t7A^C2tsb@@LgsCmVqeA8&_L|gIxF%iIgB>69kM?sieNp{ z10J7!b@)8=QmbSpZH*4CaLi!o6hqF1`rtcKI9~ERr?0S98Iu#Ue#9gDOBGY5av2yY zvgtxp62 z21v~h_hOXd;&R{?N$6a`P|8%`eY_D^U19lZ+s$4VRx6@={Gpg23IsD-mJv}^d+M+rCA}f*azSHkya;X^m>wwB3lNype#gq~w>eBB z9aUN#&`gW#CSNLOl=O^l_o>s8GIY@Jkzr|~Vf3E%FG>S?C;Uj|Y`=eD(aG-L>!Oq( z@M`^qrUNj@j;Q~EKqMr;B+-Kde9b|W!Ec8g{=MCV)!DE$oY!6z+lRc%WpTib2b%~m zoQ+v_h$w3#zw;%jjmcST1S{Iooos5_(W`ySAj48xsH~oa@R*hMZ$y+u)YfOq{Fe|c z5rBSwIz6@CFp4nHOsqMmxux#E2pO$P7}a{$ByBT$@u8a<{%G*rssNWD(SUV_j2#Q> z7W_~ejF$b4^GO_3b>aaz^|X_lZPnV7VCn}#7@T>lo_EhtDP(Pbsc96k{hBg2Gkxjt z6N4Yu4KLzTtnot$vPxZg_Md4;9reCjcp4^?!Tza^5hRF|U<0Ee7DM~QM28a2N@ou* z;$C)qGPpcc)RPy?D+q)Fk%SDsE6~Jz<`(K5T0n!7t=&pmkga(?p6ED{!_}PN5#{}O z;KjRMexlLr`l+IRtRun@f}#Qc<|58ML&GqvN}78+Y{ir?`y>KAd)K%9X8*wN+*gWd zQR%VHJW;>xXA^0-?hYxdysK2J=lC9|R2QEWHi#y}cQ_0n4ug!=CBS_);pTm|w_71_ zv}NO#$qzV!jwwszHHHqKTRDPR$!BcjnWWdgGNq_{!e)E`I)X9ig(wnxQ%vozz$ry{ z^;OjDYZnE}(ODOXDZA3Kc4N*RkSPS2VGw-X=h$Wtimuj2oj3;;6N!q_%>##kt+&9K zL7Ukd0g&5*1E^6jN)20*>KBLb6zdbkd2{xeAYXY4rI2ZzzQQZ-jnRmD8yT{wExa>w zej_yxyL|bMDK!L1D7;UPG>)+nT3Y63YM4z4TqY(s+88c3Gu(d@l=(!fps(%09&3)Rv$eTvz2 z7*&X`%*Sk#^}ZenFT2kT)Yv9W5IpN@o22RMJ0d57_uY$8G398$s|8LzP)lLJKhi*Y zsu!D-N9#Qt2e&ny+EXGh+Bjme;1Gt$*RTkuV-SyhH0as3d&5(TP1Sf&ToV+oA7m`I#@V>Y(Osn2W558<;CUkuTd;1KXN^tVcVIEQstsW z!o=tbh(8U9_QbhShh@-dBH?08|L@;qr3}0Ql?c}4pb=N|7kV=M`HoAVlNZ@nuEn|| zawK*lubwDrO42}8l z!@WRSts%=@9h{z!<@u~pvf}*YOR=t5|oD{)vF}+GK?Vn8AvQ=3|~$p zW!PgrzL}U<&A-efr>k&?OyLvvOYfXZ{KGo#7(+qEH;u%=R%aC8bT_0be1v(V-%yzm z0yJmWfp~Cw%kRDT^Bz1L(Mp$7-Kgl-H&{}%7;@UBfrZQtX74@2y*)!Dqoks*f>dvR zuLXX<#>R<+zBmheF}SmG6kk*sWQ^rJB|`n$N_W!M{AQa=cyH@{Ao~*4^E6@+Mrg>u zY%qq&e5rmV4?q1vDZD3+$cfvOv^FpDRw-=6Cs(dv5%P?;H5VS!#aZ)VnfA1#?dEs% z*LS_=C~5I{b%xciDW1-o_#`@%2)XXR7)h#%53zm58)M#vBxft~wK~DQ6P?z`*$7Y6u3Yv_emS#7PF811GVq|^LGDNm_Q>vCXj76%L*9{Hl-_i8$zEWYjErS zA!NwsuoX8`t!X#Q0^j*IE!@7zA<=GDw=O-aQLmZpfek{2=xgN!T1d2ykJzSp;ZlM9_e zS%aMx?Ug$k7X96S#$Vf0yqyif&UuoYcOPYNMWunQ%%U11EXrF-VdQgAe_{KWpu{P_ zZepeUdE(#gOT-@Dp%--V{WH0E;r{wl3!7aMK$p(3FEDoGvAt}AQ!?6$>E z66B=YRLTfuqe6g#@<*R=qorlY{1vsvK>WgJ)U&DK43y4Yl&DGGxNDOa!Wz$K)YfmQ zfGCNU*B;)kJHwwrCd-i428&_R-vsfkmfJe#@>a0sC!3I~&42r<6T&^NolIP5ZAeT_3%d zy#0Z-x|up}4aHj2heLR^SSKBpLhAE(jcolQv<=7wmYTegCFW-}@4Mbda&`Hl$Q8kv z(nqiUrgD3hl>f-qX4_MH8qo{}(mmVZ81fA-vfWMATW#n5gfboA+~iJ>9xrHsu<=nu zYr4{=A%0j!>v!;Jf!0uufGvJlkA#T#+N142nM{ql*rSC-K}?P(uWa_Y#q#b1m2ifV zDqRlKbcrqAo!uv7*BCSP`idp?J@xto`nasL`Zb?rx-FLAOta1`!wGKH8(C@Cm_;|K>Zw`n6rw$lpVhe!nmeQa_49 z40Vb(5E!dTohSqbx=QC?yS!2X^&QE&+3EsUNF?y^$`7FqQ5*jbf``Du0KRPHk`Ngd zOu8SRKKSe!ZQiGCCMetbgiJVs9*-zPeJ<7eF%|#QY2ZGzD%KOv8y$I-z%MZTBm&B_ zh;5o|6fVZLow(-B`QB+UlsW{Y?h5ta0geqrP9NX3XWx2^OP^)O4e$|Utu%}rKPa$M zvDo!Pt?wr!-Cz^N5mz=S&|Jzr*J*)RwUI(=u^vMk_olb_3<`tNrJuC*Lc6u$hKcII zv7{f2F~s$}4EshPw?7h5Nj=5S1o%x~7ibeD%af$bQ#Dvxrr3buJlo>zggqD)MnX~v z3KWoe1`|Cd$;&{_3*AXO(+O`ms!ooj_rUlCPSmVBAZtEM`Shgpe+E&kEMz1Key+uH_PQ zo|&&6eX%Gz^>c-Jy{97#W~)NV4IIm~dnvy^wS=-7*~85k-^LiF4Y%0(e*)Z*%JySV zDI|`rUV$!z9vDsZTIJCs$i7{)(?{DYxR%Q_qc7F#`+QB&Ef$4@(WW~J3oWv(`b^-u zt0#BM#W zIP%PH`zrZkbHc-f%LToEf933yz;AVsAKPT&wd)&wY%`W(%ohHpO`s#1^hn)niK zIx7>3?%r;}jUSaKh$_S%LLrqQ(Q~5XX&ipvc&9lz zW@_+KFs}oLWZiy-Yg%}DJyM?$aekFV{cuM-od&DWeYF}W2QC4%-d7J>DP-dH*q#*$ zy~)Xn^Dv3XNKl+D`R3cwM6X9?48eip#DjOSFO48RSx7cU=p?tNzCt5SaXv8yOnj->p<| z`jM7Khj1X=07i^G-6#XIRm~+&v7kZHeW@tJYCe`|HOBGo-Ud*lf{@|At)0=pi2l$g zO^1G2+>kpgDSQEX5B1IerohMx)H=UcUilc`q$|Fq_z9{%-5{K3?AMMVgY~_7TNwS*1Jze50>g;aAG=hKXyMWARgA zh#o_Z#`(4wb@80jjKd=Mpx|zrGvz-hgmcS_=9^w)i%cH-g=uC-1xXak^*@uNc zm46gM*YuwR>=*Gt#T=^@uMu#`G#zQx3RQgl$&3lajzKgj2$McA6p6KYzC>`l=~Dz_ zwPx6FVU5oB>5NCoD3y`SCUN-cn#ji0qIHp zGVgA+N`lC6p_bj-xXmPu61y)yt@wgo5$@)ocYBZl*&RjUb;$u{*CjgWS-6jbVxoSL zeInp$kUz^2n3b*rHLGdaN1{-gzM%F~1Y2hobEt+KDiC1th6mIofEg!nN2+6T`1OVrJOH^6 zOx;+R8_me6#fL%H2Jujys=^?k!$f3NqxQ3h7Y9r)Oqr;HYxas~>{kLtln=e{9|6ae zPrq;)%l>PP2@a6s(&wP=_2?ImBFzu0s+e;44|%VqZC4y`;`r4M9b(2n`FBa$wNXSZ zN(|HU8ay53=GR)wUcKjuwIN!?t20e+K?451Y_6%~GZA^RfS#3N)GY`2AFA38cbKy^ zc+9Iey(PCTahF_-Bn6=u6j^BrZk(%LxO*`XCq#)stTmg2A(C;wp2E}-$v&O6_H9S+rM{H)~_S?+LqMAZZ5d`9hZi>aEz zVjUQ)E%vj9mGTH{rR1UJnHLv?IS7(E|@!)?jQSsM(ilShj)YX4rY*4Gj2<8%*~|B#1{em#(a z!ISsx4a|o{e$-y`<)a5A^+}IZw?p`PF*E zm27h#YI`qnGtoEc@#cat%zmvUx17n@&iL;t!uoJgEYpEaF&y@=7}rbuKz46HFf6`H zdf@eM>F;W(n^IZZjV9dXm(ZS+(ZjoZ-ES@u4)$(3kSBUXScdo^%(m5SjI5+?X9SP| z1$2ACrB4ChnFxJVY~!yPBequb{va(Sk$-3T<0bDMwM&O5Up+IO$;f?4lK_){ePE9E zpK^RLq0WyaV9GdahV_0rP+3!c(Ipf-oTAkmx`CjUr zdVq#(3icQL-*GER$J-CUBUff6fk=t+zI-7ZvwIs8k4q3n3zB6(#Ptzq{*X8d2;pO2 zzfQBFcrrDawbA>$0noQdH}ii1gL^3td;_;jw{)^q-hYGvka~W<*-}j6|51?(O`~*K zd+MJ+b_gBjb~8!mz>|zCH~?bGer|VMH4Tu%VEzb=_AxH)wx{;z8m#(*I~k?Pft`V} zAy6~^^$T7kw3GdbtivmZ#uxSE79g<>7g0(4Iqs2On~Z3%tG%YEdCNpxvhsswn#>2p2@ou|#)$Fcg^z;Ll1bz!QYe){RuOY{44kLUf?A2ruD zg#{6%i7F+F7uUd$5#9cG-)wP^mSwbf%Av@fg4+9`4YdE(X5Ui}4*f3YzwwA4{(jN6 z8ZIM_x1Evt!TaimB2enR*5{O)e?n?eXRO%rw^b?4E^3Xuc&oGd%1ku5^!)vNS5gguqsv2Z zJP(gx4=^JcJBlqX4WiIFsjZ$@{2&?LJ_8#sq%QrqD*m!T672e z%x4B_9MGn%r$;vZpo;W@K)#|235CPIHiz_(RX(x z9rEx93o`cSFV;4xz4M0J5}NUS2mRaT`o2`rp{@4eKQ)y}IuO*4+I%nZk6i{oVcaGo zH)ppVXLlr@^)!_`MoZv{)!4_AN9-Tg=q?Tt9~zsRhpznpV$F^U=8mp?)0zgO+((~m?G2dcfr z<%U%l0(vqkM-Ub^Se>p00@26qZsCxfQ@eKCllxy5gd00KmCYdfGUQkVmQ#6ca%UEF!5bcEOe20Z#c)Vh{0Ttw}3y5Bz6R2|XolQYdw0GSZYZ zolNDU7_R()d0H$z#|ZlPrbC(8ewH2u%Vh<_|8jM3 z#N$#p{bhG0(H`#uuposw$fqdw;F+)bF`qk!sOddRyA%}4sUI+kD#y$m6VD|F->9RT z2n@2w@cWHg=6baK77Vd)r1iAH)klZO>UY zT((5|rfP`?9h2rAY+TALM(iT`gM?-dn3_K_p{`Oe>9N*nNAzvd{f}QXQIn-6^NGjA zQ6q+<4E2=5mm3>478eFD~EfYZ#R zZmNl3Kg07M`OGS^?;ER9bi1FJC|Z>~L&2MOnR1sHj$qf9S?9?VZ>FB9UubwAwTOh_ zQjuUQnu18MEB{MD0Fzr&@bZi#DJuO`FPsT^_wgVKB^`lnx!8Kn@_PdV9Iz@G{LZqr zg3O&S2U#GY>-$O(#h0`jmrq&^Mz&Umi{LJnvps) z>wf2*>~=H#>(>iSXj6vjauLCp8udnES!cBC=I(>XY?EW0X-5!V`&G^vx%?xQMq}&k z5j|P!NHh)s+MNivG=7-FFC_g@Q!uL>%cgRdPd^%f96y_;1C@NUWEO&(M=<8Vr$;sa z&41##VI*P>7S-=MFXw42tYTS4{GVP_I*CmMNePUCK<8rNm@YEc$4WafUBbZmoO)-O zPih6$njk?OP;h&!M5Pw8>%HuG-1}edf*&@BjW_AE1dc1f`n2To*%W@E2k=ed${>!+p+C{0IKkGv>Y))aNDR~ zV2ljEI`}ut@=54uo)L)u`uj=gcPn*CS?*15^x(|IX_u98o0#w9UDIl6vdEs>jF$z( z`X5O)SDLG5Zgzb&uaqYMS~6SvE|MqRBc3KTt=H6D=CJ0!wez)&#;GbLX9pJta+)f> zQ9-WXZ{E`B?Ob9N;g9mSTmtzC%}0#3UjzKGzY7!Qewp)^8royK3k~m*XdseK>coT8 zUwv9IRAZnHO9j?TfIJ#fsLXNxBEj`YS|=2dqmfxiz%WzQpn|asH3)X)(KLOU4}7NG z+JVMJnO3iGtb$pSQi_#I^_IW2(hv0^O!33OVPwFHdX?wfvpwYX%mklUS4LlLTkgb& zbrBF5jsq$Wc&akYtDaH2{Dekz5Z#GrWR5n5)eWDLFGS zH%_p5XFchEa(!*#8bv{lFVRXn*q^b3`;Fz==lQ4YvE=5BeE*f5h#Qnn#dEPZ5CZ0t zcO5*xjr>-bl9l-c`0!leV!%+}ct`--oE6B>7O2jT;6*hpA| z`d%A7ZBaAcVorEM{g~27Oe0zM$mZqNNdq>I1?7A%uZO#7*_`L3ZYWU7o@kNl!56_T z0`j^*`5NGwg*73W6Oo{p=Od#b*qvlj6(z_)>^hqb&f-e@i}g=<*fCJZMc=MB%MRHg zRA?MgTDLsWWRxlrN`BZqhCasnw!cm1w{?FE-bl7Q*aRGz6H)XT{`kCxoC)K_q2I*B z>AvcD18x=|Eol}Xp_bdD{V3}81)3%7J3DNaC4wIR7YJC=PH37#U?N$p^)JH*#z*q`EmtH0FE&F?TMH?K=MZ`nqr~J5D7-N zhtBjoFf9(4Aegf{&!isIX}|cYUSzgeSC(h%*%^kw)K5b&11{ww5QPJGeMT5(^JOOg zYdSgburZ4(n<~ntDxNzQ^K#VRmH9ZVx%z0m-vo=z1XUj2jk>KnpkIPfycP zy@Q(kb{JfVF=q!DWT&ioQ^51+ytN^@U-TsO`PtsPJ88R(#7RW!NN}`5BwfBJ&RnGe!=vcrQv)4D8oFfcG15$6sUuNzv`BUr5@Zw6F?Ip05mrCEWWUxUkOKMH0SerGjIXV@Wd`JcRiJ`U8T zEr@T;9lS7b!auK3eS(WZ&^~?J@~z_~H8C#>q_hoi8<|;|MTSFD4vk=+gK>v4WIkb% z-(jA(-QOtk>rpchp5(3giKwK1j3rQ_XmsAhytr#yy``19oihUhXV4!nWQ8))IH?vh z61-IcR(EXO8S9S;g%N#F=?y1bl&8+( zVe6AVR#-M)wup32>L$B#T2iNPsO_89Qd8)DPesA*V)q6t_lOvlCasksHI1HNn0yn} z8ZHr$uV0xoZY>r>L)hgoxDMvFIz!g--KsM{U~@jr=KGkyQCLuX2uQ_cVf;90u{dr& z6*aB*C+UGqKH-N^QtJH?Eo1C4x*zg8dNQjhR;nn#50^+BW1vlhEnk$Hw1=$W0;w0f zc`Tylxs`6i8Ugcf1fKfG{&Q+{eDi>Df8@itBbDFv1P3X!v|4BJS$F8%@_j%&w#&(+ zds)|DxX2M*nd>$9b-p*g!=D0l+C2h56y0f1KERf*uq(O+8&P`ib8lMB!#%loOwT|z z%~8D9bHd9f5z6I#^15hK;sp`)U5Qvza+4a^_(4n*)Jg^elelHCZP65lMAg&Nxi>H? z-eKZaQ)gvs&@SJ;ZT96#*E9f2mZ=XX_Wv?R5S3L6jI5= zALx^f$*<0_;9*~izkQ!Km?ceynJf3n;*HNfcd(q?;lhOnwp_Z|(*hgPa}l6}Wuop% zy6QSkskwaVx+#&E_U_Q_ln`DyKeUU5AL;Dk(hN94@puW4#k!oeYm}j=lbF~AR>mN( z@i1t(?7+pQEDjCBT8p&aOhyqahW3vD23n&Kwwx;xDuaLW-_EB^v?mnyZXiV^)q$+f zy{yqEQ7YpD&aFJKc|^nRX!=n!J%mcCd~^$&AGZ>??FbYyTkW6F zhAAr4EjbdrrUqde@xB49i3GKg?;R{rC&K*CJ+v#ZRW#EUYHWk3XfsTNFc;cv@hamb zIxAIu(El7BvcIH8w&s%&VFo3Mch0<0{prL4^&bBYlfPxii*##_>z!Sf1Fl`!dMv&F z>ak|OUL$Ng94v(*YX*mYfT4&_(1Fc_aV<|J962TqqG-{2)N!Ip@F?oEWl{5DsaolG0S`9vr^t~|?KUkMTCx1bG?11Gv5V=6*e;Vg_ zcZ!of;z_z3Q{u>W<7)pWe8pudmFTlv6?pR66lqk>B`sQ8MxaVeY(~Eqk6<7Y0<|&F zp4GI;HfOM=0)m;gLI6KBC});7v;9h7&qIE|A z+WdI_TzI?qwbyamD(%7K;I6uYhHIyg{||v{YofL|xyE{>I$gpmx;&`?p}YrN@Y?Xl zuGm%as@PoTS=q)Mch~+>0;=LlumT2qz@b`?UAoK4jOn+t zj{};3sUwug!0orYDI53M#b{VAhq$U$znt7AE(as5tfy>VHeV(bKe0eU`eMPGD%N0h z;ob0|@5Qz`54ZT(@81jAD;RyKa_(MhSm}vO(f&ZvLqK5bxDUbd24ibp*UuajOlULs zf6BmKjEK^xLy?@~0$1zkrE?6NlqyS!2U7B&CaE*EjZr-7fq5k#O-2QnMhgu#(1`+S zj)m!?aP1@&E}szJ__xXYD!VURH?9dZ#$|7vqaLI#w}diYR!zSV1N&lHYk;6Iq?GnU zZ&Zx%lJmd4nAq5hPm((#G)2fhsW>#3gkxVCHv+=$*;xxuNhAuK%py}Xst{8X4zy_> zrqg6vGHmlfExc4E@i0>u|9HvC?bU;s!XQ)YhDzESMpb^ONMaDpH>aJmgur^)Xq52&3uz(f-@EUul7_&K|Vo-h9nI^f~kH z;Vzydv6!yItT_+`Sozr4_vCS#2-!qs<{gQTvSc@R`z7X6P6CFVmJD>JZUwhDE_{7Y zdo}Vin0wPli^~O;Qm~95_S9O|c>@zRWTFNR5D|hTr#PjvM`5O)BI0oJu`YaK%X05I zXBwix(4V4O(0(sVX}GEatjKgllJW6&KmGP!WheCyISN8ypnuT5+n1Vx{#4nve!&xK zR3%aZUsd!UPA%X!oQQpMl;~F{JuL3yie2oy^63jRtq7jNN?}f7oM{%X+BoOUd>$kH z{$bo!XDB@Hil6dcEuZ&3+#|KhdMDF7R16cO>Q0>;D%QF7r>TEN+GoZe1r_IdH`^)Qzi zay$+d&HP{N=C{qCs^6K+uYKc!6>)8t|`xN)j6%H-t0d#*Y0OMwVB_Z z^&1bKJ#!xqYNDcG%B(Cpt^=|N9$H4jmJHGwY11P8cifVqW9xe&fEovT?@aXB3y~&I z_28hM%+OhVzS{_KqdECt|8j4+*WiyIzEwdo2ylv8Z`{&)Wd0L#%oI*Ct5P}AOkONO zgO3d)Y|m{z(EchXt(8a)41Am-@Vqt}qMVImP36hsj4iIoJq22m{Kgz%=;#2OJCx!N zW)KtDQ5+b8`F*AStteE1ciOs-`8Q zyUg9%3#T{Yd5y_xLs{o;peb4b4bV1vgWbu_CZrSREJMl*PGUU?7PB5#D|Te&iiiBpVkdawG>2Q?#0C`qQ)NNJHz$K7G7l z26G-B*(Fb-M$4eQe;d}_H_6W!b@h~g;`D7r=~Tz!rh4ChODP{+Ll1(`cUg%!<{}61 zL>J2;3f&`2b!HB@@-f#wm~n19)&B0SGOro*k?<7*z-tarO#x z<>6witMYLlZgrb{>m`4B z5?14KxWC=r`G9Xh^M=IB%!Jd#L8q^H_WGgxw(^x~oSP$=UsNSY@4!l|Wt!2`VJH?y znp*t_wQ#!cV|YA54|DH@2#9#d9{Sc_C&{Rh7+H{=tJXKS&3*Ca;tcx~eKl^} zsQ(W6?Y+j``8Moo#di)dGUmf~-oM-O520CPW}Bon(8!67kD2F@2}ycwQK?Agm`mMLuhwS*^SmTyqE*+q z>%IEZy@Vcz66Czcm$=^dOVu#rUD4o>Al*bb0+C_=Be9r;GP=B*Sh9pSu|SHf+sZPY zUOkaMrh};*;%@JPd!(PE!cP9$xyDqkQM+HK>ssK)kFWU%Rza~3?yacqKB$84*^61! z7o4{WOqvo)-%+ny_ek8 zCx(t&G&UD)0qJ&eXs(I(Y=~3Ch%rGa9zuJZwlCUU>g^w)5%4i48J5H!`cZnZ&YP~R zHLg2CJN-b9)lm=fB+Ytl+hDbih3QCva`0NxwuED>@$tt7)+I7rQ|2fX^^)XNLH;MX z)H}n{X%?UDAM+!uL*OyYaTcx{+8z3(y=IS|=SWr}Qbdr4SY5ro;c;tKd>RAq{jX_O zlldFy)e9k9sX*JD5^XV5z4Q5yU_7Jj;=B)%5DqO3^_DINq0y}O1IQd5<|Ci zNJuv#-QA6Zba%HR(%sS^UHAR&TJVp>BF;H`?)?&eSoK6iZR5uLv z7TD!^?nBvRL9;#b=gE#Nhj`=srCTsk z0*IvgALmFu@z(q5BQs||lwjQWAM#k==PC2`80yFZ5}fOlN))ZilE|f{-r#MF*fEz;}_ThlX0DA6$nsrIL$-G%GEH%IYz3KblrV6%dBunn*A(Z>2@nCnUYY z(9+8Pgg(C>(V?RPpSR+9l>3d2+Ul#@frW0^r$)?p3nx3W^pV=R;(yb1W>O&ddGJgH zes)7OKGdXH^=AN+%1p&a=X?77-X@BJ43za-H5RS7A}&B#R60K#d+Nt~Ta+0FJagIX#dFd z;Y&~mgQ-x>;QhNkPD?G=@Z}N%x5`+8rCFhNSUx_dWgki2FWpAY`M%^-KRon>&CFs> zWeg}mU!C34YV(0J~0ofmhNPUM$Uxa)`AvN=K+>K?-Ygb;fp1=mATb!@MPJ zYQ)>2!8W82FVRdxBqHzrxzuxlC<|J%$&<(CSGx6{P#hrp*bpaAkAoV-9P6KWfwRA) zd=Q2moWE0<%#9S#c&!Wxi|GwG=Xwr+(`TOz7`GYBeV#zJ=CvL9g;^~uS*%BG)dCk* zNPX(Fp}-Uw`6;1FtELvALdHh0Rd!VU zlt?!6r?#&_Klrr$!IXkwa=A2YuH(G5j`P?^CLt;{v22BX+37?Q`nM9RCka81CO|l$Y~|OwYr)W}EOo ze7`FR=@cSgoRGA%ewJyK9R&A>>85bOGhRc54EL;-JblJAuZ;gucZHX@0P+CGhuRz*w>m`(Qco4 zHzHWN)iA3fky0$s@@mkqWDmq70cbgt(h{`QicHY)ba{5(J`a`ziH`wK>H$~|b}ifT zY?bX!v*xo;l~CYQ+gZtkF36~bWs|XmXq6LDKP5!#f17CUeJ|kRMiMJ>&zb=4@9A1q z7nOgEzx*$L?~IjFe@2t=#1e7!1+Fx=gN!k^!BPJ{q7Y&#eDAr<4&-+0Ctk*v9Drqk zFKg68?)MM(NunUz_q|g(hUqYWxiq3d`EZ~kdFbJ+Yk!)09|BvIDu`}A?? zH}gc(d1Xy!AhDqzqE(g)0-;%y{55HmND;G^HZp8YaXlZ9cv9VMJFb0sA1s8IHzF-& z-i_qwI}yla`zC}c4^MfPlN8(em3A7=ClW)=uV>LJD@3fLl@f^1)Z^#{OZEX9x<1yv z?i?AlVQ-|CF*R_71%gYZr+T(pf_uX42_^ZwXyG5Gz-NqPG3o9aqiX$H2`*OCz_e?k z_N1F%kh`dj#c$}UE4oIfEKe2dP1J0P7n3I@4aMyL{a=!+Iw1`Y+(%aWi6(skH*=DA z!L2CJ5a0IH^Qk~7H7!D(Nfc}fePp<&!;g-n-b>BN@jsNvZ@o=4ETE}yI9GVMypW& zRJkfC#pmzADcd{Ok0i82rOYN0G5E_a5-1@(Vt!{1>eq|Cav5*a&yY^!EI*75dU4IC zfO?DQZdY#efLu$}_g6G+&+u$bV7KT#c9%ePtv{gkmTMG0oqSrRA*Xx))&&O9%%}GX zrUz|YKOpNqk5p@(M{q*E6+co3@}XI^vmqcEQMajbfyW6A#x_pB{9ld2DY>54f3PaubT#7TFbVxs0%F3of!T2->_ z2ka{X2M34E7}Q)~+?vtk2xs-hNZ32d=A&i~8Mrid1PSMl(D{ zP7E*7SkV`Z0h^%RIprdoummZPW`=BL8VI5&55C>3%U4A1yTH<1FKkqBjU+T8>vQ33@T7IW1f)QnBOm6XR+HkqCO4hk;rJqi?g~A|e|@i< zuzi7o;Tu&5swmk!>vr{UxKWt>;#Yz!2I9IbYY>k7O1;!%KZXcGq>QZCI`P2XYOd+Z z-fv_@KQGR#1`>ni6OoetLhz1T7$Wq>IHM)sxV@N|;FZtnW=N(ROJ>`N`QOZY-flp% zd><4R{@%Y0c`=Q?j?=gj8%?aGjorTgA_<>S84~ z9J_r{kr1Vh7h3~VJObXXxD7gMMg1PS5`Evqd2XV&GaXN6mQfN>2x_Y~^SXzO?Q#Pj zcKSDoOU5%&t+2sWu!xGvSQ2@z1Y~piWrqQ9AL4l59&az)+^{RqM6d-r5cEov|Lmak z4;OT)!_f%H*uGI+ZzluRx=+li48#|*9^8cAEu~gx>Wu6hyy2m?x36{`T5tGn?6S6d zb}9A;)U-^uB`}mI_R0qsyZ=5g^f3+fDdFdKCSZI<8dZWC{^A{Unwi%4LeY*iZH4;1 zgc?&aX)H9TcuUH4QrWUfkx;hRnz!%P!5_3S(yvq>v-+X_?m$koXZ<7kT!2r#k;pBL z=u=?58jIk4UIAwa>tVG@@fklS4@GWqXg!k&e^ty zsqimsEltlgP%&O_y8XI*-r4gge#^_J3a?bMXkk*x`RERUrUlgapFF>k@N7dyEB;mIq*(b!LRPCP(^on-J6Mm;*euCQNJ8%##TbMUa`rbz zbKi&CR=3ej=Kx*6nAp=*^B}&MKW>IB%qhlRQsZmoGX}o_Uot&CrJ6Ouxesxh!_LZ0 z^-W2m&!Dd0U z(6`QGSbP6r0^-<-$&^@1Pv!J)#N_Ga`nLARoJ`-!S9IE+KJlunUo`@jojApd6>;%C zg%a~n>nx`;B+id1%Y!y3yFK`U1++LrPHgJCa2u}k20`wX+2JiE&YOo#Zj^^}L(W%$ zPwCX#^RdppL(heF7pDE?Zw|cS}+q!(Cci zFVJOe*JK^S5W}Fa)2UrGvY#SKQYJ*d;7Bv!efA9jj9 z)NqNJuMD-XyCt${;4#Yo3gJW{BwRJ*2ft@VzLX2bwKvzPG^_$zUD)RfIcW7CH5RIA zhh?OhGY>$9BTz)+v3q+YyjW7xbt9DXctTd>SI!du6@ua;woyxmmTpXT#hTlWjuX_7 z4igvHV`aj|U@~LXGo;OB=UhyMT@^h(`M+Gq0S)_K@&wHG)IDdr&xqb090iNtHoWJnU}iO`n49U#JvcEa`BleGJ*PgZ5!nzZRsqD0r^eFr5EzRUJZmyisd)ydddth- zn6YGX!Tf5oIO;9F&WaQKv{$^o$zRiv*gbx~$nYf?_ir+{%6l(L!-wZ1wd&`m<`9bv zP$5V%K!L}=FreX1Wu+Y-=&#tCx=Hxt`Qt2*M&$9;ebRpqx&QKXuV)*y_YgsHf{!)U zQXJZ3{xBXqMqQ)?qEkSbrHg9gi~>m>v+@_rpX9-+^OjeGgk65vPUnz2Yl-qGm?BBQ z&2qN>ig`$*L*rGJ>Ync}RDBONnE$bOC=oxZ3OnJO{qBl@ajP}Plo*-8(5vlxjL2x_ z=jV*sIQUlMvPo4DG9;#Im*zH!uPXM4n&sWKUW3yPm5+GW2LgEk|NMb z*5UJS`$jT2GCOm7SlSv|N#tZHHpZ390LiW}@Q^5B;m}R(`nc*gSsYP-gC3B)YnGOs zx&KLV=S&h5ICG0DOMsnp#Z}iZmLg5p4KnnQLXEsazW+~vnBK!-fz0RA61$5Ljv94`wEZ$p@3+|`PS`kVR|8OAfR#IUAaqlC{ay^oukqiM(l_M56G4^_HLEeT&M)8zq@DF1>*y zZJ?`RWBWq&b(=igkYk7m(*%NIuZeJQEqr%4`<6z9sg5eqpy$q4QcX6y8vkL^uD!Pv zMOto^EXrj_dme*I{Fo;lCoR`zBg|G*BgqU-9D<9fWzy(ch;iH9PATSnN=S#9rMOO= zlqo>{#l07sa=1=M3W_70^hkNNl?sir>Bd!Dq|L^+ zivR9jvcYD4C83Ioy#SKXfsoUs`2ov7uHF#9whFZ;E34i$eTgs~t3ywl%IYC6*OBRR zONN^NQrejAc6aTf8buQ%p*VCGZn@perBz_^GUIfC*4ej2l?yCaxUy6RCD@zQf5?y@ z67XfQWi^p*`G$jpdKwOm`6A-tn>L{+GGDQMIqgGgGnOF<)rW@zbv!-JU;JWqgchh( zVNQc*D8E?GRVMigx;c+^8OZ-4a6AzciI-qLl0@pR;ho%AAR^CX>cPq;N+_{qT70)f z*E^W|K=tXGw1Hfvl?CR&&?epL`7l|O86YckJ5x6laSZ(#;c+P&aSVW1uHQH$y zO-#{cP2E6t!^=cr4`C`nqLtBd(R=8A*dxC5Kr+;^$&r(|i(l`4rk-f{Yt2U{;CZK$ zlT*z#WI7ZcVISYBUa3x0eufH(T7lin@=7sqFRipzyxrzDgLAUO?~jk|PLWrgAQU`7 z^0{uDzj$(7-+18`;v-g|w}~|P<_bI?=jIIDEr9=@`QzHZ&+yt{I*K1=`V9hU-1;Di z#C}OR<_s2xwwnZU9x9N}%I(Z-tv#!pU6q#n?#ySb_EaJ#H!q4Nd%UkGm3F*3$vVmc z<)#iLUzs9*l11gQppy?vLWbb3`jg2O(Jy`U$b;&$>!Qjkw*;nCm;GB$4AU4a;Bp!q ziG@~0Q82;PHY}^ez{oGWfea5IsS70%=d&g7$mo_!Sl_E1p&ajTp(;)ZwIl-=uES55 zyRok5EZH3M{Y?}Z!&E)pPY3X%!nDle-ZHp9D^AiJ%_={{GzyfoVUj#Hvn?T6?B)C} z;+80Hfo4&SbN{YP3SOsd6LgYeKq6n3EY$0zC-!5@Eu;dS7q(MwVkr%N_4e4xMhxG4 zpx(2}&{o}Dl0&ViLiX$hs?OzaQq zEUFy`FFd=4`bJ%EVtANUD-^{$&sbGE;o+}_6smCnp>leF2|=NrJLj?kjS8Nfdj#1k zH*<5Q=}D4v2`3zVNDpxMaF$LO-aH)En`=lKJCuaJ7>7_rE!^Pf7@X~7?Kmrq@H1Q0 z(4(FF3R8)p6H3<{PzdvFCvv)5AjOmISJuEgR&k6d9@PDAZ)dVzP0`B2xS7~}olJJI z-Ao;6pN#% zK$`;Z{_r=&6@@&nf5Q2k#0IU_QUqookR;8JJcPYjw{&c@%7HR0-^?a(B4u+#s#ANY zR7~PxR`Id63`uU7iv*UzmN)P`0Oc~c`JRlM-Dyf?=Jk z+`)F#SSkfPpY3t}jV58gzTGt#7-vvk*KhaPew+?JWl~YOP{trCvPj8jo-!t`MDvnO z8YP$SL+}NK21ip+(27H9ogJwVunDwyVPU0DW4a4-oEIH}%;Nr*ePiU3l(dnBv=LpX z6W(%p0F7E_7F-AcV9M7NphOk`)CFfm98CmGpV&v6W+XT<5H2Hgm)lix)IDRk)TdF^ z+y`WAisgW6TeUPELy&RPoVcSrnPjCQ{bg{##`pattbZu9A-Lri8+S$3Kan(9=^My2 zu^@w7dRP_5iQfQ}2htWZcu&Of^xI3GCoGky=i!0nQzI!5DdzJsg zxUk*OPtdHgK$bLdS=|dJGcQ?%$P|Ku4PI@S)1VUA41qmo{NJ^cS^AcV;n^w0&4FZR63miq5AA`z)d zHu@8ONRM4*Z>a4~&mTUTvs#i-0cCYf*5ZyHHp>&{?Ka>da?U##n^nesT9oM2?(L;z zm9NK)W}!wrTjT175s7F`@Re1tyll4L!X4Xb63{csqxFOQaCUED>&FGF-z8eFGyQ2X zr^HVGp}@^k23LMn5q4%&5w!s`!vE|K>xZvM{_Ff>j(76>cD<8zT8kL2f*utywc~*$ zQKNxGEBTlqgRZv=m8k)-#3n4=k|Lud|7V%$XJ3pM<%SQewwZT*(W!;%`3Xuk!V);A z88#U*izskF9NaBD`5%^gr^<>*fK##4#6`jdXe~2+c|4jhIMA%ogIJgpafS-Z$>kCe zuX3pUuWy7w180B8-VRP2vx%h%0e;@_EghL%)jq18Y5!}y8%m)tfc4{y{ORyp9w@w; zyiFfnmvgn0WuPuMdc!|z7Y~2(c+!3lpns=O_WkCel0&*tZ}9J5t^wz$?DiWt1RSd2 zBnr{b`O{vN{uv zK-yVyN;-_AF!9gcte`}@u1u%)?mq{5M%V02o6Uv4C@;r=tt5Q^Gl4U?1V3b@XrlrW zEwVC5iUIeV)#AqM0LUKsko2Aj{R&N7qM1L>l=dc*_Y)yg$4^g*2;dz@&-CR=^aJcA z-A@nft$XzP5cxQy7emoI+0K z%A@H7#95(85wAb|jSg^2;gmpx^mE2|Z3ed`TWSbgt;_>8Ulf?T-nSFs4x=>*y+<;U zHUrbTh*%&vOTtdMKEh<26Kt8O5hcow4?A}fDSLFSvZuaz3+;(H&uq0=lMOp`Uu~mW zJ69F~z2scyutLz6#y;#quA?YkYJyWlxF(82k9(Ewo53S=jISNfni=d@MApIFmD-5m2)F44aJ*Gj38Aq>+Ho#NfL< zN<9tEqct!n)9Iq|9hN12i``4h@*5sq0G|fiXDqauy^(;Jx>f?u09XIHg^LtA36#mA zE|#feQrv<>jljj6B{HaH%>_B5O50Els)Szfq7=ETGgM~eOt9(QEGBKMN)P*>td;*X z_xi6JErNjZaYpyRy}7ob!4{^>n06g%-zc_L5+VoZvHHrCGT&a7L=mCXb1h{~BUNm! z5g#jskYuq_Z2y}@klLb3FQ}rT<$h`W&RwH*Ui?LA)Ums(DROWyd&EWFS&(eoaRja7 zxl4{qY<8&qj3Usun!l9{)dbUkk&uvE@N4KOn8sYvc;5$u-_YC3&vE@qenSa^`GZ~A{Wsz^*RMP524 z@@(Bc-i|CTEwQ*i>F*w|0l(e{7Zh3Thxy&8g(`09THg5q<#~gPTn;Pn>pv@<^}kZB zk6n+*RH_=J@+vp^5?!ccBAbRWDVhZi0=-ED~ z9U7w1PIQfdkFi_S?itt6BQD{22bt_$kNKnRinz&C0bT0=|i&5 z&tj559Y|ZRD8*r==W7>+#F>tk=F=?rG~5CLgu9{52FJTI=btJikz)kMhtnxs%&$L4 za$pRo%a9e}5abjN^7+*fWILpas{z?;bxCl_a8#3ccHgUTv;2NksDb{QNp~}lir>wO z3^+&9jps3=o4zYlk{Zr^9#v)|W3yd(c;?7wy?U;f{Blc~*vMOIoTwb=GT(XoZ7Vf+ zCft4zhy26GRzqFOM~Y^_%iA6@z$Glr=&%eUkjk~Ux8FqdcqQ1y3pM6d4(3oGNF?xo z;UgKQ>^_K6NB+@PhuP1|B`oaU@x=g%4Ef|skqh^2mLVN&Jn_Y;&iTa4N%L7qftqBJ z1u1!u=B5A<(2iN~_CDP(szx~bGwaHU@8hUkA zNl~azJ6@fO?AiyTRGHvaYg#WQtEu<>lt4W#j=$kw;&yGa&~#dIeEHGC-9M(p=Kr+I zTg)O^kJ*>#`-+A#N;MlgmcT=O4xoi07U^)?WF1$3$kMWN89%Xf=@Nv_@V#d=${)ys zpa^Sx%S4XO7tjo3!o)!mMG1y?KUvC*rB~*8I(u~!W=q_IPO;jHqj%jWXO`^AzAyrC z(7Shke%Q`CTKD@Ly~MzJ`f`{(O(t^+C@a2*`N3UEqctuT`T?KXvtN6uC;{|?oE z{M1NI@Ax@+c=+#sIxA~KSnfUQl}g%J?f{B3JOG%Hp5Tax_cjHJM{j8jVUTGu7Cv|#>o$gQ(FPBD^r!<_6G zl9uhvNmazdP`@v~h?%$xT_or32GUnnkcNBY3aKZL{-t5e4><>&t-etG;_c6U9i3bz zr>Rqp5mRB>eZE7>`Siqwmmj3c@EZPh90}$a;#U(sNFjVF@{0P#3;hvXV`@|Vb$Zp4 zuz*uMqr{0^QY#_$#s?g8Le4n-JLW9+D8`s{FQlCB`MuWeikTK!DTKi}#7qvm{|W}` z70$|2ERQ-NJpX_L2#l22*f=g2ZeJ4rRTBG+pU;tsK97PE+LdtJwES?LU~lc0bIM4F zgF;Qd@VXREzABY=Y-9xRevPL~;wvR%aA!Ly8k`=a@qf*3-&kPKRI#0`BWV0WTeC*9 zLqrp!wpb`e{x5Fe;xQ@{Spv$%BaC_;dHm#e8zJ}8HN?!y zn~_FTAaxy0f+P%4aY7=8qP(wR1hIw*9TNpli=eUguACA|V0wRG;#r;fsthV{E%c6g z+sr1suKA6Ti<6l&th)c2N%jQ{Fl&KlQURyJUe23)=-Fv9x*1(`j`*hXOZ%Sd1#B}+ znACCk>eYCi8H#l5Y5}ZbuJELd+z+(i>DLDfNXaM+e*2bS5SMLstnm-8^338A+xOvZ zVQr3&5lu>)9$5?!EaqQ~nJ&}5V9=##ovZw&w{jJGZL^-!7`Z9mIlfnf~uY1zNU0P{YB-OF#lgogD;O0o@{&f2n z3gUKr#luUO%D{SC1T!5{cB*=guM!)W38X-~MoLT58q;0(=?&b|)$Li}n6vxg@~D8bUCCU}T&fY=jK;w@`na4Q z*j?o%bN4NoT+vJYPK|bHbQx#YYuuBwGx*tHqvyo~TCVxhf5{lqL+rpZX7rhclvqyl z_Hv!pf9&=7_5Kx4*qyES3Z_{xr{EOam-;QbN6aY;Ra0d}Y}Fkc6xSCHpyW++J=z{| zR<`MwyH!r42NuLWpmuj2gA^vu8y(*mlI=45MJ5)`3q>{G7;t6F6mzp{_x2Vf9&3ADzzewGnVKIXzoa)S)eWmL#AwE_4+ zDzIQ7qd!M}kW0^KA~T*W zhbA{FW6Bc5rgji-v?X-5?>UW@iYZijk&!611Po>MWUic25Wg_MiJiw4GiMr_UEMAG zv{;!YY1mLwmzt##=-Wc2R%eJQPqIr3`|t29?(7YV+1c9{Xma$Y>`AOO9Z^;peRa%R zah`u)uzVs84NxynU^FLSG;U`I5r>3%l^?D9K4M#Epe%D=7JYl)o^n1$xh6Xlf`?uf zqRsFQlBG$@THbXNYHN~);7yW#Fw4%!YjzgIDS<(CEv+nXKk;}mESe5&^Fsl~sxF_YhvHUrY%z)D zQ!*aSkQ+gI&yqGVx~)hMDe7!PS8Ji|Nu(QwCtK{xj`u9!LPf993R#4apj}e0T2rTqzvLIH2-i7(# zuU~nFYdg(9#{zY&$R<)i0a}@qZ$k=rHP8it96gQul8U|3#0@%9Wvx-!{Un${Axlq< z-{~j7dPZ5A<1X8)@mBb)f2vsb>pDjuP_)o&i<`l6{xvoS(STpATr(c6&bzXalz;(|#LD*h zLiCN08wf0RkI?f?Zk9<`(IL*|iJP~aizr~oVqD!Ds->u{xJdM}fT}VmC+f-ZD3*X= zA$%LC`qT8z!=;||_9FSC(J&zJ&0D6t;DJ{z)`Mpe$VWz2`S+v6y|0NaiJWCm0m9F@ zxs|nM9D*V0{w~G+G%ExDVfqSo*A@P$3Z*{|+qf$xH`ziu4H#Pd?pGY0&AqUaUC^@E z;F9YQrRFymH0wklZNO2(0@f;vB1U&Uq3}?TI><-whgEWnK>aN(_tCEgD4q+?h_`c* z&x70FNLHy2<(t_`9}y1LV$@k5kZuV8f>lI7Svl&o|4jWQOLwtM8z)41_K%$=pt9xY zJ}(SF(5&Pu%lZMLMklT3y^1+A3#8ETW8j4RLxbesQD8Uwo(+R{nCmBnPOJf($?o%) zgBz5jpwSQ{(!%cdtlBai!1&Th_JG4Hz0^$=D_blBYs zMXXx27lYFfv-eNRC_q$`$E0~_qst&7CQUFbup44f%Z48wTdh7K2fCAp_)-<5%z;VL zD*D9P_p1*xrrNHD8wjweQ5}Ef{oja z#X?usYo%jEGx?VTpNcBc1X`Yx(5ZsAhpCr+Fus$()p!1$&mVm=vJ@fVPZ6K8d=WxD z*dB8&5SrJoKty|1I7rmQl+d(TB}OW9H@bkV__k`3;U_i#GCI1q{#_w3F|%>47aCx= zK)|~Aj!i|GJ((PKS01}_!_#;nf)2t&^&LB7QsE9SWNJz zMy1bKIk6$*empA0?;yyJCZn(f;9$*$Yy z&&Xhrn-61YngIZrqO*QXvl+~z0749UDA@ds?;)j?+~FXjrG*ItUn`;krQY^k$XG{% z_@J^vMgjl7^X`|qj|}|d9h6LE4BOMC3xTne1DTFz!}V{U-c(3d#8um*<=UGFxR{Wl zj9H+ruE7PzqYiUlDb^%$%}`{{nC`@|p7=*sX2=s34v>7Cl}?s4c+T~?$gqFHtAqM) z3-onAZTJmL_-nHZFZ{*Ix8?OBou`tQO>vzTAgSIj?cnT9*(=^^+K+9qcZQsX6c+F1On~T zqqd2t$3Y#`5ujR`KhoBiJxi1^GvDW3=opn}A$fHDPCHW20L#L#X+wATV8>sql$Z}p zsbgl2qWyNAPmlb0WJ227{Iy++5)4QcyLc$H#KJpaJzsg%day+Uiw6Z;CMav?$nv07 z;&YlIP^4*$JXaDgf>}>uVoPT`3yOjsV>jUFp2vd~e(Np9q>eYQ3kF!_yskcu(c2&l zkQh7CrqWmIJiU6fCS9XFZ-Vq!Z&Sx#dSr$;H{x9G+Gqwcww;^u55`t8f76G#-Z^+K>P$>sxNxG z$H|V0`rYuAh{(&8kr{yb@{Sw-C8e2COUx*;&#-=15m6veWBfrqs8(Hz5Tl;mnlv5m z!#7t??^kL-6aCUNXxAR7Wx0=>tMl9o@^)w6>ZE10{yow_g1HK_=>E%-E8VFr*S#*& zs25&Qe&8;2w_9TrT4DoR4wgay`LBgw1rMYAY6w!U!Ra78cwG-LHVF5d^%!4~2(Tl13% z)|d@_Yi@B;uKuf(KmgP{UN<)OsqKSHmOdugtT->cVYDsYPz$x_NTuFCB+CV2bYFzN zK|ibStvwE<)-YNXt&}32|JU6|7UY@hCuK~$6i4ur^!jndqouL+D)6Ld;j=ey^-lvjgC8?}lf*MdTs@e$Rk^m^lWKbMjJYEnX6e~4srDu6|CVxr-n z17PTIXY3OKi)$`fTa3$MJ_Qri1n)BU&$5sIiGwm&&{z;5$Za;+o5`k4T0B$XIkx;D zRU`P5DA;%SnS!KXC;_qr1UC{LKlzRRs(6*8rVc;so>}DX&J}1xfeK=XBMxFgQ20ea zgD(iv#KG(GB^a*7EEz-K6q_wdwI(@##-Se`8q!XUfRK1fa-Ij3;O)3~N^&jLnH8$L zr=>)qhwZd%^M+O@L_PyT%7JawS3I(8>QO}DtOVXk+r#4eAJED?L-y@eso^}%>(5zq zwaWPIDSJQKy8f%$>Z8=wO)6q%nK%J9aVLKts8yEQ5rIG2=fdF7MIza1ROQ(XRO%_w zXY-DWDG31Gkq{@eq!i-W3Yybf zUKnGQ8>dI@kN-v$HRJFn|AXJBdzTaKtxuep;p_bW0Xp&PUO06nJ zqDC14xc!w-L7r3rR&)xr?bWa-a>Q2~>t zuXRSqb{P}8(q4w$Zx+xtB*vnTSJ^WsmNUlWKu8vbqAFwCa7W8Qjbh8iQzaC`R@2Yv zCRwcxH*QSF?(WAi)#yBmOxbJi@gC*G|3&Qi+CMWrTc#Zvcy!+(aPT|zUB{%BQBw7Y zN_QF&S2tE)`hP1(T*!$5EbdktaALgwYC4a zP$SsNnLxH__p@S(DO}|fkXVl>Fl9OLwJVqwcg`<^a3th4r!y){E$s2HKOwKa;LLG{ zdiOC+?ay#y*||SLTH$@Gf5F13lLhB1EEbwV0TBqDO=MXSjz6A)FVbtmOSRI(+lt;B?(*51Jp+)#t1xfRH@i# zQM7c^as&4t(`=i|UT7kLR$}yPB@S!i*Z`%CEJN|{9D0YomKTOzMll~0_fuG(7o{l1 z*y``iF=^LTWBkh=<|guv~n(=j5L*gIf?JUn<Hj5>L-ycIJ~Nz;7cWSJ?pV`~uM~ zE4jX|+)&mnT5!HVQ>bEj$fshY`716+>v=FJ#^RT-0uK^2isH)N&i%^>l1n11*<=C3KF;EJF^GpJm*2OC(D2%*VjtAOe}htjxyIlS726APVHTk##h)N{1oZO`yUOMWlF zK}o9o5P^9sBEM*5qquUFF?7RQSly6`q)^NhqF7l=WX##w5(jjDmzekTKc8)CnazqS zv*0a$JOE9+SV(bpd)Wu_T!=8=VR+I{<4~NIlbhTf;nk6=d>+jDf2x2cRl75c7x0S4F-6298!IdRR}<$ckgmAg?lj3MX$@ zaL*+_JbSFSe#=th27+MA&ZorqXM-$A4{lJ;5hcxqo_9LWgW&=Y=C2klCqNGh^q*=0 z51!1(KMiU}0zobJEUFlM-~zF7yg)K0-A(Ybmvlqnt|OPgr#_$!>wsz-rc}G6aeB;f zjv&s7_c!Jkr2<#K6FDK#mvN^~I|VxnWl0|nu2E~*Flfessl<4hK`|y+AV%S&>fx%FsI&F^xDW2Hv+N)(!D+6mhjiX%dx#I-gWb}LI0W##jy*RA&IPmw-;8v6Lh&- z-;faD=6-&sQ5{S0ps60!tv9k0{19!OS^%*`$F_B-IZ z+q316C|0~i9K0vx&(LMo93(|q1o(On36nTnWvyB>KNJ&4{^}>=&t@3Uf!~12to^6C z)^?aYjMh{^TT6(7S4(E}9haLgplm?{>Ir;^U1h9+e(u#@q;j zNc4rrq+75|V96)UfAI)vR4q^MwiRWN(bs&A?4NGtJX}s>W_dL$>67fp0EO#iWRoKB z=IMCiAWf79EV%MuSIIa}Fwl&KP3z%itHXD_6dzf?BcxZYjx5BfDA~Ql-9Mfl9zS;C z?1?>lW=3*^p}*Y#+6+2nE*D<}KQu`|B+F%9tL#NAH4i+QI3d(MKU~EktP72KN_H0h z^E>>S{Ky$l-t(|TTEG-wzX(u&bDwrwU(jp`xw*cff;^Yq-?^)u# z2LfTj-l&LKP-2q{Mbc5b?g0w`hF*f@INf}KM~&u&Um8~;tGps|b7U|%HS1#@BkS1r zI0kX9_iig&0)2pEGwHWKTw8Skw92?}S#Fd;pd`UQU(7AvhQ&=ewED$BlEJkuVhyQ7Ve#`-G*L*|zkSsvSgi&w*yMho;sN+Q?-AkgYA0fF{odcKA z<^mQ@0bW*$xG=8j={S=qx=E+|P|Du5PIA5FCWXL_TlNuis21|GA^7?WqCy4@CIV(S zqMI-UurOrf!piRhMo#%UuQI?Q05((;2&wyN+VEy!CFkObvIe>h;8~Nc3c9X(^LNhW z6vx1DImAA_<$ABuCDG9b68%`R;<_FTQxfYejCtDz&`!WSSzxY@a~uGLbSuHfao&2S zSTHRP2vpWC#TRLwq!uxEd2U-ck%t@Y+>r!H$!xgjEct%>7UnlqgU{x?(hJkqls9;~ z0iU4~px2aF1ZgLpFQEqc*~EyRLVSzW(@ZsuFpn zX{Yfl=%4}*3Mxscpd-Y;_k7{^A2C}@Rz$0`%{;&g13D@IIo!sNIiqi^1l-?%P&ot_ zzRb}7dQeTUp4;f(a#qsvO|~*zhM8!U0IcX@Yb0&^#oOa4guLgNdHyqnD~~c$WTe2? zA5A|4r$LQ2foxKiZxjj0O?BRk2aJ4GHm`6CYz4_YXJ9kRKuZ$BBhMmE$-<~#cEiC` z$~1{Cr$|l@wUIVp`LHXUklJfSIq{7d_@5c74gZQB7V^^Whn=^N} z%wEhJYy>YP>%qf(q|r|Mh^h%G`#QkeO`goU2~4z0B9Q0S#WJo1MrQ^)yU+2?F6lPb zz#Y$hf2upnowwq<9-L&Gyk9(yamQUPdE_qC@*G&2&)JlF+|Wyn-Jgdg4vNnN?IuLC za)^Sf+`YGQ>L|BE`}{>?_zjy5*UQng>#0=<0-yFIa5h3y4F|QG!N}+tlcWHq5NAmu zEH`3{4F}BF|8=m?tc}9`oJeq%tEI`-9ZBeG2%W=%Wul}gwi1@ZQz4Q8^-?Vt*+ulrs(R`O zJOk2fgesQ2c@n|(-%7`g)VG~enQ^ivDv8E_%nifsGn0mtWLDa3?+sh66Nyr;+&ChtxT!>85E2uN}J>UXpHS*Z+5C-Oe+12FTyHgD9> zDC7XLnipP6OUt3@1pB|~=mgyG))$Gch)hc71T-J;7y;G6DwaXvOe&=;r@)RCDfih@ zKRuvjRuQ_8?gDq}_UiQxOylEycpY?>j^SOG+js;O;(Ykfo>zM%8AN6dgCT{gh%k@n zj%hh5oi)?}BH5>xla)kZF&-R|Pcpve7TsW~V;4fPi&uvtLy?2t1B1uO(^Vf$VeG+~ zuIv@7G+?6N5TGA`j=(|_*O3F3Pd!1^a%#p&1Y32@AEid~$7;d3uIM)>t5V>t7jy~w zv5FGl=e7=;YT~&~XPG4kAN>72gXMH4khqYv=7`42|xh(s@ZPDytwFl4W{%6^wQ zz3Sw(8Mqx@s(_gq_z^ZOQ>90_ZWL2QBloU^Ioh{ipg(p$0pJZ5)%7Cyl+pX zvIcaF^ao>#vzLWV$}XfqwUbJIC>6bLML$mM5)|_uc+=4O-Cfuy&Z}Buto+v)^zym^ z0vA^x4)_nh@ZHQJBi^O@<`=KOBQvEwh6w`@EL3Y=J&m)Xg`w@Sns|&D42L4Aw;;r{ z5-lC`?|)eSP5g9h{ z;V1*8rh@#Il;{my!b2ACDSUQ8pzyTm3CJxjigFJN8J+pX2x zlzh%92Dsy8!SHFA3%qp7=E4u!g&z$_%854XXIE>;CARR9C%!2EMp3(%s!9C5SYNbV|2$cQ?`@NOy-KlF}vJ9p8SxG5EpY&mEk5&N^$aHRm(O7$v$^ zhu~wM;rPB>(?CLD`*?<~m%0{97-M;J=^_?*Ifxj1eX6j{-DNULAeY|i_yp0i&Y6h) zeH$~>O59P))W=?@Ha<^ENk0@w;ZPL1=49|?(z3HxuNUr~TJZT!ncoiHi11{nKz_7N z*iiXVtkTAqTazqg;iZ0bt}?y-7Up-4X5{l6hSvDUNt8*DyDnY%Z_xO6HXg$x$%T4* zX=FjzLD)r@1nu|x4PyS(^}xeKGK9Q|z3o&rO@7=(Me`fnZ?8OMv+iAr{r9*wc!~cbEV3@X@ z5G-AC(^-a!4_mH4Y-{HC7Eosn3d2L($?CPpvmBdS1Li&tqs{3bIh^!Qj^FvFfvLi~K!>&dWOAheNBSEN6^<+RgH8J?gkT~W&aprgdM?1gL%D`ci}*^BNnu) z;iTmofNg##3>L^E4Vx6q`-bmLHLh$bLK$=@3Szv zlyyahU+Q>igu+BkEKTI+evNQC6=XE*nFH%l{EepDJ*_D-s0*jqTyIG3E!DnjD-MhQ zFm`aTpia`&1{)fQe^SOj6fL05@%_9@J1iZ_#IVPhepd>hDxx1bL_JsJu0GloiX4?kEa_)@B5Gqz%h-`ugOi5qSFmKI!6&O{(2Zc+ko`CBPu~bw zQVyVOW0s-1R69M6?$!*B))l6`(+{3#ewOYE5yuaMy3pPf_G=)h%;i;R|0Ev5IRlYY zMp|NsRz{i+dRsyhP@#rwAC5k(-kbOnYWuN!O5M(6q2o?9^)vMRspr4$$H61tIw~r< zp)QT2Z-8Rnlmv{p@|wmstzb}LTJ9Q%@|7j`5>3Lb*}?!@%h44PHMEa9$Me`? zbaiSlM0*RL0N5}jO8JHne>H=8jyYHOtvSJblbgsFKh$R1mR|s#87qg9=ch3Y01((6 z*rnxWhLwyIP`DY4x@!ra-F4BaoZ~&ViT%iRel8b17Iq2?^q6ctLM9GPL!P zL@uKWn4jtp%U?Rk=>Ult3-K9Sa<&Z&8hZa@NtD>{iq+D=Ev*U_+vt-kVhPoEdQ3^U!s)`)pi~eM!h>hYFC(wB)Jg3VP{>*UPcT%@>v@wa4 z8`wLWf-T;m8#+4hiScA9q;E(UCzxl-sUfoUoHH^Z%Q|@62=QI}KgId$hYxh`CGDwU z=oEMct&`5_=R~2@2a<8kDG}&3?bp1zb3(|45HLTd9}2A2%4lPSaT1{ntv28Df?~io z*`{j+(IzR;yF9=bjVpMZchVcV^mwYX-yc@>az$7gYLwYK0n3{%pP*-c@L*A-)Az*E zBpXOl!f;#cWR9zjPMyOPrqO8x)^;#%;_E=ajM|qWP02;LTvI$esfP1Q?y2>OC%JRQ zCe4u^TeVow^Bk7lgrxcMek zat{6YtB>gR&&rDaKf9^)VS2ak#4UMZnqI|Qw7kTZLP>CJCtKVCKNT~f>byn8hv80G zel98^K^=kZ-dINe!z%>Lh(Pf@`>k)<)#s4bO6vuIi1AMJ{)vKYC}5MnmVO<_Sgw>*9))-dW^}C#)`5`{yU26SKfE^R3b>mmozCtiF0^A` z=6Kuq9kGhvX?Z$E@~9AV2e2r*zG{zO{3?9pn*Apj%xlrfcXJ$ZCq;HIAJaDnzbuu! zgQF&z4O+^Yul4oFlPawJ)T zHX)fbjvJR(qSMiC7kzhg#st|;pcXX&FPeR(J%NVl?JQ)P+hU7ECLi6|ct*WOSDjht zKeORBs7>HS2%rFCdK&YCYzhgFNh{)NHYx`73_A z$>%+j1@Sb5!po*J8Ypo9(4k7wmjHB z8;(t1rGYiGFT(!-(=3C~r4Oj}Rzg1-re(hKw-5^7L=bDapst=ghjl|x$cL*ezcL^e zEhJQ9Ra7`jO88+5m2zM}SiEAv*9o8uMus56N1?kMu<5Cs^E(BOe&!c1)(rRr@w8U| zKAra$1=qNf`0GuXw^0Nc?+O%*qbKfEF1ZQ|HFAnKtNfNM1GHAvb1MvV}?0q zM;g8XndRaXb#Qx_ZvxHYrZY~xXiMeKG%@HPk!MY8vLO2o7 z-AWj2BF$eP+~udx1#_33wUsB4M?p%e24p?~6`DrnTG5xbd?U}9IgmaIOc)j9yAjPQ z@U*XXz(UglHssAY5u9KhYqb%J5U-*Uvvs|Hs2O^Q-Feq_@xhP+OH>2E z11rkrJKQ-ay!QoKaAW>GPP*(=nP?G4d(Wwa>pSwQ( z$J>*q$0sa&{S~h94_~K`ag0_R2l0we1FH;~%>44axVI%?iVCKTjK=4wAhj*^*#>Tv zitknwfk~Vc$cxcVdzG_X8CN9DKSY3e1_1GZi!VtLQ`uzvsRvsfKT4J&_04N}EkSa6 zO#e}+V`%mI;FL+QtT_p~i*1J*p9J=fSj%oH!eOs3%LW2W_dIq*MS&pOO-exg!z%3! zHCTl8x^lhix(bza`oq8e?`hkl=eXm6VOum(gZ)KsxC3$f!0)Up987pfQ#+D0q~B}_ z8~XHWwbJR!_k1=Xu9V|~&DUk$=6^ZoPM*Q<^4KI`N|ry>VZYN(dXI|+Gr8vdbR@;! zTpvRf#dP0&YD#Qne`Js`v^Yp;#Ls}kP$DtIZwD_X*fTBNE1Z^{FIs1t#m^bbH$q_R1uQXG(HDa?x)L24z=HNco1Y@ zEf-Y+Cs1jU$O``W-gM9++>R7aIws_e_hV;N{3_Tz->0IpbrKrVpEf0y?q?ozMX`|$ zDM=7<$V3nmS^BiLSjnDaBw+Np5V|Hao_P+BB^J^&L7_U|kW{&sN#0wiHy>X@w9+w} zWTB8yV8C3cT@O?OC_z1ZaG|xfZ|wM+e+1Cw*W0)L6NutZ<~0A5RrG;k?=p%i%5i;Y zswdqli(v|ju3iwDN=~Ae(0?0tvI5^X9nV>mZd#P*)s#hxXZ1EzwkkmnIU}lC7~(14 zYO3^UGE$N>bTE%QaN;jdv9G03w`}^LS8wSNzG>6TJKxWg_}H6qpyF@$i|CuY9a^+R0vhvrbOecD z+3abnjA)V_E34DR%#-PAV~Ej6GxngPFNxx8y{6OJy$c%*>twtF^_v796$S2fElkvq znxa5dW{&o@ z-YMcU`WQWFX;v-iZk8*TMuLh(DaeDYse3u)l9oMjGW$dbkcDgc=AND$`fMdLN&z+` zK&lQ^!E3T5ejv_6psF~jH}9)s{?Y;fmI^jfi8)`qus+zbM50QSRtQ7$EQ_~HT^poI ztIfKxq#kGI#E^6{c|Ice6Zu_mnRs4M+0h{!Rk7e+#A5O?;g^H@&UpMc#2u3?bPdA= zkONd5#JTctE^yDGG@No+vRl!5Is_%mjyKluI{1so-@U$+Mcu#N9W|Rok(xW5-xP{zlqSsrf&rF zE*&RUG82=q5wxFmDpBn)%@+T?7l6((kE#3fUV#uqLQBU(Lf)1MK3vh243$4vilt=m za&{IK&?>2d4-lsz73q=&bZN<4KV5AqA`P0dfRuUv@H%^AMPVAJ^W8x%Ui(TU+L#T+V#-a29s-_VzX6T zVrWd0$SSa@CAhBGFWV6bw)9eRdJD}ZOlS*r_%FN|6>!Ukm!8x==ZlsrLF9K2C*G?c zaRxF?hj|9f=AIw%P|c}q(G^lHh@T(TJ_TB2>|Oo1?GcIX+iKMyF09pM#$o-T$nX(c zcD~IHC!4(|{*A2#_-EkOkddRBO)5=47QzE=uI9`eh+w+`gDLiVxdKihqoQOBVgM|n z`NLF6@Gqj2CdZ&DkWS_2zw5Iztn3)$HaF^t|8q}Z56!=BA^`rtHj}Ig_6|}&pV`_M zZ?}RuN~#ZHqo#W_is299Gi8lN6rJgD+xLxTB!U9$Ix-<{3(0yf-&@lOZIihw2zpgt z3-JOy0yd2lcniY?650N)1i#ma(Y;ZjE~}tI1^|rdGMzfRA>q(!r3H6i|3tOrAUcJ* z&V_*;!hs)!bUi5Egl9o*ls(Tkx^Z;OJC>P7$EcV0w1-AnA=q%yUT=^zE`yLs4UKcq zX)0^e=@Q*6LVgR(P&GP8Sh=!daIvjo|oQ+V?O+P?{q;3o~8F zI|++CoiVy&!4OW=;grIGnKB(yLqc#_xvrH-f2ihDt4x{Y1H~hlQ}90};Q?mO^^r+0 zc&wn2b#)8Hcpuj`CfqTpcRuG@kN~wLaN|2$rB~x^xem5q#eB@ysmY%?GMDO{A>+$! zpRh^Up@MSa^$feiAnT`~L>IznC|_8S1oxym?Q!4*A_yVn(N9SDk9W_liiR71{kZJ= zq7{7EKmE}rC4WmF(u$zmz&|&cZTU7O-+)@@FPBOq$s2Gv`W(aeN_4A$643h1J{Yk9 zu0*RFUivWjN6eIK8Hk2BrQ#Et`JF^iW4Zd50LCdZn}U;(kr}Y8(EgfGj#ZK(_ab-n zAWwB&vZyXMg#UK}HH`*rTr<~>#7q|;x&eg74g=Xla!-IZ7>sM>7Z#l`bbVhQX|Bi$ zm-|Qvzr2`|zI)q`Vxus++j&0+XdOHUI>9&+HiirmmR4_HMeE$TH3Wd=EduqCeMUuZ zvkH2;{ZB3opmV!Kl1ftKby1P}&Noh(WJ#ot6?y*41XAau2 zi7IyR#Vk$;_A*IgOCu9MxqhdC9Vr?k5JMA=fYdUtVunFf?J?$5yiRxQ(Q;PywN$Bqa3n6LyMC_Z2+rWOKYF zf@iYRK9m|&OW4qs&qh((it=IYAOBhKUnT;1{&x?3hS+&b!*#P5{%YcV(*OZQxU;DL z_OIzf);Ki~AiX!q!?A)-*jGW*{WM;HUC5D&tgDoB4FfxZn}ID5jVJr>BNzx>BKe5@ z2!lBdn7LV>$rW_i5D~r@BRU;ylLKg;Upz8xZ>>IJTYuj#o2!`jJ4G(vk0yDfGR+i| zs?wC~&>gXGYrBebDoDW&TwD6{YEV_#Q{w3r90&PmG-1zRh_N7|_edI&R}xsuM>E|7M{F&wZaE5@z2{ zqL7LJvy7HAg@2TiLyss7lX%-(x$plS<0Kfujd?A$3gYlSDu;f>r0un6P=-%6IyD<_ zT=KNtZ;3x0PzXGEx|R44IQ@ZJ^(F~ZdRDA0n8A1;}mKe=!c+DTFRk*=HZPBz4StBV=({0-RcO3N7){ZVwpoH?9 z=vrd@kXsypli{bv*45F-vt==e76jecAKqpvu7LR6LhulOEDRdsc*F*_#&N#xe}pnP zz7G$4b*-~9e*6Vib#5~UvFaO853&JBOA$&|qXXZH&y9$62I>;sVpG}#TsGd-`)dcf z!#(X!&!g{9NnE0?s@SzxI?$!2+^@5h&>-k?`b$(+=pKxJ1Y&>aGWT7;W4fmU4m!{) zrt|;n|FY-SyvARQ5*Nsn&|Dk96r;|P9A8ZAd)GS#3YxzFSu4;wLS3SvJ3+|VEB?Rb zsn_%oL?{~i7^BS3!@0xD*|nGj+O?VSvYKrmz?gxpeTysOA~{nYkHdwItl2hmv^kT@ zpZe1!8KqRq_gwiI*3dB1W9_F^yk+o$tdkOpSE266diNWKa?sU5eozEM!eCU4!$7FJ zc_2cnK}7FQlvu|LrI?VAR$i|sYJ-XYzI2-mkCRg_`^@$i0Ba8Z`<4G?lk!80Q_1h0 zl+i}5kd2-5T>y%u0HE0V3ATpQ^ zK6v~WEuI$H5{DBi*A+!xkuH?;06S@4Nz>Dh&G(b&M8$>O|NUNLzR zLzD{hd^%`&J8`lK>QT>eu?*l`y|?(1m|$&`t1VTnq4q0@EF|hjqGOcNCsk&vVxXY} zr4oR1HrCe7gLMyaJ?QmaDs+DNA~;`t*>=v|rX%a2u=*s~ebE7D35!M((bSGGCHw%l zvg{YEJ*Z5js>x$(fi7$E$pjG3K^xz!3^cshl2qFyFo}KozqGzN8O!3w=k_$CvyM;n zUVIs!np_*MBLK1ujh&X}kLjQ7(fHoEFSMN0$7WHdS-j2X!Vcd+wwz>Anp=ID#+yIX zbW|hLIu7XXORdsnqaEYwz4<j2hp(;QxJw#$sxZs#d>AQIsw%=DMv| zJ}qho^p7Mw-~Y-yu!e?}P4QToB<$dNJ~Ja!*JUxbi;6lOm-8!ya&ZSlN57eG?!&)e zsppxPS!Q-^(-)A>Sj0N5sa(mq;ROo1=}?(>9V!lpD6N&|0UO&VjIaRE$KSd6h()@x!E3 z-+gQ2=qaqO{YL;`M^&q>;{}=E`R)Q&bCuERt@xB;#1tP@+OWK|OF-P9U<>xUo5w$0NT{%UNWZ^O&|v%)NM#Tyw8! z4|T_+=ri4;9D?ViG5pTN&eIWz3KBWWc!s~=FILtxyW_*V=qaKKNWU)j#SJW zr$VPdn{;fvh0f?2@%+s&8pEGq53=k}NU!aT+Z$d)0F6pjn#L~{8%{!=6ovm6&92;T zZHu`f2!9=I%6-u?+iR3M`0;9~AP{qrS<5<$i4UV6X$T7Qw_+Xz$D1Ivycx=0%;Wj_JoIY5{pIPAT%k_7I>`&4!_hiTUYl>d zY8A2F6HEyHU1!@GjCw(7;(O*oEg#TXari^s3k`c;Gd8e-%(|gL^Wgk`+SQ&kx2f# z*!Y4AgTV^NzF!JXMwHDcz5@z7O&8UW=Oe_SZL?K{EFMJLi><+XFY-+JF`Mb#-ONkFFc-_=2(dvZ zyQJmiENUA7htIw9gx!ypUC~INjvxJ2#u`$hTClIoQxXM-UqtNr6=SA|h)o=?k7W$t zrT6zqa_(nO%rahic67iTS&{<;4)E;()Jk*R00$NItj1ie!wqgs^*8Fs_rEK(F`4fUq3oni_}ub|Fn&K!t>WGczR?$-a=qXzZEN!Dk;eU?|7u6;doNKoF*f)5KId z&sS=?t=(y}D7K>&u^*5SeCDaphM^%(0?y6AH{Sukn|=VyC8uZHGv$=3Jmob_KiS5T%$>gEGoIjCl znPH{-sUrtyT_1H5r1}cxI#c^Ip++=H&*j@C&BR+QKoI4_;jBkKbjeWvE1R#6QhlOA z{!behXOo%wT8l;%e<01~&Ga^tbnA!R45QXVD)XvAEI4#I>Hdn^(W(j-_Q(F#`3`Tr zPFSM3$U&Yh61;HxF5NQ1SEw4%b`_|9P4`56iGsNm>};K#@x(mO5z~IW3OXRNG;j^3 zrk_d=g$4A=|D2q8+E1BXkEt|xaNJd|c!iP>nbb%H;_lL4vA1nKDy%bII|(nz(*F0rb~#wN9CSxW`dM`$p#X75|<*HObh6=DiIt^G^K#{=thK20dC3dG)RbYB*XxD(G;F4z@%u1JVBJ!2aKTb4=`&EomYryDUgwmk=}CigDBbfIdnffV_WtYemMFHewg)x0}20j9?WBYh?KD@m`wLPfUqh z_;zn*?>eF|+nyDbgBj-f=&dAnFd!Q)z$R!QS1!qA(QWw)T)5=HwB!vVod=`EoAK`z z8dRyV!j+@IG%|mfVcfptXWa46SgTz)uPfBo*O&gjG;o9^Eb}$g4i83is>nf7D>O+~AUAmoyh z?2@yq1=%>Wf2c4T@wW727437HqF3f@;B-lrb}`hoZLqu%oHF)ODOP4#}W=al*&XX9U<)_3_s`=Jz z3%Cr~J9-e=)awt%sl;;wxh?KUqJX)uiKssB$e0T>PkGBHoA%kv0b@dpI`+vt(Gp#; zAOL)ZC%z7x%!O%=XhEfF4jl5mCQVY}AJcbY1M%dm&h3WEJw@V97HRJ zNsB6VsJ|GYSQQFIPoFQi#T3tr56BG)8CIFNFbWR%#-n5gVaS;Hlf*17ru0q7bFAy( zIvWPxnk4(&QJG{SCInCxX~EAp&%qvsY?|HNXTZD!{n{Q0e6KxUE~}JH%6mI!c!2TK ztx==?H+-TTnV!@Z%!yWXPd3y~hoLUdibwqFiNa@9u3;hAW?}E<-eV)3DBc3jSH2{@ zy=rp2tvI1CD_KzV>}La1&n5K5y()W?yl$Y2k(uE8fMeA9XMcl^_!_)llGf}WTGWsO z{+kc4h-gRzQY!2cL;Q*wMhUL;mOrW65 ztz-5i13*2O8?ECNz(|%q?W8(vAt_H$@44rcQk}vg_nugBT+L6i`dZaT&yKxMbe>#IzfD{H z%WDY_^Z4X`JSx=>2+MRTV95?V^{pQ^D|5R@npIp*&nX0mWhK~SQd2kb#ra<$eE2cZ za;v5peJvd8h=>B|Z$|FY*b>h@=iIbHrJnw0zaAi$OMetj9zc=_d*wy&B2|<7pKEzb zNNr_^S;x_kAk+YN2ssGmM}8j<8xt2IkKdjfo_dR%8Cm_F%<-b+2M9(OP~J_*qQ%AF z>xSj{+`qMV3(Go9$k%Lkyl_o*=0Z3M1Yu2FY=IZJjY2<;k((`cJ5i|-?mH*~KFGg> z944&|fXu6j&EIL~HZdy-?HQ>#{Id+7NopWpSs z$iaDT-BDJgGS!6UheLVt)N#VjhQ9@D%eI=nn%u!4 zFk4qLoJ>{t^h>arxN-}F1tOegG+PYbvb&<`FM)K0ndmb*!ff7iQZPcEktkQb(VHjK z{p#c6Ny)g&ucWp7a+=t3mN6D*QIxV6!!4tjE@qSYJbLY$tMZbaWhDtyA`nex=;OzO zB+V`Q%&PF-;~3i?Zpkt4ASqCB|Joz#M)+21`E+4w%c7m8u;so{OZ5Y>u5Jnrf+&bi zz9^iP&ekz9W`ywDF2$-r*GdYQrZIf=qAH{n1W@KAlqW?-mR)=iMm~@wI%gi!pZ-}o zF|{lS(X9;GHkbQp-#q-UG>UIsL2TxC1M1DIzUu8cNfVAa_`%h5eIc2lHQt+TP894D z$yROZMgA2kwxygM#I(mxbwA6+{Sk{bJs;OLS9(4SA?Egi{npICJQIg*=)}n1oUTNI zjhU0B+dq!Dn{8rV&?%IzTdm!^zc~^kX0f8~Qns`%8+6jlB0qQE`(1DVP&LOF+!PB8D@PBjQcy#TrZZPM_SiJvRswq zlEG5z1+PC10Dzic2$NJ=!$hjh!nJSHo`)$};x#9t@{zq3K%s_UKCS*?`q4uT6r3Z> zg>xtC&vH2d7P!&($*DeMj%4|>EWc(PGkpnA&k=L`lT?HPGVeOg3sXv%6X;}9iXiAn zo^UIUswyV#Kg_l!myeEBF4ycC5z#3Gg!trak-I(v?R+b{@|a}D${Oc1ocj3>n~=3e z*Py#$G!Fl_HeP?(1^==oDOvm`!AKfQ-o)0}QX-O6w#Sta&o6+YJMO}7%_h}phv2AB z3Vfjyeav^x-*S7mzM}X%rTw~-wSqP;MCoQZ=9fZkpS+HC5(|Ryv+v?BkG|oTKsr^R zWL4aDs;>Kgx*G%2QY_afR}^IXD)YOEnzk`qZL{;vQ)N}lbnHtE*~u-I)&+ey5^!Wn zY2a&DHf1e4wLRox>%z;uBAMlC_JT)-1q4&0^Kxf^v-H;dxD(xxmcv@K89hb5g>p;f?N%|71Z^vi$AKL%BRchoTciw?QR2&TPRd4@~~+4J#j3XKxC z{kNK)8xcrJNNcO-&hI4icv7m7Z>C-TkHZ(sbtqI?kfEI6_lan%(%gP2+6gBg5R z&hJ$Tq;YoR&;m?i)on)r(LAUL?g#!Fx#9NxtyCE1I&d*bU0WzdRb&LysA+wW%o{b@ zpEGf#wun+eQ;?W1!eB+9eX?Do{L9Qp(iI#LE^apYRZNbv*!t6JQ@EC+$=q6Ojbd^b z@Y-HnLlws-@;Ou!E<0lj;(|S~AJx`)_p#qzZ@=98J@|!dsR;%;5N;aRt|4-9CB5#q75-y8$e8JGYQ+qj^dH>}5r503k@4tn1T@l#!XGe%zv&W71c zqy2Uzf7h}Oc+0as&llP&n>+-9GQ4~!huphdw}l^_=tpK)q9nnXjO!nbW7vW z2QjDMq$AZ~0AK(RPgsyf%1X=X<#eebYg{TPUh=AR{COhR*KAx!KZ#BBi_QwVbNz%c zxd-W87D<>73eDJ>Cwq1a>YaILkf^R+N}bgSiU6%^Z+Es6-s19dzquM7QQ*f6`POk= zi{*~s#Abw{MGJ)|<%ZBojxJCq^DLyqxi2TTN}wIhwMUPHHA0(#Z7I?B| z;b89Uz~gv(%9Zr`(__Z%e;_5KNf9BxPlHD8Lo*~qXhG4n~t~pV4z7VC?w!lS!TyM7ec`9LjqzpRZV`|y}(GsR5bVn zZYa>$J@^KbqPuqkvxQxN%xckjX|a1|GsD`amr2Ik-hd&0jk4372whgCq>E|3gC~|w z0UTV{5vl3Dm=G5Cl-}d9?*K90ku1TSktYsMC>>JCwRv^i7-UslgQ?SZx$SXokp5w6 zIW&8ca6kIh&}wnB#0axk=PnL+~n3>IvuNGZU90d|~1$4p|B>Zf1dJ&}No z-&o?%27ixz8|)m~xk1!|8@wGUIg49D2;aB-Q7`|RFK3JGmQwOu20Ga!K}PSE;xZrO zZ|I4H#Yx9oqKBBim^tDX%pGh@e8)8PG>6&V&+MYc*XjAkc+^W=n&ZzD1?A=&`d3TG zgPO!?L$@@dy+=)1Z=5OoBz`M^CVS8J*H1PD^4)vpoXz zjp|~=%Ll0zU-34^rL66Qp#O3;_8C5PY%89dsPT1*qQisR6d`?h+P3MZb#uvKuT2CC zYsw?7mnUfWoy|qmdXiTkKr{oqy7;^cPCm^+#;a^c^f^B7X`YJGDSj;#QIiao3Pq1l zGcqYX9V5rHp<27$NffhJ9P$dfis>nda2Il`3zQF0>He-U+}=51Cf~mTz%LGcrtQ6s z4$xq4L3k_%qm3wG6CHTLDB2kAwJR$eZq;0cKlZdMIT99SpRPmr%T-Mm*D+V_=<6{# zjhb1rMc(q}R?D}0n=XP!+N8S}TYeIQYc;>}4?Njdd^WK>wvUpHR*o!@- z)LtaMbgqH3=o)zW2aoGkqPmx9)QV)>q^XyYcW7^S-qy^wTyCgEXFIfCXiZ-KR((UQ zzQmJ4->T2tul-2z#ENiw{QTsxzmRH{{z_XXWRfZp2R>ogGDEvbCP=&1eGb#*A&65I zn3?+#NhPrB*_3kpfuk{YGcU~mn~VjAxB!ZP&%)N^9J#Ym?d9_lYo~Q^C?wWSNo~%! zXEgNp2up)mAkBvF=Mfg5m{Y%}BkrS$3QmG0lTTx=;R_=be#6h< zVo#&d!z~^lRwhX?IdDYF`ff$$DY@zN^0=9@)aT}M(<7_0^0mj#o!&HQ)cJe*o<*e& zgB>T!UT(5hQAx{P1&FlvAH4Y#g}l6sP&yS5KNMOWmSDcVJQI$|cC++(JvJe8JVf*p zqt(Emxf;YnT6Sb;ZFJjV! zA1;2s%q;QM$fC)$p8O`P{X5uT1%%~FV-NaxjS2mVDJwS@^7|$-&8}d^MT)aDUN5Kw z3xwzkRxJ7d;KZ9WWs47~B58cCQ8lyCaJi3DVGy{YLTQWMM%sq>lQ0aK+~&aKP19h& zQKoEfbq1CCK9d0v%~mkP1iQe5-mLZ`8W=esVUiaXl<<(x!7<^A#gbV^!bzhPe-Y$e zn1R`z*=^1#$kVA=yRYO3J&1ws-GHc|LhSP_Ux%eAi>1}CDxKdE0ET)uya4|odj(;m zp3EFyi=cc9CJEabNc1%8>zs##0ZQE?Dr61XRb;cr?|8!Aeg&}3!bRo!X&>JY^|HEg zH0S*zshAa%iyd~ym%~ASOU=0CXg=7*#cOMmu~z*+A?3a&p~J+hs| zI^CFx#Z|ib+m`hVrF=UQ5F9`@v`&}S`{nyT!?QV`&Fp5z|C!%+y zqj;2To1`7a9pA%+qEWJ?FRwXDZ2iU-8%DNT&UIJ)oCfcoSS^2#O#PY9emhn-^T=ls zRQNSjG|e8|$r_I2QRSWqJ+#p?r0$}UO+(>}N+QR-VHSf45*OpJUBt5U-qH+Dw6GX< zbLYyiv@uCTjL|s|k-;4Fw#Wu73T%1)32UWOCG`g5dlCGanG9y$^*dDNA!(Fn8Yyp~ zDtWi|yX9pVoUq2mhCZLrc8RCJdb>|b!RWtT>fJCx%b^sX;d#FbyT(uWpH0PsRfrd8 zDFJAMaTmZpfPfj;_34-;faEDbyi_dEJbjDObXB!ftaqXGcwjpKhpU7!Eyzec8>DyQJfuXw0LRrzH;+ZUFJi*@vDo~wK zTPPZZdnL(<&?iCBsqWgR(N95Ky~!`5YG}G8DOu`|<6jmQVWb-O52Ns7K)O$kbBZhw zfB20^^ulMX_wyTGm2;Irs13>O#F$^VP3d8X4T*Ydud;@|vDs`+j!Mp;QSyk+PMs}0 z%wH3tSdl9-(DW#l&m0vRP%nSUR#4cxMj)j2s4o_zM+oo$`5WC(>wu+{2w*a}XAm$| z?h~(4By$OhS6Y`~w|2BA6(vHh`u4H+H^J`;bBBA6KZCYi6}YETWJB^Z!M$4FC@M2h zlRfcZ5IMTe1%2-?o%gVeoIX4L^}rt}(|Il6GvghW>YS;bvj&T&wf`ejHrjOFZv4O0 zbwx~0@-PkMlhhEcKd9E{7(_M9*47FjJ0D9siU)m9`VSH{QzJoJH!K2>Vp1O#+N%YGr;JZ~xV zK9uQPc$ZZu4v5cvSyu~jDSNZ86KvKma7U6txa)YF&ZyDRt_YvI63=iXvyk-oo8RM9 zgiN%#=kBiCdTV`eUyw&<0Hn0PR=!uSNsiECqiJD^uuOoemzd*hC}T?loM*Fz8HlPY zDF@0(0Al%6gB87AAt(|_wS_G4ZF079Gi3XRsHorOwh;mU-^j_Vo145YkePhi>+o)B zRycywYEO`LEZBlX8j0cTjM#ayjxE?sI&d_zvg8-Yy2aAT|810;Uq{!A-s3dGT)^!e z-Q#>PPZ$Uth2PcdfOes%r` z+@5fmHa{`43y^=5tUtzPM>|ikL(iQ;$;AU(ztCAddMD6n501wiE63e*>M|FKIe{rB z_XNqk)J71UXWX93r7zcxbU0o?DpKDLtiPE$W^gr5yZ(3)H);dH0H;I=hAaiyQtz@d z_b%aokV>-~VO4?cqKM_^2;L^bq39tZMvsl%slqLO>?mkQdwmQwt%}YVX3{~|tyKXq zbwYvImzh>c%gr}UU3)|5VO)li(HnO-mm~ye+s7LgoG|>4qUTKfkR-4Hy^PGzSPCMI zP*YUS#7SfXK&y4oASTstVQLi|gaq$s6dDL^wLyMdQ9j;TNC6Y@El%@b6viXT=m54R zx-KpG(rAS2zD`B=!IfN=1#;S}?7ZfW>kYYgbhcNVqd&WfkHk(Xa=fnTHSz8&kR+0% z6druV`5i+gkUPfLaAi@l;4bX3Wj|~{Z6btaiX9X?`Fo(7k&^o1wyH#`U5OWQEr;By z6bU4QO#ozgadFYEwYxjW8!d(hS7}!Xo)jyEJ>rlB9t@WUT3p$Zh&djNA0@pPeal49 zrD~wzrFofcYhZLhYz9LddwAI$VXGxZMCXn{V zd<0w2&A#277`*Evc66!1UuT`>VQCG9BVGnVNns?#r^Q40EkmwIKl1twC#JqZ{y(5}O=!w7W2zyKI; zEZWqIJauXi=KCpUSeWLIT9c|~7=s$Ydhh&*h|N*HRXLdLc>$cHODNCHBg|;k>LY19 z9W(2U^mj4;LF*q)iV+fgWiID~cI+h?PP(rX=tk}q=~313Wj!uO-u0%GlFl(WxZQ&^ z)ZYf8Y#_&dxE@_X{;>2{#O56=g16>&(Hl}}VTJw}R6x)S3Zx59t5!U&h$bmt`ups! zMokqJ9;}J4ikz`n*Ra!Pl^ex9B^=H8#r8YfxAejx-P4)Uccz9~0TVH;#UTPNUSCSY zNU^QR#F!M2SC)9t(-?ZV0+f_U-@MJE#&$Bk8`OGZD(~fA63Ov;=p;F-#1rQrNOkm4f8R zjbL6IcK-Ohq$?IjLfZ<(m}R=2;VVwQnSM}<3gf@t#8lV&;P9j!Br;3<>kKw|{Ukv< z6*g+K`lBRYDFf`uF0B!3KhTazv0LPs7jeXtU3nYe_x7%O+x&fs2J_yH1 zOng*c28EW3IEt5zsC*Y`n2QtEiUA>7AZjoy1r8BVQ$R+1x1B>fTHo}~gV?g(#EA$V z6=zFoG2}YvTbmk!vH88TU z-~SFX>=hw7M7e|~xchCBZuhD;l%Ers1i zCXV$HoN&1>8%OIbXfCNrjHnTIEDex*u>oQhs65@ATBXreEoN0@QMc6^R|dn^b{jf4Zzt4Co7zo?rsZAW^B zX(!Y(MCe1mXXJjQj=^KC0h&J>OqHPNU@`ZhPMM3| zJJu43Rsz#sYV`{Wvgj&(k49)IHb5k0aen@qt2KH@6**ys@gy@1 z{}*wIQAujN_v4k;oCdSd^xMi=R}63Wqi!y4ra3zc{SpNv0|)=ORvZlDA$lSoFbN}9 zATf6~hrhV^s;*6tbFlG~(J#cU5_YOITFdO#cc_k&#Iv!djD;nct5J!g?YzqsVaF6OikEtqPAIyaIXWW?#}0to&J0^}PNy*g;{)-6 zegYDk>RxerNkorn_&A8%@EGW_^*ORaveqs4l1QOx2Pg_`Ak@hzr zqoWEIZ#`TCnN#=UKjkJVH;VBNv(+G-tO{V}MSp9Slu3=VG{Sk~`LO|ht`u5g{x+B_ zi*8ywdczyDBsNZn-s4mBYAHNRA|iq0$UWM>yewLL6CBSOvjRAp}t&96h<-mIhZv_Q4|Cr(WRAa8?}mq-oV97tBz`=gyq#ubopd8bS>Xl zlQhHYZym(D2M%G+&c$5527^BCeDJB<=OndDdKbi17)Au8tw4ccXecv5iER;jvh+Vm zqE)&0cQ4JOUL)U#fS}?XRe|RRh@u$1!3e6NqFJwCt2fAfHkza+H+3|sRcM-uG)ZCG zCTYg3-MY+<6~!@jEX-nktAou>AHRC>_pl8Od-m=@nkMjE7f<~B1%R2PC>39Wt`o72 z$V7e^M{J5J-}>wSi*P+3f}I>C$HvJg?!_P8dVA5^<=h?0hy90EV3|~iQN0gG@g8xA0sW0?$3A#E_U+pX0C?lgxA6Q+uak=CdYEfB z;W##WOq~s0EY(U0N0!&}4NE5a)R>YzVOv#jsdkc*XT0Q4g2 z%Nvd;v+(BYJ_Aivv9+~@7k~E_Y10MD`Y4Lg9};I{&YdX)l&}58{~|okhaZHPn`uGU zH4OM=(qv}%#;w=E(1})?nt~*b@uPblp+zGRU->1d>Nw1yX~euD2|v?}DyO>r0d#Hb zXvvtBNZMf~$0oCcg*;9YKoEtO>-n^kGeJYQKSat4qFN~-2qTniD)H2H4W?fJjRY z8ucpHHafKMIVCw^%|c;+qF~k^RaKbInff2HQ^{Z_p3Mb7w2@_~@oC_CenCU9k)*T{ zs0=s)*3Q7EqQ$Xn!DYp?=_IJvD!Ag(^YaZ)C7l;>oZyc89z&XDtT}jS)GB!-g)}D} zo$dfCBNt2UfmB+i5eI2gen1e^DyttF+r*B4rQ^i)}bV z*Kp>kd+~vD&LkUeBB5fehQ?8hAK&vR2__Fneo8_;wm&S(K-sZ#wj0XYV@T|jCP!t$ zM#IoyTNXOK0cw>J9NU7;p~EmF$6LKt#e0X3!Sy^Cx{i}i+>6o3MYlgd6vc2oAB{Sp z9d~=fJhbN67F^F~HI33uzxsu1snY`Ryb**zUY06EhBQfV`2SbfmG(+fmEq^qUe#UI z>s@AH7KTL-;|yWRWzi8e3??jJh!Ty7U;G{Z2#L{%L8FO6RNN3kP>`4m8c>0NVaYJt zow?n&_pYw>RQch3Pj%m!LF&uQ+^KtSSDka-<$2%tdCp(NU;q9u*fy1a41-E3fnP%u z#hA>9u3V)Q$~$YCK;2ua%#Nos2yUJ#%M#&&K(ewdD9vGD%4M)C;_@1KE+u_M_b47d z71|d(b5Un~z-CZqSv3PdiBk`3AqX1PLa7vmz5t*sF_}#8(yOl{ib&x(=WazMm#sQ5 zOAhq`0>li1sH$ztq!JfPX&XAtbr`0Bjcyxj-4@PYyo~-}48h=(p=%h=LR`0R9hZ6o zfQ^{zScu{jL7k|!6gq$Q;tqrm=rnySq6DsMV>%0Q%Z*17E+QN|you|N9;u}51y5*} zWr4w97hbIf*Y)s&pF9f)pcVL)oVu=S=(Zb(lN96W98g4{Sq=z+jcyCwb`v}O0XEmW z`0(TNh~gC8R)A*UV|O%0QIr&t>pBkZTSJ_r=(L(h(+rn-yKGcQ1XbfQZo*NWY3Mj~ zU=wFAT!zk)K-i9#22=XqucFPdt&()PLSE1sg}!JN<9DKD;QQe*)mi&INYSc9D%w= zxwP_hb+(Md(G)s2^e8aJGEMk?9ch}vuhSs|fFPif;7)&+?;`+jcQ8h?;bT0R!gXvc zq6GDti~e8?+cMDz>KF`3Bfw)(1(<>01 zlhX!vFrJduy#oDo&M}Lkgy&c=3Q^ixQg~9n9wogi(U6+i$_Gx7_@R&0Krem=5$i@BSBWzWqUU z&?$YR@S3~_;Q-2`luQ;wMzch>-GFUbmCAFRrbyEamSti%npPpG#Vy;E3Q+`QnFcyd zYQXVao1F+5>nXPvH+i2VJcy%dG?sh+)^XHoL^xs^CUQw&m}!_W3<81+N#$YHD*fbB zzk;STF?BLxDtZ+wZNP@EcTArlW@svX~|1WR_)NJegLYg=JaD^BnVV z0mrf7*d%BYE+VQ{j3+P|(U-Zz=0=z5x2*lH!t2K$dywE;tU5n>ma;4`olQ~m>P&66 zaubtaKKI9$QG#%fJeQR>2a|TF4dXd9F1l*v+YO(ovFLtHBXoE&4XfDzje$Bcq?ATj zEL*KSt7N+GguyxTZJG$97)hE@i?c(b8j@9mS`8mjl%U!0F`3R$ zuens(jVU5g6%xbHFb^Y2Qx-AyZ**|#%lEO@C#h{+*HQACN=byb6*ZP&a0lq}y?p=2 zzl7&Fq*KTWx)jM0FlrbE9Lt2yQJ=2sX!vzp>J6xottfE#zy>VSgy*@~9gF}WFd9#* zh*)SE9Oe=<3@{ z%eGCpwvBnXAO#)XXxAS;fTAd|I~c(<4b*EMG)=?SU7ufhm``|F1rbx$>-D8-5wK^Y zpad>lxPTX4`3Hql{Jx5^gvoOgC4ibob$(3~$Z`n*LiTm8JxdO;ORkmxfRyyyD*Q_l z@(c>}quib7v=)n)fE?}(&@@7zZOg%&(3eN! z5q|%|YgP25jDY$)3ecj#tP;~%SXCs-vP7FD*@~jXTDOI85uw{@;mp~K$a0BJGeDA& z_=#g%ICFj*SuWx6kjx@VkaO2$w@u*iJdD^=mU#H9TR42^&~k}*56eRJ4``J)Op*jB z3)qeeQKhx~pa1}YA3ptT70HreqkA-NQ#N!R&4!OG&#~U^U@#n2A^&=}jp3N2-VN^D z8;mC$4N_#9WCVIUtokG+0f#!{B%{d;HIJ$Tj$`B8#mm%Jpb2cQw{X)9hk+8rc%{Pr zPdT%>%#p+qbX{km7!SG3p@02}C%zXnS}Y;WcA_khOB$)A?x-9|64T6lnnLE(!))4i zWle3%#(@KyK#`-@A7d7Vn9ZY=ArT7eYaUs2l|Kq0AUQgia41b4N{#1|3Cr0-(87{M zS6;PbpeyieNYfOTc6Kow5#qW#7~|20PU7JH4VvH+Vh>?L!A!(TJVOXnk%q48e6M<| zykUeu&}iW1V~6n8yB|{Ni4p3uEFsA%z|b`KHIG0W4$ak}ur0F!?XrxXh5m2(UK%_Y zq=t?x(?FbLaBK^4oI+>+$ImPP2kasaMFQtxq`Qp(dNZ>~59(k#Pzw~hBdI?EAphMSKa z#EIjd15mY4zNubZ&DtWHQg;dLFVggK1jK3as7%vykRl2HUo)jX)la zHb_wvm`uj7ZKv8~QM4dFJ%hsZGasMB={Mhl$uif5P7Os_M&psQ`2sbMIs!F~02?*#@1buuRjyU_6DxC8B~G zhZfw-qpN(Kf1kmfib7D`+GI)Vf>Vqvm-yD>UxQ^?71~`{|2)TdGD54_V$BB%gJqsG zQD%a3XU|}LgIHyE`+aQg+lN}+r=LY`0W!}q9uM)i({Ce+6BLYlsD>4tc@_i+WmdyA zp6g&bU%<9Z1b!V-O7sWRN6`ps00b5b^608$IAqck+cFWy2^`19bRMD=_(-!1-By4H zPHtU6FMq{#t%ycls$x?~bE~gkegBVs`s*qZ@mw2@O2|Aq)gr*^h6I{BygI7<#Rh98HMy~}EDX6Y1$#v1dry8+(VFp`i zLY(QdC^R#U_X$MIvkb0FEUsYT2U2&bz5z*+ zz_Lm0=>J}2iC(XVKmX-4LT+*re25M1rtA;La4eI1^lfNDV9Z9#G?oGqhM^;jsOwST z0G>;u%%VyicvwhKSo+AR`_XPI{lhD-D|htjzn9igO7g}+2=seBv^yPWnoivHn!s!} zLlVar4Tfm8n#l7UulgPQ9)H5)2N?G%qCLfO-y$(?nV>%Bp7*3F;8M>V&7K<2R6vHxUE<^2o zA;6JphBVFLI2Pt%1f2cvIIx`F=zFmGnFftY}R00000NkvXXu0mjfkkMPr literal 69036 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%0DbidU+vi=~hGp7Trp&ZIJ> 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 BdinqW From 9ec2a89a951e325fca796a915fece49b802d6f23 Mon Sep 17 00:00:00 2001 From: cheater Date: Thu, 23 Jul 2026 01:35:21 +0300 Subject: [PATCH 10/10] remove temp comments --- sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp | 19 +++++-------- sources/HAL/HAL.CommandList.ixx | 6 ++--- sources/HAL/HAL.Debug.ixx | 6 ++--- sources/HAL/HAL.ResourceStates.ixx | 14 +++++----- .../FrameGraph/FrameGraph.Base.ixx | 4 +-- .../RenderSystem/FrameGraph/FrameGraph.cpp | 27 +++++-------------- 6 files changed, 27 insertions(+), 49 deletions(-) diff --git a/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp b/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp index fb14912a..2a4d8aa9 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp +++ b/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp @@ -265,19 +265,12 @@ namespace HAL { PROFILE(L"Queue::execute"); - // One ExecuteCommandLists per list. - // - // Batching several lists into one ECL scope is phase 4 step 2, and it - // requires TaskBuilder::link_list_groups to chain resource state - // across list boundaries first: 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). Each list is currently self-contained (SYNC_NONE - // seed in, SYNC_NONE release out), so it must get its own scope. - // Accumulate into one ExecuteCommandLists (requirement 6). Legal now - // that TaskBuilder::link_list_groups chains resource state across list - // boundaries within a group — no per-list flush. gpu_wait/signal still - // flush at real sync points. + // 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()); } diff --git a/sources/HAL/HAL.CommandList.ixx b/sources/HAL/HAL.CommandList.ixx index ce477698..5d82de2a 100644 --- a/sources/HAL/HAL.CommandList.ixx +++ b/sources/HAL/HAL.CommandList.ixx @@ -80,7 +80,7 @@ export{ std::set need_check_transitions; void create_usage_point(BarrierSync operation, bool end = true); - // --- Operation batching (phase 3) --------------------------------- + // --- 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 @@ -121,8 +121,8 @@ export{ void use_resource(const HAL::Resource* resource); // Resources this list touched (populated by use_resource). Used by - // list-group compilation to find resources shared between lists that - // land in the same ExecuteCommandLists scope. + // 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: diff --git a/sources/HAL/HAL.Debug.ixx b/sources/HAL/HAL.Debug.ixx index 57bb2bbb..8034c4fd 100644 --- a/sources/HAL/HAL.Debug.ixx +++ b/sources/HAL/HAL.Debug.ixx @@ -10,9 +10,9 @@ export namespace HAL { constexpr bool RunForPix = false; - // Phase-3 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. + // 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 diff --git a/sources/HAL/HAL.ResourceStates.ixx b/sources/HAL/HAL.ResourceStates.ixx index c48eecd4..31b18562 100644 --- a/sources/HAL/HAL.ResourceStates.ixx +++ b/sources/HAL/HAL.ResourceStates.ixx @@ -112,10 +112,10 @@ export bool used = false; bool need_discard = false; - // List-group compilation (phase 4): 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 + // 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, @@ -142,8 +142,8 @@ export bool used = false; - // Operation batching (phase 3): the id of the batch this resource was - // last touched in, and the state it was transitioned to. The command + // 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; @@ -256,7 +256,7 @@ export void connect(Transitions* from, Transitions* to); - // List-group chaining (phase 4). `from` and `to` are two lists that + // `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 diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx b/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx index faec13a6..a5d2bd47 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx +++ b/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx @@ -872,8 +872,8 @@ public: void process_transitions(); void process_fences(); - // Phase 4: mirror commit_command_lists' batching to find the sets of - // lists that will be submitted in ONE ExecuteCommandLists, and chain + // 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. diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.cpp index 14fad9b2..f2102b66 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.cpp @@ -385,9 +385,8 @@ namespace FrameGraph // 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. This is what removes the class-1 #1334 - // flood (uploaded assets no longer rest in COMMON / rely on implicit - // promotion) — validated 2026-07-21. + // 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(); @@ -667,24 +666,10 @@ namespace FrameGraph builder.process_transitions(); builder.process_fences(); - // DISABLED — phase 4 step 2 (multiple command lists per ExecuteCommandLists). - // - // link_list_groups() runs AFTER process_transitions(), which has already - // finalized the persistent GPU layout via set_cpu_state (gpu.layout = - // last usage's layout). Chaining then retroactively suppresses barriers - // that computation depended on, so the tracked layout and the real layout - // drift apart and the next frame's seed mismatches (D3D12 #1334). - // - // The fix is not another suppression rule: group membership has to be - // known BEFORE transitions are generated, so releases are never emitted - // for intra-group hand-offs in the first place and set_cpu_state sees the - // truth. Kept compiled but unreferenced until that reordering is done. - // Requirement 6: chain resource state across command-list boundaries so - // multiple lists share one ExecuteCommandLists scope. Depends on the - // UploadManager (class-1 assets rest in a read state) and chain_lists' - // all-none handling (class-2 FG transients). #1417/#1334 eliminated - // 2026-07-22 (2 frame-1-transient #1334 on a pre-promote deserialize - // texture remain). + // 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();