From 5c2eeb91629894fd511fccb6934e743f9f62d08c Mon Sep 17 00:00:00 2001 From: goldislead <69987043+goldislead@users.noreply.github.com> Date: Wed, 8 Apr 2026 01:29:26 -0700 Subject: [PATCH] [GPU/D3D12] Implement ZPD queries for ROV path A new counter buffer path has been added for the ROV ZPD path. The backend allocates and publishes a counter slot for the active query, the translated PS accumulates surviving MSAA samples into that slot, and submission close copies finished counters to readback. --- src/xenia/gpu/command_processor.cc | 3 +- .../gpu/d3d12/d3d12_command_processor.cc | 162 ++++++++- src/xenia/gpu/d3d12/d3d12_command_processor.h | 13 +- src/xenia/gpu/d3d12/d3d12_zpd_query_pool.cc | 341 +++++++++++++----- src/xenia/gpu/d3d12/d3d12_zpd_query_pool.h | 35 +- src/xenia/gpu/d3d12/deferred_command_list.cc | 16 +- src/xenia/gpu/d3d12/deferred_command_list.h | 17 +- src/xenia/gpu/dxbc.h | 5 + src/xenia/gpu/dxbc_shader_translator.cc | 23 +- src/xenia/gpu/dxbc_shader_translator.h | 10 +- src/xenia/gpu/dxbc_shader_translator_om.cc | 55 +++ 11 files changed, 558 insertions(+), 122 deletions(-) diff --git a/src/xenia/gpu/command_processor.cc b/src/xenia/gpu/command_processor.cc index 5f3d475fe..b7ff0bb8e 100644 --- a/src/xenia/gpu/command_processor.cc +++ b/src/xenia/gpu/command_processor.cc @@ -57,7 +57,8 @@ DEFINE_string( occlusion_query, "fast", "Controls hardware occlusion query behavior for EVENT_WRITE_ZPD.\n" "Used for effects like lens flares, object culling, and auto-exposure.\n" - "ROV render path currently supports fake mode only.\n" + "Titles that use QueryBatch are not currently supported and fall back to\n" + "fake mode, regardless of this setting.\n" " fake: Write a fake result without asking the GPU. Safe for most games,\n" " though some effects may look slightly wrong.\n" " fast: Ask the GPU but don't wait for the answer. Writes a cached\n" diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.cc b/src/xenia/gpu/d3d12/d3d12_command_processor.cc index 8b425091c..139d7fb0e 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.cc +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.cc @@ -310,8 +310,8 @@ ID3D12RootSignature* D3D12CommandProcessor::GetRootSignature( parameter.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; } - // Shared memory and, if ROVs are used, EDRAM. - D3D12_DESCRIPTOR_RANGE shared_memory_and_edram_ranges[3]; + // Shared memory and, if ROVs are used, EDRAM and the ZPD counter. + D3D12_DESCRIPTOR_RANGE shared_memory_and_edram_ranges[4]; { auto& parameter = parameters[kRootParameter_Bindful_SharedMemoryAndEdram]; parameter.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; @@ -344,6 +344,14 @@ ID3D12RootSignature* D3D12CommandProcessor::GetRootSignature( UINT(DxbcShaderTranslator::UAVRegister::kEdram); shared_memory_and_edram_ranges[2].RegisterSpace = 0; shared_memory_and_edram_ranges[2].OffsetInDescriptorsFromTableStart = 2; + ++parameter.DescriptorTable.NumDescriptorRanges; + shared_memory_and_edram_ranges[3].RangeType = + D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + shared_memory_and_edram_ranges[3].NumDescriptors = 1; + shared_memory_and_edram_ranges[3].BaseShaderRegister = + UINT(DxbcShaderTranslator::UAVRegister::kZpdRovCounter); + shared_memory_and_edram_ranges[3].RegisterSpace = 0; + shared_memory_and_edram_ranges[3].OffsetInDescriptorsFromTableStart = 3; } } @@ -871,8 +879,8 @@ bool D3D12CommandProcessor::SetupContext() { } // Initially in open state, wait until a deferred command list submission. command_list_->Close(); - // Optional - added in Creators Update (SDK 10.0.15063.0). command_list_->QueryInterface(IID_PPV_ARGS(&command_list_1_)); + command_list_->QueryInterface(IID_PPV_ARGS(&command_list_2_)); bindless_resources_used_ = cvars::d3d12_bindless && @@ -1098,7 +1106,7 @@ bool D3D12CommandProcessor::SetupContext() { root_bindless_sampler_range.OffsetInDescriptorsFromTableStart = 0; } // View heap. - D3D12_DESCRIPTOR_RANGE root_bindless_view_ranges[4]; + D3D12_DESCRIPTOR_RANGE root_bindless_view_ranges[5]; { auto& parameter = root_parameters_bindless[kRootParameter_Bindless_ViewHeap]; @@ -1121,6 +1129,18 @@ bool D3D12CommandProcessor::SetupContext() { range.RegisterSpace = 0; range.OffsetInDescriptorsFromTableStart = UINT(SystemBindlessView::kEdramR32UintUAV); + assert_true(parameter.DescriptorTable.NumDescriptorRanges < + xe::countof(root_bindless_view_ranges)); + auto& counter_range = + root_bindless_view_ranges[parameter.DescriptorTable + .NumDescriptorRanges++]; + counter_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + counter_range.NumDescriptors = 1; + counter_range.BaseShaderRegister = + UINT(DxbcShaderTranslator::UAVRegister::kZpdRovCounter); + counter_range.RegisterSpace = 0; + counter_range.OffsetInDescriptorsFromTableStart = + UINT(SystemBindlessView::kZpdROVCounterRawUAV); } // Used UAV and SRV ranges must not overlap on Nvidia Fermi, so textures // have OffsetInDescriptorsFromTableStart after all static descriptors of @@ -1672,6 +1692,13 @@ bool D3D12CommandProcessor::SetupContext() { view_bindless_heap_cpu_start_, uint32_t(SystemBindlessView::kEdramR32G32B32A32UintUAV)), 4); + // kZpdROVCounterRawUAV. + ui::d3d12::util::CreateBufferRawUAV( + device, + provider.OffsetViewDescriptor( + view_bindless_heap_cpu_start_, + uint32_t(SystemBindlessView::kZpdROVCounterRawUAV)), + nullptr, 0); // kGammaRampTableSRV. WriteGammaRampSRV(false, provider.OffsetViewDescriptor( @@ -1798,6 +1825,7 @@ void D3D12CommandProcessor::ShutdownContext() { deferred_command_list_.Reset(); ui::d3d12::util::ReleaseAndNull(command_list_1_); + ui::d3d12::util::ReleaseAndNull(command_list_2_); ui::d3d12::util::ReleaseAndNull(command_list_); ClearCommandAllocatorCache(); @@ -4027,7 +4055,8 @@ bool D3D12CommandProcessor::EndSubmission(bool is_swap) { command_allocator_writable_first_->command_allocator; command_allocator->Reset(); command_list_->Reset(command_allocator, nullptr); - deferred_command_list_.Execute(command_list_, command_list_1_); + deferred_command_list_.Execute(command_list_, command_list_1_, + command_list_2_); command_list_->Close(); ID3D12CommandList* execute_command_lists[] = {command_list_}; direct_queue->ExecuteCommandLists(1, execute_command_lists); @@ -4664,9 +4693,17 @@ XE_NOINLINE void D3D12CommandProcessor::UpdateSystemConstantValues_Impl( rb_depth_info.depth_base * edram_tile_dwords_scaled; update_dirty_uint32_cmp(system_constants_.edram_depth_base_dwords_scaled, depth_base_dwords_scaled); - system_constants_.edram_depth_base_dwords_scaled = depth_base_dwords_scaled; + uint32_t zpd_rov_counter_index = UINT32_MAX; + if (zpd_active_query_is_rov_ && zpd_active_query_index_ != UINT32_MAX && + zpd_host_query_pool_->rov_counter_initialized()) { + zpd_rov_counter_index = zpd_active_query_index_; + } + update_dirty_uint32_cmp(system_constants_.zpd_rov_counter_index, + zpd_rov_counter_index); + system_constants_.zpd_rov_counter_index = zpd_rov_counter_index; + // For non-polygons, front polygon offset is used, and it's enabled if // POLY_OFFSET_PARA_ENABLED is set, for polygons, separate front and back // are used. @@ -5543,8 +5580,8 @@ bool D3D12CommandProcessor::UpdateBindings_BindfulPath( size_t view_count_full_update = 2 + texture_count_vertex + texture_count_pixel; if (edram_rov_used) { - // + EDRAM UAV. - ++view_count_full_update; + // + EDRAM UAV and the ZPD counter UAV. + view_count_full_update += 2; } D3D12_CPU_DESCRIPTOR_HANDLE view_cpu_handle; D3D12_GPU_DESCRIPTOR_HANDLE view_gpu_handle; @@ -5588,7 +5625,7 @@ bool D3D12CommandProcessor::UpdateBindings_BindfulPath( bindful_textures_written_vertex_ = false; bindful_textures_written_pixel_ = false; // If updating fully, write the shared memory SRV and UAV descriptors and, - // if needed, the EDRAM descriptor. + // if needed, the EDRAM and ZPD counter descriptors. gpu_handle_shared_memory_srv_and_edram_ = view_gpu_handle; shared_memory_->WriteRawSRVDescriptor(view_cpu_handle); view_cpu_handle.ptr += descriptor_size_view; @@ -5600,8 +5637,19 @@ bool D3D12CommandProcessor::UpdateBindings_BindfulPath( render_target_cache_->WriteEdramUintPow2UAVDescriptor(view_cpu_handle, 2); view_cpu_handle.ptr += descriptor_size_view; view_gpu_handle.ptr += descriptor_size_view; + if (zpd_host_query_pool_->rov_counter_initialized()) { + ui::d3d12::util::CreateBufferRawUAV( + provider.GetDevice(), view_cpu_handle, + zpd_host_query_pool_->rov_counter_buffer(), + sizeof(uint32_t) * zpd_host_query_pool_->capacity()); + } else { + ui::d3d12::util::CreateBufferRawUAV(provider.GetDevice(), + view_cpu_handle, nullptr, 0); + } + view_cpu_handle.ptr += descriptor_size_view; + view_gpu_handle.ptr += descriptor_size_view; } - // Null SRV + UAV + EDRAM. + // Null SRV + UAV + EDRAM + ZPD counter. gpu_handle_shared_memory_uav_and_edram_ = view_gpu_handle; ui::d3d12::util::CreateBufferRawSRV(provider.GetDevice(), view_cpu_handle, nullptr, 0); @@ -5614,6 +5662,17 @@ bool D3D12CommandProcessor::UpdateBindings_BindfulPath( render_target_cache_->WriteEdramUintPow2UAVDescriptor(view_cpu_handle, 2); view_cpu_handle.ptr += descriptor_size_view; view_gpu_handle.ptr += descriptor_size_view; + if (zpd_host_query_pool_->rov_counter_initialized()) { + ui::d3d12::util::CreateBufferRawUAV( + provider.GetDevice(), view_cpu_handle, + zpd_host_query_pool_->rov_counter_buffer(), + sizeof(uint32_t) * zpd_host_query_pool_->capacity()); + } else { + ui::d3d12::util::CreateBufferRawUAV(provider.GetDevice(), + view_cpu_handle, nullptr, 0); + } + view_cpu_handle.ptr += descriptor_size_view; + view_gpu_handle.ptr += descriptor_size_view; } current_graphics_root_up_to_date_ &= ~(1u << kRootParameter_Bindful_SharedMemoryAndEdram); @@ -5746,16 +5805,57 @@ void D3D12CommandProcessor::EnsureZPDQueryResources() { bool can_recreate = !zpd_active_segment_.logical_active && !zpd_active_segment_.segment_active && + zpd_active_query_index_ == UINT32_MAX && + !zpd_active_query_is_rov_ && !zpd_host_query_pool_->has_pending_resolve_batch() && zpd_resolves_in_flight_.empty(); + // The ROV counter clear uses WriteBufferImmediate, so only initialize when + // CommandList2 is available. + bool initialize_rov_counter = + render_target_cache_->GetPath() == + RenderTargetCache::Path::kPixelShaderInterlock && + command_list_2_ != nullptr; + zpd_host_query_pool_->EnsureInitialized(GetD3D12Provider(), - kZPDQueryPoolCapacity, can_recreate); + kZPDQueryPoolCapacity, can_recreate, + initialize_rov_counter); + if (bindless_resources_used_) { + D3D12_CPU_DESCRIPTOR_HANDLE handle = + GetD3D12Provider().OffsetViewDescriptor( + view_bindless_heap_cpu_start_, + uint32_t(SystemBindlessView::kZpdROVCounterRawUAV)); + if (zpd_host_query_pool_->rov_counter_initialized()) { + ui::d3d12::util::CreateBufferRawUAV( + GetD3D12Provider().GetDevice(), handle, + zpd_host_query_pool_->rov_counter_buffer(), + sizeof(uint32_t) * zpd_host_query_pool_->capacity()); + } else { + ui::d3d12::util::CreateBufferRawUAV(GetD3D12Provider().GetDevice(), + handle, nullptr, 0); + } + } +} + +bool D3D12CommandProcessor::IsZPDQueryPoolReady() const { + if (!zpd_host_query_pool_ || !zpd_host_query_pool_->is_initialized()) { + return false; + } + if (!render_target_cache_ || + render_target_cache_->GetPath() != + RenderTargetCache::Path::kPixelShaderInterlock) { + return true; + } + return zpd_host_query_pool_->rov_counter_initialized(); } bool D3D12CommandProcessor::CanOpenZPDQuery() const { return submission_open_; } CommandProcessor::QueryOpenResult D3D12CommandProcessor::OpenZPDQuery( ReportHandle report_handle, bool can_close_submission) { + bool use_rov_counter_path = + zpd_host_query_pool_->rov_counter_initialized() && + render_target_cache_->GetPath() == + RenderTargetCache::Path::kPixelShaderInterlock; bool is_pool_exhausted = !zpd_host_query_pool_->has_free_indices(); if (is_pool_exhausted) { @@ -5808,29 +5908,58 @@ CommandProcessor::QueryOpenResult D3D12CommandProcessor::OpenZPDQuery( return QueryOpenResult::kFailed; } + zpd_active_query_is_rov_ = use_rov_counter_path; + + // ROV queries don't use D3D12 occlusion queries at all. + // While the segment is open, the translated pixel shader accumulates passed + // MSAA samples into one counter slot selected via zpd_rov_counter_index. + // Clear the slot here so a recycled index never inherits old counts. + if (zpd_active_query_is_rov_) { + zpd_host_query_pool_->ClearROVCounter(deferred_command_list_, + zpd_active_query_index_); + return QueryOpenResult::kOpened; + } + zpd_host_query_pool_->BeginQuery(deferred_command_list_, zpd_active_query_index_); return QueryOpenResult::kOpened; } bool D3D12CommandProcessor::CloseZPDQuery(ReportHandle report_handle) { - zpd_host_query_pool_->EndQuery(deferred_command_list_, - zpd_active_query_index_); - zpd_host_query_pool_->QueueQueryResolve(zpd_active_query_index_); + if (zpd_active_query_is_rov_) { + zpd_host_query_pool_->QueueQueryResolve(zpd_active_query_index_, true); + } else { + zpd_host_query_pool_->EndQuery(deferred_command_list_, + zpd_active_query_index_); + zpd_host_query_pool_->QueueQueryResolve(zpd_active_query_index_, false); + } PendingQueryResolve resolve; resolve.submission = GetCurrentSubmission(); resolve.query_index = zpd_active_query_index_; resolve.query_generation = zpd_active_query_generation_; + resolve.uses_rov_counter = zpd_active_query_is_rov_; resolve.report_handle = report_handle; zpd_resolves_in_flight_.push_back(resolve); zpd_active_query_index_ = UINT32_MAX; zpd_active_query_generation_ = 0; + zpd_active_query_is_rov_ = false; return true; } bool D3D12CommandProcessor::DiscardZPDQuery() { + if (zpd_active_query_is_rov_) { + // The slot counter may be dirty if draws ran between OpenZPDQuery and here, + // but the next OpenZPDQuery will zero it before any new shader accumulates. + zpd_host_query_pool_->ReleaseQueryIndex(zpd_active_query_index_, + zpd_active_query_generation_); + zpd_active_query_index_ = UINT32_MAX; + zpd_active_query_generation_ = 0; + zpd_active_query_is_rov_ = false; + return true; + } + // D3D12 requires a paired EndQuery before the slot can be released. // EndSubmission flushes it so the slot can be freed without a resolve. zpd_host_query_pool_->EndQuery(deferred_command_list_, @@ -5842,6 +5971,7 @@ bool D3D12CommandProcessor::DiscardZPDQuery() { zpd_active_query_generation_); zpd_active_query_index_ = UINT32_MAX; zpd_active_query_generation_ = 0; + zpd_active_query_is_rov_ = false; return true; } @@ -5864,8 +5994,8 @@ void D3D12CommandProcessor::PumpQueryResolves() { if (zpd_host_query_pool_->GenerationMatches(resolve.query_index, resolve.query_generation)) { - uint64_t raw_samples = - zpd_host_query_pool_->GetQueryReadbackValue(resolve.query_index); + uint64_t raw_samples = zpd_host_query_pool_->GetQueryReadbackValue( + resolve.query_index, resolve.uses_rov_counter); zpd_host_query_pool_->ReleaseQueryIndex(resolve.query_index, resolve.query_generation); OnZPDQueryResolved(resolve.report_handle, raw_samples); diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.h b/src/xenia/gpu/d3d12/d3d12_command_processor.h index 1025a2ad7..d4cd99a87 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.h +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.h @@ -182,6 +182,7 @@ class D3D12CommandProcessor final : public CommandProcessor { kEdramR32UintUAV, kEdramR32G32UintUAV, kEdramR32G32B32A32UintUAV, + kZpdROVCounterRawUAV, kGammaRampTableSRV, kGammaRampPWLSRV, @@ -511,18 +512,19 @@ class D3D12CommandProcessor final : public CommandProcessor { // BeginQuery/EndQuery must be in the same command list, segments split at // EndSubmission, resume at BeginSubmission. Discarded queries still need // EndQuery or the heap slot breaks on some drivers. RecordZPDResolveBatch - // emits coalesced ResolveQueryData at submit. + // emits coalesced ResolveQueryData and ROV counter copies at submit. void EnsureZPDQueryResources() override; void ShutdownZPDQueryResources() override { zpd_resolves_in_flight_.clear(); + zpd_active_query_index_ = UINT32_MAX; + zpd_active_query_generation_ = 0; + zpd_active_query_is_rov_ = false; if (zpd_host_query_pool_) { zpd_host_query_pool_->Shutdown(); } } - bool IsZPDQueryPoolReady() const override { - return zpd_host_query_pool_->is_initialized(); - } + bool IsZPDQueryPoolReady() const override; bool CanOpenZPDQuery() const override; QueryOpenResult OpenZPDQuery(ReportHandle report_handle, @@ -542,10 +544,12 @@ class D3D12CommandProcessor final : public CommandProcessor { uint64_t submission = 0; uint32_t query_index = UINT32_MAX; uint32_t query_generation = 0; + bool uses_rov_counter = false; ReportHandle report_handle = kInvalidReportHandle; }; uint32_t zpd_active_query_index_ = UINT32_MAX; uint32_t zpd_active_query_generation_ = 0; + bool zpd_active_query_is_rov_ = false; std::deque zpd_resolves_in_flight_; std::unique_ptr completion_timeline_; @@ -578,6 +582,7 @@ class D3D12CommandProcessor final : public CommandProcessor { CommandAllocator* command_allocator_submitted_last_ = nullptr; ID3D12GraphicsCommandList* command_list_ = nullptr; ID3D12GraphicsCommandList1* command_list_1_ = nullptr; + ID3D12GraphicsCommandList2* command_list_2_ = nullptr; DeferredCommandList deferred_command_list_; // Should bindless textures and samplers be used - many times faster diff --git a/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.cc b/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.cc index 6fb4ef719..985846d0b 100644 --- a/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.cc +++ b/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.cc @@ -22,81 +22,156 @@ namespace d3d12 { bool D3D12ZPDQueryPool::EnsureInitialized( const ui::d3d12::D3D12Provider& provider, uint32_t requested_capacity, - bool can_recreate) { - if (is_initialized() && (capacity_ == requested_capacity || !can_recreate)) { + bool can_recreate, bool initialize_rov_counter) { + bool rtv_initialized = is_initialized(); + bool rov_initialized = rov_counter_initialized(); + if (rtv_initialized && (!initialize_rov_counter || rov_initialized) && + (capacity_ == requested_capacity || !can_recreate)) { return true; } - // Can't recreate while resolves are in-flight, that would destroy the heap - // under a live ResolveQueryData call. - assert_true(!is_initialized() || !has_pending_resolve_batch()); - Shutdown(); + if (rtv_initialized && capacity_ != requested_capacity) { + if (!can_recreate) { + requested_capacity = capacity_; + } else { + // Can't recreate while resolves are in-flight, that would destroy the + // backing resources under pending resolve or copy work. + assert_true(!has_pending_resolve_batch()); + Shutdown(); + rtv_initialized = false; + rov_initialized = false; + } + } ID3D12Device* device = provider.GetDevice(); - D3D12_QUERY_HEAP_DESC heap_desc = {}; - heap_desc.Type = D3D12_QUERY_HEAP_TYPE_OCCLUSION; - heap_desc.Count = requested_capacity; - heap_desc.NodeMask = 0; + if (!rtv_initialized) { + D3D12_QUERY_HEAP_DESC heap_desc = {}; + heap_desc.Type = D3D12_QUERY_HEAP_TYPE_OCCLUSION; + heap_desc.Count = requested_capacity; + heap_desc.NodeMask = 0; - if (FAILED(device->CreateQueryHeap(&heap_desc, IID_PPV_ARGS(&query_heap_)))) { - XELOGW( - "D3D12ZPDQueryPool: Failed to create the ZPD query " - "heap, falling back to fake sample counts."); + if (FAILED( + device->CreateQueryHeap(&heap_desc, IID_PPV_ARGS(&query_heap_)))) { + XELOGW( + "D3D12ZPDQueryPool: Failed to create the ZPD query " + "heap, falling back to fake sample counts."); + return false; + } + + D3D12_RESOURCE_DESC buffer_desc; + ui::d3d12::util::FillBufferResourceDesc( + buffer_desc, sizeof(uint64_t) * requested_capacity, + D3D12_RESOURCE_FLAG_NONE); + + if (FAILED(device->CreateCommittedResource( + &ui::d3d12::util::kHeapPropertiesReadback, + provider.GetHeapFlagCreateNotZeroed(), &buffer_desc, + D3D12_RESOURCE_STATE_COPY_DEST, nullptr, + IID_PPV_ARGS(&readback_buffer_)))) { + XELOGW( + "D3D12ZPDQueryPool: Failed to allocate the ZPD query " + "readback buffer, falling back to fake sample counts."); + Shutdown(); + return false; + } + + D3D12_RANGE read_range = {}; + read_range.Begin = 0; + read_range.End = sizeof(uint64_t) * requested_capacity; + + void* mapping = nullptr; + if (FAILED(readback_buffer_->Map(0, &read_range, &mapping))) { + XELOGW( + "D3D12ZPDQueryPool: Failed to map the ZPD query " + "readback buffer, falling back to fake sample counts."); + Shutdown(); + return false; + } + + readback_mapping_ = reinterpret_cast(mapping); + capacity_ = requested_capacity; + + resolve_batch_pending_.assign(requested_capacity, 0); + resolve_batch_indices_.clear(); + rov_counter_resolve_batch_pending_.assign(requested_capacity, 0); + rov_counter_resolve_batch_indices_.clear(); + resolve_batch_ranges_.clear(); + + free_indices_.clear(); + free_indices_.reserve(requested_capacity); + for (uint32_t i = requested_capacity; i > 0; --i) { + free_indices_.push_back(i - 1); + } + index_generations_.assign(requested_capacity, 0); + } + + if (!initialize_rov_counter || rov_initialized) { + return true; + } + + if (rov_counter_readback_mapping_ && rov_counter_readback_buffer_) { + D3D12_RANGE written_range = {0, 0}; + rov_counter_readback_buffer_->Unmap(0, &written_range); + } + rov_counter_readback_mapping_ = nullptr; + rov_counter_readback_buffer_.Reset(); + rov_counter_buffer_.Reset(); + rov_counter_resolve_batch_pending_.assign(requested_capacity, 0); + rov_counter_resolve_batch_indices_.clear(); + + D3D12_RESOURCE_DESC counter_buffer_desc; + ui::d3d12::util::FillBufferResourceDesc( + counter_buffer_desc, sizeof(uint32_t) * requested_capacity, + D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); + if (FAILED(device->CreateCommittedResource( + &ui::d3d12::util::kHeapPropertiesDefault, + provider.GetHeapFlagCreateNotZeroed(), &counter_buffer_desc, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, + IID_PPV_ARGS(&rov_counter_buffer_)))) { + XELOGW("D3D12ZPDQueryPool: Failed to allocate the ZPD ROV counter buffer."); return false; } - D3D12_RESOURCE_DESC buffer_desc; - ui::d3d12::util::FillBufferResourceDesc(buffer_desc, - sizeof(uint64_t) * requested_capacity, + D3D12_RESOURCE_DESC readback_buffer_desc; + ui::d3d12::util::FillBufferResourceDesc(readback_buffer_desc, + sizeof(uint32_t) * requested_capacity, D3D12_RESOURCE_FLAG_NONE); - if (FAILED(device->CreateCommittedResource( &ui::d3d12::util::kHeapPropertiesReadback, - provider.GetHeapFlagCreateNotZeroed(), &buffer_desc, + provider.GetHeapFlagCreateNotZeroed(), &readback_buffer_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, - IID_PPV_ARGS(&readback_buffer_)))) { + IID_PPV_ARGS(&rov_counter_readback_buffer_)))) { XELOGW( - "D3D12ZPDQueryPool: Failed to allocate the ZPD query " - "readback buffer, falling back to fake sample counts."); - Shutdown(); + "D3D12ZPDQueryPool: Failed to allocate the ZPD ROV counter readback " + "buffer."); + rov_counter_buffer_.Reset(); return false; } D3D12_RANGE read_range = {}; read_range.Begin = 0; - read_range.End = sizeof(uint64_t) * requested_capacity; + read_range.End = sizeof(uint32_t) * requested_capacity; void* mapping = nullptr; - if (FAILED(readback_buffer_->Map(0, &read_range, &mapping))) { + if (FAILED(rov_counter_readback_buffer_->Map(0, &read_range, &mapping))) { XELOGW( - "D3D12ZPDQueryPool: Failed to map the ZPD query " - "readback buffer, falling back to fake sample counts."); - Shutdown(); + "D3D12ZPDQueryPool: Failed to map the ZPD ROV counter readback " + "buffer."); + rov_counter_readback_buffer_.Reset(); + rov_counter_buffer_.Reset(); return false; } - readback_mapping_ = reinterpret_cast(mapping); - capacity_ = requested_capacity; - - resolve_batch_pending_.assign(requested_capacity, 0); - resolve_batch_indices_.clear(); - resolve_batch_index_count_ = 0; - - free_indices_.clear(); - free_indices_.reserve(requested_capacity); - for (uint32_t i = requested_capacity; i > 0; --i) { - free_indices_.push_back(i - 1); - } - index_generations_.assign(requested_capacity, 0); - + rov_counter_readback_mapping_ = reinterpret_cast(mapping); return true; } void D3D12ZPDQueryPool::Shutdown() { resolve_batch_pending_.clear(); resolve_batch_indices_.clear(); - resolve_batch_index_count_ = 0; + rov_counter_resolve_batch_pending_.clear(); + rov_counter_resolve_batch_indices_.clear(); free_indices_.clear(); index_generations_.clear(); @@ -107,10 +182,14 @@ void D3D12ZPDQueryPool::Shutdown() { D3D12_RANGE written_range = {0, 0}; readback_buffer_->Unmap(0, &written_range); } - readback_mapping_ = nullptr; + if (rov_counter_readback_mapping_ && rov_counter_readback_buffer_) { + D3D12_RANGE written_range = {0, 0}; + rov_counter_readback_buffer_->Unmap(0, &written_range); + } - readback_buffer_.Reset(); - query_heap_.Reset(); + rov_counter_readback_mapping_ = nullptr; + rov_counter_readback_buffer_.Reset(); + rov_counter_buffer_.Reset(); } bool D3D12ZPDQueryPool::AcquireQueryIndex(uint32_t& query_index, @@ -174,87 +253,165 @@ void D3D12ZPDQueryPool::EndQuery(DeferredCommandList& deferred_command_list, D3D12_QUERY_TYPE_OCCLUSION, query_index); } -void D3D12ZPDQueryPool::QueueQueryResolve(uint32_t query_index) { +void D3D12ZPDQueryPool::QueueQueryResolve(uint32_t query_index, + bool uses_rov_counter) { if (query_index >= capacity_) { return; } // Guard against duplicates. Split paths can touch the same index twice before // the batch drains at EndSubmission. + if (uses_rov_counter) { + if (!rov_counter_resolve_batch_pending_[query_index]) { + rov_counter_resolve_batch_pending_[query_index] = 1; + rov_counter_resolve_batch_indices_.push_back(query_index); + } + return; + } + if (!resolve_batch_pending_[query_index]) { resolve_batch_pending_[query_index] = 1; resolve_batch_indices_.push_back(query_index); - ++resolve_batch_index_count_; } } +void D3D12ZPDQueryPool::ClearROVCounter( + DeferredCommandList& deferred_command_list, uint32_t query_index) const { + if (!rov_counter_initialized() || query_index >= capacity_) { + return; + } + + // This buffer stays in UNORDERED_ACCESS for the duration of its use. Before + // reusing a slot, order this write after any atomic adds issued by the + // previous query that owned the same index. + D3D12_RESOURCE_BARRIER uav_barrier = {}; + uav_barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; + uav_barrier.UAV.pResource = rov_counter_buffer_.Get(); + deferred_command_list.D3DResourceBarrier(1, &uav_barrier); + + // Only the selected 32 bit slot needs to be reset, so use + // WriteBufferImmediate instead of transitioning the whole buffer through a + // copy path. + deferred_command_list.D3DWriteBufferImmediate( + rov_counter_buffer_->GetGPUVirtualAddress() + + static_cast(query_index) * sizeof(uint32_t), + 0u); + + // Order the zero write before any upcoming PS atomic adds so the next query + // using this slot sees the cleared counter value. + deferred_command_list.D3DResourceBarrier(1, &uav_barrier); +} + void D3D12ZPDQueryPool::FlushResolveBatch( DeferredCommandList& deferred_command_list, bool submission_open) { - if (!submission_open) { + if (!submission_open || (resolve_batch_indices_.empty() && + rov_counter_resolve_batch_indices_.empty())) { return; } - if (!resolve_batch_index_count_) { - return; - } - - if (!is_initialized()) { - for (uint32_t index : resolve_batch_indices_) { - resolve_batch_pending_[index] = 0; - } - resolve_batch_indices_.clear(); - resolve_batch_index_count_ = 0; - return; - } - - // Sort so we can coalesce contiguous indices into ranges, cutting down on - // ResolveQueryData calls which have considerable overhead. - std::sort(resolve_batch_indices_.begin(), resolve_batch_indices_.end()); - - resolve_batch_ranges_.clear(); - uint32_t range_start = 0; - uint32_t range_count = 0; - for (uint32_t index : resolve_batch_indices_) { - if (range_count == 0) { + // Sorts indices, coalesces contiguous runs into resolve_batch_ranges_, resets + // pending flags, and clears the index list. + auto build_ranges = [this](std::vector& indices, + std::vector& pending) { + std::sort(indices.begin(), indices.end()); + resolve_batch_ranges_.clear(); + uint32_t range_start = 0; + uint32_t range_count = 0; + for (uint32_t index : indices) { + if (range_count == 0) { + range_start = index; + range_count = 1; + continue; + } + if (index == range_start + range_count) { + ++range_count; + continue; + } + resolve_batch_ranges_.push_back({range_start, range_count}); range_start = index; range_count = 1; - continue; } - - if (index == range_start + range_count) { - ++range_count; - continue; + if (range_count != 0) { + resolve_batch_ranges_.push_back({range_start, range_count}); } + for (uint32_t index : indices) { + pending[index] = 0; + } + indices.clear(); + }; - resolve_batch_ranges_.push_back({range_start, range_count}); - range_start = index; - range_count = 1; + if (!resolve_batch_indices_.empty()) { + if (!is_initialized()) { + for (uint32_t index : resolve_batch_indices_) { + resolve_batch_pending_[index] = 0; + } + resolve_batch_indices_.clear(); + } else { + build_ranges(resolve_batch_indices_, resolve_batch_pending_); + for (const ResolveRange& range : resolve_batch_ranges_) { + deferred_command_list.D3DResolveQueryData( + query_heap_.Get(), D3D12_QUERY_TYPE_OCCLUSION, range.start, + range.count, readback_buffer_.Get(), + range.start * sizeof(uint64_t)); + } + } } - if (range_count != 0) { - resolve_batch_ranges_.push_back({range_start, range_count}); + if (rov_counter_resolve_batch_indices_.empty()) { + return; } - // Reset the batch. ENDs from later in this submission belong to the next. - for (uint32_t index : resolve_batch_indices_) { - resolve_batch_pending_[index] = 0; + if (!rov_counter_initialized()) { + for (uint32_t index : rov_counter_resolve_batch_indices_) { + rov_counter_resolve_batch_pending_[index] = 0; + } + rov_counter_resolve_batch_indices_.clear(); + return; } - resolve_batch_indices_.clear(); - resolve_batch_index_count_ = 0; + // The shader path writes counters through UAV atomics, so resolve on + // this path means copying the finished 32 bit slots out of the UAV buffer. + // The whole buffer is transitioned for the copy and then returned to + // UNORDERED_ACCESS since D3D12 state is tracked per resource, not per range. + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.pResource = rov_counter_buffer_.Get(); + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; + deferred_command_list.D3DResourceBarrier(1, &barrier); + + build_ranges(rov_counter_resolve_batch_indices_, + rov_counter_resolve_batch_pending_); for (const ResolveRange& range : resolve_batch_ranges_) { - deferred_command_list.D3DResolveQueryData( - query_heap_.Get(), D3D12_QUERY_TYPE_OCCLUSION, range.start, range.count, - readback_buffer_.Get(), range.start * sizeof(uint64_t)); + uint64_t offset = static_cast(range.start) * sizeof(uint32_t); + uint64_t size = static_cast(range.count) * sizeof(uint32_t); + deferred_command_list.D3DCopyBufferRegion( + rov_counter_readback_buffer_.Get(), offset, rov_counter_buffer_.Get(), + offset, size); } + + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + deferred_command_list.D3DResourceBarrier(1, &barrier); } -uint64_t D3D12ZPDQueryPool::GetQueryReadbackValue(uint32_t query_index) const { - if (!readback_mapping_ || query_index >= capacity_) { +uint64_t D3D12ZPDQueryPool::GetQueryReadbackValue(uint32_t query_index, + bool uses_rov_counter) const { + if (query_index >= capacity_) { return 0; } - return readback_mapping_[query_index]; + if (uses_rov_counter) { + // ROV queries read back a translated 32 bit sample count. Widen here so + // paths feed the uint64_t resolve. + return rov_counter_readback_mapping_ + ? static_cast( + rov_counter_readback_mapping_[query_index]) + : 0; + } + + return readback_mapping_ ? readback_mapping_[query_index] : 0; } } // namespace d3d12 diff --git a/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.h b/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.h index 3f741f936..6b6813103 100644 --- a/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.h +++ b/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.h @@ -37,6 +37,11 @@ class DeferredCommandList; // // FlushResolveBatch coalesces pending indices into contiguous ranges to cut // down on ResolveQueryData call count. +// +// ROV queries use a separate path instead of normal D3D12 results. They write +// surviving MSAA coverage into a dedicated buffer, one slot per active query. +// QueueQueryResolve + ClearROVCounter are used instead of BeginQuery and +// EndQuery. class D3D12ZPDQueryPool { public: D3D12ZPDQueryPool() = default; @@ -45,7 +50,8 @@ class D3D12ZPDQueryPool { ~D3D12ZPDQueryPool() { Shutdown(); } bool EnsureInitialized(const ui::d3d12::D3D12Provider& provider, - uint32_t requested_capacity, bool can_recreate); + uint32_t requested_capacity, bool can_recreate, + bool initialize_rov_counter); void Shutdown(); bool is_initialized() const { @@ -56,7 +62,17 @@ class D3D12ZPDQueryPool { uint32_t capacity() const { return capacity_; } bool has_pending_resolve_batch() const { - return resolve_batch_index_count_ != 0; + return !resolve_batch_indices_.empty() || + !rov_counter_resolve_batch_indices_.empty(); + } + + bool rov_counter_initialized() const { + return rov_counter_buffer_ && rov_counter_readback_buffer_ && + rov_counter_readback_mapping_ != nullptr && capacity_ != 0; + } + + ID3D12Resource* rov_counter_buffer() const { + return rov_counter_buffer_.Get(); } bool has_free_indices() const { return !free_indices_.empty(); } @@ -69,11 +85,15 @@ class D3D12ZPDQueryPool { uint32_t query_index) const; void EndQuery(DeferredCommandList& deferred_command_list, uint32_t query_index) const; - void QueueQueryResolve(uint32_t query_index); + void QueueQueryResolve(uint32_t query_index, bool uses_rov_counter); + void ClearROVCounter(DeferredCommandList& deferred_command_list, + uint32_t query_index) const; + void FlushResolveBatch(DeferredCommandList& deferred_command_list, bool submission_open); - uint64_t GetQueryReadbackValue(uint32_t query_index) const; + uint64_t GetQueryReadbackValue(uint32_t query_index, + bool uses_rov_counter) const; private: Microsoft::WRL::ComPtr query_heap_; @@ -82,6 +102,10 @@ class D3D12ZPDQueryPool { Microsoft::WRL::ComPtr readback_buffer_; uint64_t* readback_mapping_ = nullptr; + Microsoft::WRL::ComPtr rov_counter_buffer_; + Microsoft::WRL::ComPtr rov_counter_readback_buffer_; + uint32_t* rov_counter_readback_mapping_ = nullptr; + uint32_t capacity_ = 0; std::vector free_indices_; @@ -92,9 +116,10 @@ class D3D12ZPDQueryPool { // Active indices with resolve_batch_pending_[i] == 1, so flush iterates // only the active entries instead of scanning the full capacity. std::vector resolve_batch_indices_; + std::vector rov_counter_resolve_batch_pending_; + std::vector rov_counter_resolve_batch_indices_; // Reusable scratch for coalesced contiguous ranges during flush. std::vector resolve_batch_ranges_; - uint32_t resolve_batch_index_count_ = 0; }; } // namespace d3d12 diff --git a/src/xenia/gpu/d3d12/deferred_command_list.cc b/src/xenia/gpu/d3d12/deferred_command_list.cc index e21d990a4..73313267a 100644 --- a/src/xenia/gpu/d3d12/deferred_command_list.cc +++ b/src/xenia/gpu/d3d12/deferred_command_list.cc @@ -28,7 +28,8 @@ DeferredCommandList::DeferredCommandList( void DeferredCommandList::Reset() { command_stream_.clear(); } void DeferredCommandList::Execute(ID3D12GraphicsCommandList* command_list, - ID3D12GraphicsCommandList1* command_list_1) { + ID3D12GraphicsCommandList1* command_list_1, + ID3D12GraphicsCommandList2* command_list_2) { #if XE_GPU_FINE_GRAINED_DRAW_SCOPES SCOPE_profile_cpu_f("gpu"); #endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES @@ -295,6 +296,19 @@ void DeferredCommandList::Execute(ID3D12GraphicsCommandList* command_list, : nullptr); } } break; + case Command::kD3DWriteBufferImmediate: { + if (command_list_2 != nullptr) { + auto& args = + *reinterpret_cast( + stream); + D3D12_WRITEBUFFERIMMEDIATE_PARAMETER param; + param.Dest = args.dest; + param.Value = args.value; + D3D12_WRITEBUFFERIMMEDIATE_MODE mode = + D3D12_WRITEBUFFERIMMEDIATE_MODE_DEFAULT; + command_list_2->WriteBufferImmediate(1, ¶m, &mode); + } + } break; case Command::kBeginDebugMarker: { auto& args = *reinterpret_cast(stream); const char* label_name = reinterpret_cast( diff --git a/src/xenia/gpu/d3d12/deferred_command_list.h b/src/xenia/gpu/d3d12/deferred_command_list.h index 3bbae0e63..5d4a5d8c7 100644 --- a/src/xenia/gpu/d3d12/deferred_command_list.h +++ b/src/xenia/gpu/d3d12/deferred_command_list.h @@ -40,7 +40,8 @@ class DeferredCommandList { void Reset(); void Execute(ID3D12GraphicsCommandList* command_list, - ID3D12GraphicsCommandList1* command_list_1); + ID3D12GraphicsCommandList1* command_list_1, + ID3D12GraphicsCommandList2* command_list_2); D3D12_RECT* ClearDepthStencilViewAllocatedRects( D3D12_CPU_DESCRIPTOR_HANDLE depth_stencil_view, @@ -471,6 +472,14 @@ class DeferredCommandList { sizeof(D3D12_SAMPLE_POSITION)); } + void D3DWriteBufferImmediate(D3D12_GPU_VIRTUAL_ADDRESS dest, UINT value) { + auto& args = *reinterpret_cast( + WriteCommand(Command::kD3DWriteBufferImmediate, + sizeof(D3DWriteBufferImmediateArguments))); + args.dest = dest; + args.value = value; + } + // Debug marker support for PIX/RenderDoc annotation. void BeginDebugMarker(const char* label_name) { size_t label_len = std::strlen(label_name); @@ -535,6 +544,7 @@ class DeferredCommandList { kD3DSetPipelineState, kSetPipelineStateHandle, kD3DSetSamplePositions, + kD3DWriteBufferImmediate, kBeginDebugMarker, kEndDebugMarker, kInsertDebugMarker, @@ -677,6 +687,11 @@ class DeferredCommandList { D3D12_SAMPLE_POSITION sample_positions[16]; }; + struct D3DWriteBufferImmediateArguments { + D3D12_GPU_VIRTUAL_ADDRESS dest; + UINT value; + }; + struct DebugMarkerHeader { uint32_t label_length; // Followed by null-terminated label string. diff --git a/src/xenia/gpu/dxbc.h b/src/xenia/gpu/dxbc.h index cb88b81b7..3ba21c022 100644 --- a/src/xenia/gpu/dxbc.h +++ b/src/xenia/gpu/dxbc.h @@ -1516,6 +1516,7 @@ enum class Opcode : uint32_t { kStoreRaw = 166, kAtomicAnd = 169, kAtomicOr = 170, + kAtomicIAdd = 173, kEvalSampleIndex = 204, kEvalCentroid = 205, }; @@ -2416,6 +2417,10 @@ class Assembler { uint32_t address_components, const Src& value) { EmitAtomicOp(Opcode::kAtomicOr, dest, address, address_components, value); } + void OpAtomicIAdd(const Dest& dest, const Src& address, + uint32_t address_components, const Src& value) { + EmitAtomicOp(Opcode::kAtomicIAdd, dest, address, address_components, value); + } void OpEvalSampleIndex(const Dest& dest, const Src& value, const Src& sample_index) { uint32_t dest_write_mask = dest.GetMask(); diff --git a/src/xenia/gpu/dxbc_shader_translator.cc b/src/xenia/gpu/dxbc_shader_translator.cc index d41cfca77..4abd2ffdc 100644 --- a/src/xenia/gpu/dxbc_shader_translator.cc +++ b/src/xenia/gpu/dxbc_shader_translator.cc @@ -173,6 +173,7 @@ void DxbcShaderTranslator::Reset() { uav_count_ = 0; uav_index_shared_memory_ = kBindingIndexUnallocated; uav_index_edram_ = kBindingIndexUnallocated; + uav_index_zpd_rov_counter_ = kBindingIndexUnallocated; sampler_bindings_.clear(); @@ -2146,7 +2147,9 @@ constexpr DxbcShaderTranslator::SystemConstantRdef {"xe_edram_32bpp_tile_pitch_dwords_scaled", ShaderRdefTypeIndex::kUint, sizeof(uint32_t)}, {"xe_edram_depth_base_dwords_scaled", ShaderRdefTypeIndex::kUint, - sizeof(uint32_t), sizeof(uint32_t)}, + sizeof(uint32_t)}, + {"xe_zpd_rov_counter_index", ShaderRdefTypeIndex::kUint, + sizeof(uint32_t)}, {"xe_color_exp_bias", ShaderRdefTypeIndex::kFloat4, sizeof(float) * 4}, @@ -2548,6 +2551,11 @@ void DxbcShaderTranslator::WriteResourceDefinition() { if (uav_index_edram_ != kBindingIndexUnallocated) { name_ptr += dxbc::AppendAlignedString(shader_object_, "xe_edram"); } + uint32_t zpd_rov_counter_name_ptr = name_ptr; + if (uav_index_zpd_rov_counter_ != kBindingIndexUnallocated) { + name_ptr += + dxbc::AppendAlignedString(shader_object_, "xe_zpd_rov_counter_uav"); + } uint32_t bindings_position_dwords = uint32_t(shader_object_.size()); @@ -2678,6 +2686,13 @@ void DxbcShaderTranslator::WriteResourceDefinition() { uav.dimension = dxbc::RdefDimension::kUAVBuffer; uav.sample_count = UINT32_MAX; uav.bind_point = uint32_t(UAVRegister::kEdram); + } else if (i == uav_index_zpd_rov_counter_) { + // ROV ZPD counter buffer. + uav.name_ptr = zpd_rov_counter_name_ptr; + uav.type = dxbc::RdefInputType::kUAVRWByteAddress; + uav.return_type = dxbc::ResourceReturnType::kMixed; + uav.dimension = dxbc::RdefDimension::kUAVBuffer; + uav.bind_point = uint32_t(UAVRegister::kZpdRovCounter); } else { assert_unhandled_case(i); } @@ -3572,6 +3587,12 @@ void DxbcShaderTranslator::WriteShaderCode() { dxbc::Src::U(dxbc::Src::Dcl, uav_index_edram_, uint32_t(UAVRegister::kEdram), uint32_t(UAVRegister::kEdram))); + } else if (i == uav_index_zpd_rov_counter_) { + // ROV ZPD counter buffer. + ao_.OpDclUnorderedAccessViewRaw( + 0, dxbc::Src::U(dxbc::Src::Dcl, uav_index_zpd_rov_counter_, + uint32_t(UAVRegister::kZpdRovCounter), + uint32_t(UAVRegister::kZpdRovCounter))); } else { assert_unhandled_case(i); } diff --git a/src/xenia/gpu/dxbc_shader_translator.h b/src/xenia/gpu/dxbc_shader_translator.h index 2b5e8599c..f71473b43 100644 --- a/src/xenia/gpu/dxbc_shader_translator.h +++ b/src/xenia/gpu/dxbc_shader_translator.h @@ -329,7 +329,9 @@ class DxbcShaderTranslator : public ShaderTranslator { uint32_t alpha_to_mask; uint32_t edram_32bpp_tile_pitch_dwords_scaled; uint32_t edram_depth_base_dwords_scaled; - uint32_t padding_edram_depth_base_dwords_scaled; + // UINT32_MAX when this draw is outside an active ZPD segment. The shader + // helper should treat that as a skip sentinel. + uint32_t zpd_rov_counter_index; float color_exp_bias[4]; @@ -431,6 +433,7 @@ class DxbcShaderTranslator : public ShaderTranslator { kAlphaToMask, kEdram32bppTilePitchDwordsScaled, kEdramDepthBaseDwordsScaled, + kZpdRovCounterIndex, kColorExpBias, @@ -516,6 +519,7 @@ class DxbcShaderTranslator : public ShaderTranslator { enum class UAVRegister { kSharedMemory, kEdram, + kZpdRovCounter, }; uint64_t GetDefaultVertexShaderModification( @@ -762,6 +766,9 @@ class DxbcShaderTranslator : public ShaderTranslator { // unchanged or known that it's safe not to await kills/alphatest/AtoC), // returns from the shader. void ROV_DepthStencilTest(); + // Adds the surviving coverage MSAA counts from ROV params to the active ZPD + // counter slot after the final PS depth/stencil decision. + void ROV_AddPassedMSAASamplesToZPD(); // Unpacks a 32bpp or a 64bpp color in packed_temp.packed_temp_components to // color_temp, using 2 temporary VGPRs. void ROV_UnpackColor(uint32_t rt_index, uint32_t packed_temp, @@ -1207,6 +1214,7 @@ class DxbcShaderTranslator : public ShaderTranslator { uint32_t uav_count_; uint32_t uav_index_shared_memory_; uint32_t uav_index_edram_; + uint32_t uav_index_zpd_rov_counter_; std::vector sampler_bindings_; }; diff --git a/src/xenia/gpu/dxbc_shader_translator_om.cc b/src/xenia/gpu/dxbc_shader_translator_om.cc index a181008ed..22d18b1f6 100644 --- a/src/xenia/gpu/dxbc_shader_translator_om.cc +++ b/src/xenia/gpu/dxbc_shader_translator_om.cc @@ -2120,6 +2120,59 @@ void DxbcShaderTranslator::CompletePixelShader_AlphaToMask() { a_.OpEndIf(); } +void DxbcShaderTranslator::ROV_AddPassedMSAASamplesToZPD() { + if (uav_index_zpd_rov_counter_ == kBindingIndexUnallocated) { + uav_index_zpd_rov_counter_ = uav_count_++; + } + + uint32_t temp = PushSystemTemp(); + dxbc::Dest temp_x_dest(dxbc::Dest::R(temp, 0b0001)); + dxbc::Src temp_x_src(dxbc::Src::R(temp, dxbc::Src::kXXXX)); + dxbc::Dest temp_y_dest(dxbc::Dest::R(temp, 0b0010)); + dxbc::Src temp_y_src(dxbc::Src::R(temp, dxbc::Src::kYYYY)); + + dxbc::Src counter_index_src(LoadSystemConstant( + SystemConstants::Index::kZpdRovCounterIndex, + offsetof(SystemConstants, zpd_rov_counter_index), dxbc::Src::kXXXX)); + + // UINT32_MAX means no ZPD segment is currently open for this draw. + a_.OpINE(temp_x_dest, counter_index_src, dxbc::Src::LU(UINT32_MAX)); + a_.OpIf(true, temp_x_src); + + { + dxbc::Src covered_samples_src( + dxbc::Src::R(system_temp_rov_params_, dxbc::Src::kXXXX)); + a_.OpUBFE(temp_x_dest, dxbc::Src::LU(1), dxbc::Src::LU(0), + covered_samples_src); + a_.OpUBFE(temp_y_dest, dxbc::Src::LU(1), dxbc::Src::LU(1), + covered_samples_src); + a_.OpIAdd(temp_x_dest, temp_x_src, temp_y_src); + a_.OpUBFE(temp_y_dest, dxbc::Src::LU(1), dxbc::Src::LU(2), + covered_samples_src); + a_.OpIAdd(temp_x_dest, temp_x_src, temp_y_src); + a_.OpUBFE(temp_y_dest, dxbc::Src::LU(1), dxbc::Src::LU(3), + covered_samples_src); + a_.OpIAdd(temp_x_dest, temp_x_src, temp_y_src); + a_.OpIf(true, temp_x_src); + { + // The counter UAV is raw, so address it in bytes. + // One counter slot is one uint32_t. + a_.OpUMul(dxbc::Dest::Null(), temp_y_dest, counter_index_src, + dxbc::Src::LU(sizeof(uint32_t))); + // Add the number of samples that survived depth/stencil for this pixel to + // the active query slot. This slot is copied to the readback buffer when + // the ZPD segment is closed. + a_.OpAtomicIAdd(dxbc::Dest::U(uav_index_zpd_rov_counter_, + uint32_t(UAVRegister::kZpdRovCounter), 0), + temp_y_src, 0b0001, temp_x_src); + } + a_.OpEndIf(); + } + a_.OpEndIf(); + + PopSystemTemp(); +} + void DxbcShaderTranslator::CompletePixelShader_WriteToROV() { uint32_t temp = PushSystemTemp(); dxbc::Dest temp_x_dest(dxbc::Dest::R(temp, 0b0001)); @@ -2176,6 +2229,8 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() { // system_temp_rov_params_.y (the depth / stencil sample address) is not // needed anymore, can be used for color writing. + ROV_AddPassedMSAASamplesToZPD(); + if (!is_depth_only_pixel_shader_) { // Check if any sample is still covered after depth testing and writing, // skip color writing completely in this case.