diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index 17bf1c398f..de624db4e3 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -101,7 +101,7 @@ static u64 s_total_barriers = 0; static u64 s_total_copies = 0; static u64 s_total_uploads = 0; static u64 s_total_readbacks = 0; -static u64 s_total_depth_copies_rov = 0; +static u64 s_total_copies_rov = 0; static u64 s_total_draws_rov = 0; static u64 s_total_barriers_rov = 0; static u32 s_total_frames = 0; @@ -298,7 +298,7 @@ void Host::BeginPresentFrame() update_stat(GSPerfMon::TextureCopies, s_total_copies, s_last_copies); update_stat(GSPerfMon::TextureUploads, s_total_uploads, s_last_uploads); update_stat(GSPerfMon::Readbacks, s_total_readbacks, s_last_readbacks); - update_stat(GSPerfMon::DepthCopiesROV, s_total_depth_copies_rov, s_last_depth_copies_rov); + update_stat(GSPerfMon::TextureCopiesROV, s_total_copies_rov, s_last_depth_copies_rov); update_stat(GSPerfMon::DrawCallsROV, s_total_draws_rov, s_last_draws_rov); update_stat(GSPerfMon::BarriersROV, s_total_barriers_rov, s_last_barriers_rov); @@ -917,7 +917,7 @@ void GSRunner::DumpStats() Console.WriteLn(fmt::format("@HWSTAT@ Copies: {} (avg {})", s_total_copies, static_cast(std::ceil(s_total_copies / static_cast(s_total_drawn_frames))))); Console.WriteLn(fmt::format("@HWSTAT@ Uploads: {} (avg {})", s_total_uploads, static_cast(std::ceil(s_total_uploads / static_cast(s_total_drawn_frames))))); Console.WriteLn(fmt::format("@HWSTAT@ Readbacks: {} (avg {})", s_total_readbacks, static_cast(std::ceil(s_total_readbacks / static_cast(s_total_drawn_frames))))); - Console.WriteLn(fmt::format("@HWSTAT@ Depth Copies (ROV): {} (avg {})", s_total_depth_copies_rov, static_cast(std::ceil(s_total_depth_copies_rov / static_cast(s_total_drawn_frames))))); + Console.WriteLn(fmt::format("@HWSTAT@ Copies (ROV): {} (avg {})", s_total_copies_rov, static_cast(std::ceil(s_total_copies_rov / static_cast(s_total_drawn_frames))))); Console.WriteLn(fmt::format("@HWSTAT@ Draws Calls (ROV): {} (avg {})", s_total_draws_rov, static_cast(std::ceil(s_total_draws_rov / static_cast(s_total_drawn_frames))))); Console.WriteLn(fmt::format("@HWSTAT@ Barriers (ROV): {} (avg {})", s_total_barriers_rov, static_cast(std::ceil(s_total_barriers_rov / static_cast(s_total_drawn_frames))))); if (s_perf_enable) diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index 241d29f703..6a541794d9 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -729,7 +729,7 @@ void GSgetStats(SmallStringBase& info) (int)std::ceil(pm.Get(GSPerfMon::RenderPasses)), (int)std::ceil(pm.Get(GSPerfMon::Readbacks)), (int)std::ceil(pm.Get(GSPerfMon::TextureCopies)), - (int)std::ceil(pm.Get(GSPerfMon::DepthCopiesROV)), + (int)std::ceil(pm.Get(GSPerfMon::TextureCopiesROV)), (int)std::ceil(pm.Get(GSPerfMon::TextureUploads))); } } diff --git a/pcsx2/GS/GSPerfMon.h b/pcsx2/GS/GSPerfMon.h index 928eb3ec68..d4defa8125 100644 --- a/pcsx2/GS/GSPerfMon.h +++ b/pcsx2/GS/GSPerfMon.h @@ -23,7 +23,7 @@ public: SyncPoint, Barriers, RenderPasses, - DepthCopiesROV, // Overlaps with regular texture copies. + TextureCopiesROV, // Overlaps with regular texture copies. DrawCallsROV, // Overlaps with regular draw calls. BarriersROV, // Overlaps with regular barriers. CounterLast, diff --git a/pcsx2/GS/Renderers/Common/GSDevice.cpp b/pcsx2/GS/Renderers/Common/GSDevice.cpp index 6b75fb9170..c8fc427e27 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.cpp +++ b/pcsx2/GS/Renderers/Common/GSDevice.cpp @@ -696,8 +696,6 @@ void GSDevice::Recycle(GSTexture* t) return; t->SetLastFrameUsed(m_frame); - - t->ClearUnorderedAccess(); #ifdef PCSX2_DEVBUILD t->SetDebugName(""); diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index a535287d06..4970cc67a7 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -1492,7 +1492,7 @@ protected: /// Resolves CAS shader includes for the specified source. static bool GetCASShaderSource(std::string* source); - /// Applies CAS and writes to the destination texture, which should be a RWTexture. + /// Applies CAS and writes to the destination texture, which should be a shader writeable texture. virtual bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array& constants) = 0; /// Perform texture operations for ImGui diff --git a/pcsx2/GS/Renderers/Common/GSTexture.h b/pcsx2/GS/Renderers/Common/GSTexture.h index 685dcbdddd..14c6efabd7 100644 --- a/pcsx2/GS/Renderers/Common/GSTexture.h +++ b/pcsx2/GS/Renderers/Common/GSTexture.h @@ -40,7 +40,7 @@ public: static constexpr Usage Texture = Usage::Texture; static constexpr Usage RenderTarget = Usage::RenderTarget; static constexpr Usage DepthStencil = Usage::DepthStencil; - static constexpr Usage FeedbackTarget = Usage::RenderTarget | Usage::Feedback | Usage::ShaderWrite; + static constexpr Usage FeedbackTarget = Usage::RenderTarget | Usage::Feedback; static constexpr Usage FeedbackDepth = Usage::DepthStencil | Usage::Feedback; static constexpr Usage ShaderWriteTarget = Usage::RenderTarget | Usage::Feedback | Usage::ShaderWrite; static constexpr Usage ShaderWriteTexture = Usage::Texture | Usage::ShaderWrite; @@ -94,10 +94,6 @@ protected: bool m_needs_mipmaps_generated = true; ClearValue m_clear_value = {}; - // For GL/DX11 since they don't track layouts. - // Used heuristically to decide whether to use ROV for a draw. - bool m_unordered_access = false; - #ifdef PCSX2_DEVBUILD std::string m_debug_name; #endif @@ -223,6 +219,12 @@ public: return IsFeedbackOrShaderWrite(m_usage); } + virtual bool IsShaderWriteMode() const + { + // Backends that track state explicitly can specialize this to use the actual state. + return IsShaderWrite(); + } + __fi State GetState() const { return m_state; } __fi void SetState(State state) { m_state = state; } @@ -254,12 +256,6 @@ public: // Typical size of a RGBA texture u32 GetMemUsage() const { return m_size.x * m_size.y * (m_format == Format::UNorm8 ? 1 : 4); } - // The unordered access flag is sticky, so once it's set we keep using ROV for the target - // until it's recycled/destroyed. Only used for DX11/GL, which don't track actual resource layout/state. - virtual bool IsUnorderedAccess() const { return m_unordered_access; } - void SetUnorderedAccess() { m_unordered_access = true; } - void ClearUnorderedAccess() { m_unordered_access = false; } - // Helper routines for formats/types static bool IsCompressedFormat(Format format) { return (format >= Format::BC1 && format <= Format::BC7); } }; diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index 76afa12ed2..1848b8efd0 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -48,6 +48,15 @@ static bool IsTextureFormatHWBlendable(ID3D11Device* dev, DXGI_FORMAT format) return (support & D3D11_FORMAT_SUPPORT_RENDER_TARGET) && (support & D3D11_FORMAT_SUPPORT_BLENDABLE); } +static bool IsTextureFormatUAVCapable(ID3D11Device* dev, DXGI_FORMAT format) +{ + UINT support; + if (FAILED(dev->CheckFormatSupport(format, &support))) + return false; + + return (support & D3D11_FORMAT_SUPPORT_TYPED_UNORDERED_ACCESS_VIEW) != 0; +} + GSDevice11::GSDevice11() { memset(&m_state, 0, sizeof(m_state)); @@ -594,7 +603,8 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) } // 1x1 dummy texture. - m_null_texture = CreateSurface(GSTexture::ShaderWriteTarget, 1, 1, 1, GSTexture::Format::Color); + const GSTexture::Usage null_usage = m_features.rov ? GSTexture::ShaderWriteTarget : GSTexture::Feedback; + m_null_texture = CreateSurface(null_usage, 1, 1, 1, GSTexture::Format::Color); if (!m_null_texture) return false; @@ -717,6 +727,11 @@ void GSDevice11::SetFeatures(IDXGIAdapter1* adapter) D3D11_FEATURE_DATA_D3D11_OPTIONS2 options2{}; m_dev->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS2, &options2, sizeof(options2)); m_features.rov = m_uav_texture && options2.ROVsSupported; + for (u32 fmt = static_cast(GSTexture::Format::Color); fmt <= static_cast(GSTexture::Format::PrimID); fmt++) + { + if (GSTexture::IsShaderWriteFormat(static_cast(fmt))) + m_features.rov &= IsTextureFormatUAVCapable(m_dev.get(), GSTexture11::GetDXGIFormat(static_cast(fmt))); + } // Let the user know if said features are available. Console.WriteLnFmt("D3D11: DXTn Texture Compression: {}", m_features.dxt_textures ? "Supported" : "Not Supported"); diff --git a/pcsx2/GS/Renderers/DX11/GSTexture11.cpp b/pcsx2/GS/Renderers/DX11/GSTexture11.cpp index 9b522221ce..0e736cf1cb 100644 --- a/pcsx2/GS/Renderers/DX11/GSTexture11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSTexture11.cpp @@ -102,6 +102,8 @@ void GSTexture11::SetDebugName(std::string_view name) GSDevice11::SetD3DDebugObjectName(m_srv.get(), fmt::format("{} SRV", name)); if (m_rtv) GSDevice11::SetD3DDebugObjectName(m_rtv.get(), fmt::format("{} RTV", name)); + if (m_uav) + GSDevice11::SetD3DDebugObjectName(m_uav.get(), fmt::format("{} UAV", name)); m_debug_name = name; } diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index 5b4f214a8b..78233263d8 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -134,6 +134,15 @@ bool GSDevice12::SupportsTextureFormat(DXGI_FORMAT format) (support.Support1 & required) == required; } +bool GSDevice12::IsTextureFormatUAVCapable(DXGI_FORMAT format) +{ + constexpr u32 required = D3D12_FORMAT_SUPPORT1_TYPED_UNORDERED_ACCESS_VIEW; + + D3D12_FEATURE_DATA_FORMAT_SUPPORT support = { format }; + return SUCCEEDED(m_device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &support, sizeof(support))) && + (support.Support1 & required) == required; +} + bool GSDevice12::SupportsProgrammableSamplePositions() { D3D12_FEATURE_DATA_D3D12_OPTIONS2 options = {}; @@ -1441,6 +1450,15 @@ bool GSDevice12::CheckFeatures(const u32& vendor_id) D3D12_FEATURE_DATA_D3D12_OPTIONS options{}; m_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS)); m_features.rov = options.ROVsSupported; + for (u32 fmt = static_cast(GSTexture::Format::Color); fmt <= static_cast(GSTexture::Format::PrimID); fmt++) + { + if (GSTexture::IsShaderWriteFormat(static_cast(fmt))) + { + DXGI_FORMAT dxgi_fmt; + LookupNativeFormat(static_cast(fmt), &dxgi_fmt, nullptr, nullptr, nullptr, nullptr); + m_features.rov &= IsTextureFormatUAVCapable(dxgi_fmt); + } + } Console.WriteLnFmt("D3D12: Rasterizer Ordered Views: {}", m_features.rov ? "Supported" : "Not Supported"); Console.WriteLnFmt("D3D12: Tight Alignment: {}", m_allocator->IsTightAlignmentSupported() ? "Supported" : "Not Supported"); @@ -1635,7 +1653,7 @@ void GSDevice12::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, sTex12->TransitionToState(GSTexture12::ResourceState::CopySrc); sTex12->SetUseFenceCounter(GetCurrentFenceValue()); if (m_tfx_textures[0] && sTex12->GetSRVDescriptor() == m_tfx_textures[0]) - PSSetShaderResource(0, nullptr, false); + PSSetShaderResource(TEXTURE_TEXTURE, nullptr, false); dTex12->TransitionToState(GSTexture12::ResourceState::CopyDst); dTex12->SetUseFenceCounter(GetCurrentFenceValue()); @@ -2605,9 +2623,11 @@ GSDevice12::ComPtr GSDevice12::GetUtilityPixelShader(const std::string bool GSDevice12::CreateNullTexture() { + const GSTexture::Usage null_access = m_features.rov ? GSTexture::ShaderWriteTarget : GSTexture::FeedbackTarget; + const DXGI_FORMAT null_uav_format = m_features.rov ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_UNKNOWN; m_null_texture = - GSTexture12::Create(GSTexture::ShaderWriteTarget, GSTexture::Format::Color, 1, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM, - DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8G8B8A8_UNORM); + GSTexture12::Create(null_access, GSTexture::Format::Color, 1, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM, + DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, null_uav_format); if (!m_null_texture) return false; @@ -3528,7 +3548,7 @@ void GSDevice12::PSSetSampler(GSHWDrawConfig::SamplerSelector sel) m_dirty_flags |= DIRTY_FLAG_TFX_SAMPLERS; } -void GSDevice12::PSSetUnorderedAccess(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds) +void GSDevice12::PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds) { GSTexture12* d12Rt = static_cast(rt); GSTexture12* d12Ds = static_cast(ds); @@ -4222,7 +4242,7 @@ GSTexture12* GSDevice12::SetupPrimitiveTrackingDATE(GSHWDrawConfig& config, Pipe // and bind the image to the primitive sampler image->TransitionToState(GSTexture12::ResourceState::PixelShaderResource); - PSSetShaderResource(3, image, false); + PSSetShaderResource(TEXTURE_PRIMID, image, false); return image; } @@ -4340,11 +4360,11 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) // bind textures before checking the render pass, in case we need to transition them if (config.tex) { - PSSetShaderResource(0, config.tex, config.tex != config.rt && config.tex != config.ds); + PSSetShaderResource(TEXTURE_TEXTURE, config.tex, config.tex != config.rt && config.tex != config.ds); PSSetSampler(config.sampler); } if (config.pal) - PSSetShaderResource(1, config.pal, true); + PSSetShaderResource(TEXTURE_PALETTE, config.pal, true); if (config.blend.constant_enable) SetBlendConstants(config.blend.constant); @@ -4400,7 +4420,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) // we're not drawing to the RT, so we can use it as a source if (config.require_one_barrier && !m_features.texture_barrier) - PSSetShaderResource(2, draw_rt, true); + PSSetShaderResource(TEXTURE_RT, draw_rt, true); } draw_rt = colclip_rt; @@ -4409,7 +4429,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) // Clear texture binding when it's bound to RT or DS. if (!config.tex && ((draw_rt && static_cast(draw_rt)->GetSRVDescriptor() == m_tfx_textures[0]) || (draw_ds && static_cast(draw_ds)->GetSRVDescriptor() == m_tfx_textures[0]))) - PSSetShaderResource(0, nullptr, false); + PSSetShaderResource(TEXTURE_TEXTURE, nullptr, false); if (InRenderPass() && (m_current_render_target == draw_rt || m_current_depth_target == draw_ds)) { @@ -4471,15 +4491,15 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) } if (config.require_one_barrier) - PSSetShaderResource(2, draw_rt_clone, true); + PSSetShaderResource(TEXTURE_RT, draw_rt_clone, true); if (config.tex_hazard == GSHWDrawConfig::TEX_HAZARD_RT) - PSSetShaderResource(0, draw_rt_clone, true); + PSSetShaderResource(TEXTURE_TEXTURE, draw_rt_clone, true); } else Console.Warning("D3D12: Failed to allocate temp texture for RT copy."); } - PSSetUnorderedAccess(draw_rt_rov, draw_ds_rov, config.ps.HasColorOutput(), config.ps.HasDepthROVWrite()); + PSSetROVs(draw_rt_rov, draw_ds_rov, config.ps.HasColorOutput(), config.ps.HasDepthROVWrite()); // For depth testing and sampling, use a read only dsv, otherwise use a write dsv OMSetRenderTargets(draw_rt, draw_ds_as_rt, draw_ds, config.scissor, @@ -4645,11 +4665,11 @@ void GSDevice12::SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig& Console.Warning("D3D12: Possible unnecessary barrier detected."); #endif if ((one_barrier || full_barrier) && feedback_rt) - PSSetShaderResource(2, draw_rt, false, ResourceType::FBL); + PSSetShaderResource(TEXTURE_RT, draw_rt, false, ResourceType::FBL); if (config.tex_hazard == GSHWDrawConfig::TEX_HAZARD_RT) - PSSetShaderResource(0, draw_rt, false, ResourceType::FBL); + PSSetShaderResource(TEXTURE_TEXTURE, draw_rt, false, ResourceType::FBL); if ((one_barrier || full_barrier) && feedback_depth) - PSSetShaderResource(4, draw_ds, false, ResourceType::FBL); + PSSetShaderResource(TEXTURE_DEPTH, draw_ds, false, ResourceType::FBL); if (full_barrier) { diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.h b/pcsx2/GS/Renderers/DX12/GSDevice12.h index 961184ab46..938cfcb2bc 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.h +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.h @@ -152,6 +152,9 @@ public: /// Test for support for the specified texture format. bool SupportsTextureFormat(DXGI_FORMAT format); + /// Test for UAV support for the specified texture format. + bool IsTextureFormatUAVCapable(DXGI_FORMAT format); + // Partial depth copies require ProgrammableSamplePositions tier 1. bool SupportsProgrammableSamplePositions(); @@ -552,7 +555,7 @@ public: void PSSetShaderResource(int i, GSTexture* sr, bool check_state, ResourceType type = ResourceType::SRV); void PSSetSampler(GSHWDrawConfig::SamplerSelector sel); - void PSSetUnorderedAccess(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds); + void PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds); void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, GSTexture* ds_as_rt, const GSVector4i& scissor, bool depth_read = false, const GSVector2i& viewport_size = {}); diff --git a/pcsx2/GS/Renderers/DX12/GSTexture12.h b/pcsx2/GS/Renderers/DX12/GSTexture12.h index 0ce998bd7d..ecfe618876 100644 --- a/pcsx2/GS/Renderers/DX12/GSTexture12.h +++ b/pcsx2/GS/Renderers/DX12/GSTexture12.h @@ -56,7 +56,7 @@ public: __fi ID3D12Resource* GetResource() const { return m_resource.get(); } __fi ID3D12Resource* GetFBLResource() const { return m_resource_fbl.get(); } - virtual bool IsUnorderedAccess() const override { return GetResourceState() == ResourceState::PixelShaderUAV; } + bool IsShaderWriteMode() const override { return GetResourceState() == ResourceState::PixelShaderUAV; } void* GetNativeHandle() const override; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index bd62247028..db71724ccc 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -6241,7 +6241,7 @@ void GSRendererHW::EmulateDither() if (m_conf.ps.dither || m_conf.blend_multi_pass.dither) { const GIFRegDIMX& DIMX = m_draw_env->DIMX; - GL_DBG("DITHERING mode %d (%d)", (GSConfig.Dithering == 3) ? "Force 32bit" : ((GSConfig.Dithering == 0) ? "Disabled" : "Enabled"), GSConfig.Dithering); + GL_DBG("DITHERING mode %s (%d)", (GSConfig.Dithering == 3) ? "Force 32bit" : ((GSConfig.Dithering == 0) ? "Disabled" : "Enabled"), GSConfig.Dithering); if (m_conf.ps.dither || GSConfig.Dithering == 3) m_conf.ps.dither = GSConfig.Dithering; @@ -7601,7 +7601,7 @@ void GSRendererHW::DetermineROVUsage(GSTextureCache::Target* rt, GSTextureCache: const bool multipass_depth = (full_barrier && m_conf.ps.IsFeedbackLoopDepth()) || two_pass_alpha; // If already ROV, just continue the usage. - const bool color_is_rov = rt && rt->m_texture->IsUnorderedAccess(); + const bool color_is_rov = rt && rt->m_texture->IsShaderWriteMode(); const bool depth_is_rov = ds && ds->m_texture->IsDepthColor(); bool use_rov_color = (color_write && multipass_color) || color_is_rov; @@ -7688,7 +7688,7 @@ void GSRendererHW::ConfigureROV(bool color_rov, bool depth_rov) { m_conf.cb_ps.FbMask |= fbmask; } - GL_INS("ROV: FbMask={ R=%x, G=%x, B=%x, A=%x }", + GL_INS("ROV: FbMask={R=%x, G=%x, B=%x, A=%x}", m_conf.cb_ps.FbMask.r, m_conf.cb_ps.FbMask.g, m_conf.cb_ps.FbMask.b, m_conf.cb_ps.FbMask.a); } else @@ -7807,89 +7807,92 @@ void GSRendererHW::ConfigureROV(bool color_rov, bool depth_rov) } } -void GSRendererHW::SetUnorderedAccessFlag(GSTextureCache::Target* rt) +void GSRendererHW::ConvertTextureTypeROVSingle(GSTextureCache::Target* tgt, bool shader_write) { - // Set flag for ROV activation heuristic. Only used by DX11. - // Only needed for RT, as we use a different method for depth. - if (rt) - { - if (m_conf.ps.HasColorROV()) - rt->m_texture->SetUnorderedAccess(); - else - rt->m_texture->ClearUnorderedAccess(); - } -} + const bool depth = (tgt->m_type == GSTextureCache::DepthStencil); -static void CopyDepthTextureROV(GSTexture* src, GSTexture* dst) -{ - switch (src->GetState()) + GSTexture* old_tex = depth ? m_conf.ds : m_conf.rt; + + GSTexture::Usage usage = shader_write ? GSTexture::ShaderWriteTarget : GSTexture::FeedbackTarget; + GSTexture* new_tex = depth ? + (shader_write ? + g_gs_device->FetchSurface(usage, old_tex->GetSize(), 1, GSTexture::Format::DepthColor, false, false) : + g_gs_device->CreateDepthStencil(old_tex->GetSize(), false, true)) : + g_gs_device->FetchSurface(usage, old_tex->GetSize(), 1, GSTexture::Format::Color, false, false); + + switch (old_tex->GetState()) { case GSTexture::State::Cleared: - g_gs_device->ClearDepth(dst, src->GetClearDepth()); + if (depth) + g_gs_device->ClearDepth(new_tex, old_tex->GetClearDepth()); + else + g_gs_device->ClearRenderTarget(new_tex, old_tex->GetClearColor()); break; case GSTexture::State::Invalidated: - g_gs_device->InvalidateRenderTarget(dst); + g_gs_device->InvalidateRenderTarget(new_tex); break; case GSTexture::State::Dirty: - g_gs_device->StretchRectAuto(src, dst, Nearest); + g_gs_device->StretchRectAuto(old_tex, new_tex, Nearest); + + // Count stats as part of both standard and ROV. + g_perfmon.Put(GSPerfMon::TextureCopiesROV, 1.0); + g_perfmon.Put(GSPerfMon::DrawCallsROV, 1.0); + break; + default: + pxAssert(false); break; } - // These stats are counted both as part of ROV and non-ROV stats. - g_perfmon.Put(GSPerfMon::DepthCopiesROV, 1.0); - g_perfmon.Put(GSPerfMon::DrawCallsROV, 1.0); -}; - -void GSRendererHW::ConvertDepthFormatROV(GSTextureCache::Target* ds) -{ - if (!ds) - return; - - GSTexture* ds_tex_old = m_conf.ds; - GSTexture* ds_tex_new = nullptr; - - // Convert depth to depth color or vice versa if needed. - bool depth_to_color; - if (m_conf.ps.HasDepthROV() && !ds_tex_old->IsDepthColor()) - { - depth_to_color = true; - ds_tex_new = g_gs_device->CreateShaderWriteTarget(ds_tex_old->GetSize(), GSTexture::Format::DepthColor, false, true); - } - else if (!m_conf.ps.HasDepthROV() && ds_tex_old->IsDepthColor()) - { - depth_to_color = false; - ds_tex_new = g_gs_device->CreateDepthStencil(ds->m_texture->GetSize(), false, true); - } - else - { - return; - } - - GL_PUSH("HW: Convert %s for ROV.", depth_to_color ? "DepthStencil -> DepthColor" : "DepthColor -> DepthStencil"); - - CopyDepthTextureROV(ds_tex_old, ds_tex_new); - #if PCSX2_DEVBUILD - ds_tex_new->SetDebugName(ds->m_texture->GetDebugName()); + new_tex->SetDebugName(tgt->m_texture->GetDebugName()); #endif - // Fix up the texture cache. - if (ds->m_texture == ds_tex_old) + if (tgt->m_texture == old_tex) { - GL_CACHE("HW: Replaced texture for DS @ 0x%04x", ds->m_TEX0.TBP0); - ds->m_texture = ds_tex_new; + GL_CACHE("HW: Replaced texture for %s @ 0x%04x", depth ? "DS" : "RT", tgt->m_TEX0.TBP0); + tgt->m_texture = new_tex; } else { // Must be the temporary Z. - pxAssert(g_texture_cache->GetTemporaryZ() == ds_tex_old); + pxAssert(depth && g_texture_cache->GetTemporaryZ() == old_tex); GL_CACHE("HW: Replaced texture for temporary Z @ 0x%04x", g_texture_cache->GetTemporaryZInfo().ZBP); - g_texture_cache->SetTemporaryZ(ds_tex_new); + g_texture_cache->SetTemporaryZ(new_tex); } - g_gs_device->Recycle(ds_tex_old); + // Fixup the backend config. + if (depth) + m_conf.ds = new_tex; + else + m_conf.rt = new_tex; - m_conf.ds = ds_tex_new; + g_gs_device->Recycle(old_tex); +} + +void GSRendererHW::ConvertTextureTypeROV(GSTextureCache::Target* rt, GSTextureCache::Target* ds) +{ + // Convert depth to the proper type/format. + if (ds) + { + if (m_conf.ps.HasDepthROV() && !ds->m_texture->IsShaderWrite()) + { + GL_PUSH("HW: Convert DepthStencil -> DepthColor for ROV."); + ConvertTextureTypeROVSingle(ds, true); + } + else if (!m_conf.ps.HasDepthROV() && !ds->m_texture->IsDepthStencil()) + { + GL_PUSH("HW: Convert DepthColor -> DepthStencil for non-ROV."); + ConvertTextureTypeROVSingle(ds, false); + } + } + + // Convert color to the proper type. This only adds the shader read/write flag and doesn't remove it, + // since adding the read/write flag doesn't lose any pipeline capabilities. + if (rt && m_conf.ps.HasColorROV() && !rt->m_texture->IsShaderWrite()) + { + GL_PUSH("HW: Convert RenderTarget -> RenderTarget (shader write) for ROV."); + ConvertTextureTypeROVSingle(rt, true); + } } __ri static constexpr bool IsRedundantClamp(u8 clamp, u32 clamp_min, u32 clamp_max, u32 tsize) @@ -9455,8 +9458,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta // Call before computing the full drawlist in case ROV is used and we don't need it. DetermineROVUsage(rt, ds); - ConvertDepthFormatROV(ds); - SetUnorderedAccessFlag(rt); + ConvertTextureTypeROV(rt, ds); // Barriers must be determined before indices are modified via HandleProvokingVertexFirst/SetupIA. // This also computes the drawlist if needed. diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index 1a9d0362d5..bce5419503 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -256,8 +256,8 @@ private: void GetForcedROVUsage(bool& color_cov, bool& depth_rov); // Whether having color or depth with the current config forces the other. void DetermineROVUsage(GSTextureCache::Target* rt, GSTextureCache::Target* ds); // Heuristics to determine whether to enable/disable ROV void ConfigureROV(bool color_rov, bool depth_rov); // Actual config for ROV - void SetUnorderedAccessFlag(GSTextureCache::Target* rt); // Set flag for DX11 ROV heuristic to work. - void ConvertDepthFormatROV(GSTextureCache::Target* ds); // Convert between depth and depth color if needed. + void ConvertTextureTypeROV(GSTextureCache::Target* rt, GSTextureCache::Target* ds); // Convert to RW capable textures if needed. + void ConvertTextureTypeROVSingle(GSTextureCache::Target* tgt, bool shader_write); // Helper to do the above. void SetTCOffset(); bool NextDrawColClip() const; diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index 1d0e492d44..fc0e32c154 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -2007,7 +2007,7 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr MTLRenderPipelineColorAttachmentDescriptor* color1 = [[pdesc colorAttachments] objectAtIndexedSubscript:1]; [color1 setPixelFormat:MTLPixelFormatR32Float]; } - NSString* pname = [NSString stringWithFormat:@"HW Render %x.%x.%llx.%llx", vssel_mtl.key, pssel.key_hi, pssel.key_lo, extras.fullkey]; + NSString* pname = [NSString stringWithFormat:@"HW Render %x.%llx.%llx.%x", vssel_mtl.key, pssel.key_hi, pssel.key_lo, extras.fullkey]; auto pipeline = MakePipeline(pdesc, vs, ps, pname); [m_current_render.encoder setRenderPipelineState:pipeline]; diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index 1873ea6394..127108b7fd 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -908,11 +908,6 @@ bool GSDeviceOGL::CheckFeatures() } m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand && m_features.feedback_loops(); - - if (GSConfig.HWROV) - { - Console.Warning("GL: ROV is not implemented for GL and will be disabled."); - } return true; } diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index a08d78526f..52941002b3 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -2735,14 +2735,18 @@ bool GSDeviceVK::CheckFeatures() m_features.point_expand ? "hardware" : "vertex expanding", m_features.line_expand ? "hardware" : "vertex expanding"); + bool has_rov_storage_flags = true; + // Check texture format support before we try to create them. for (u32 fmt = static_cast(GSTexture::Format::Color); fmt < static_cast(GSTexture::Format::PrimID); fmt++) { const VkFormat vkfmt = LookupNativeFormat(static_cast(fmt)); - const VkFormatFeatureFlags bits = - (static_cast(fmt) == GSTexture::Format::DepthStencil) ? - (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) : - (VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT); + VkFormatFeatureFlags bits = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; + + if (static_cast(fmt) == GSTexture::Format::DepthStencil) + bits |= VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; + else + bits |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT; VkFormatProperties props = {}; vkGetPhysicalDeviceFormatProperties(m_physical_device, vkfmt, &props); @@ -2753,6 +2757,9 @@ bool GSDeviceVK::CheckFeatures() fmt, static_cast(vkfmt), props.optimalTilingFeatures, bits); return false; } + + if (GSTexture::IsShaderWriteFormat(static_cast(fmt))) + has_rov_storage_flags &= ((props.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0); } m_features.dxt_textures = m_device_features.textureCompressionBC; @@ -2769,7 +2776,9 @@ bool GSDeviceVK::CheckFeatures() m_max_texture_size = m_device_properties.limits.maxImageDimension2D; m_features.rov = m_optional_extensions.vk_ext_fragment_shader_interlock && - m_device_features.fragmentStoresAndAtomics; + m_device_features.fragmentStoresAndAtomics && + has_rov_storage_flags && + !m_features.framebuffer_fetch; return true; } @@ -3822,7 +3831,8 @@ VkShaderModule GSDeviceVK::GetUtilityFragmentShader(const std::string& source, c bool GSDeviceVK::CreateNullTexture() { - m_null_texture = GSTextureVK::Create(GSTexture::ShaderWriteTarget, GSTexture::Format::Color, 1, 1, 1); + GSTexture::Usage null_usage = m_features.rov ? GSTexture::ShaderWriteTarget : GSTexture::FeedbackTarget; + m_null_texture = GSTextureVK::Create(null_usage, GSTexture::Format::Color, 1, 1, 1); if (!m_null_texture) return false; @@ -5266,7 +5276,7 @@ void GSDeviceVK::SetLineWidth(float width) m_dirty_flags |= DIRTY_FLAG_LINE_WIDTH; } -void GSDeviceVK::PSSetUnorderedAccess(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds) +void GSDeviceVK::PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds) { GSTextureVK* vkRt = static_cast(rt); GSTextureVK* vkDs = static_cast(ds); @@ -6183,7 +6193,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) PSSetShaderResource(TFX_TEXTURE_DEPTH, nullptr, false); } - PSSetUnorderedAccess(draw_rt_rov, draw_ds_rov, config.ps.HasColorOutput(), config.ps.HasDepthROVWrite()); + PSSetROVs(draw_rt_rov, draw_ds_rov, config.ps.HasColorOutput(), config.ps.HasDepthROVWrite()); OMSetRenderTargets(draw_rt, draw_ds, config.scissor, static_cast(pipe.feedback_loop_flags), rtsize); diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h index ac6bb0811d..67636f373d 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h @@ -607,7 +607,7 @@ public: void IASetIndexBuffer(const void* index, size_t count); void VSSetIndexBuffer(const void* index, size_t count); - void PSSetUnorderedAccess(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds); + void PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds); void PSSetShaderResource(int i, GSTexture* sr, bool check_state, ResourceType type = ResourceType::SRV); void PSSetSampler(GSHWDrawConfig::SamplerSelector sel); diff --git a/pcsx2/GS/Renderers/Vulkan/GSTextureVK.h b/pcsx2/GS/Renderers/Vulkan/GSTextureVK.h index 7c26ace2bb..74324ebad8 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSTextureVK.h +++ b/pcsx2/GS/Renderers/Vulkan/GSTextureVK.h @@ -42,7 +42,7 @@ public: __fi VkImage GetImage() const { return m_image; } __fi VkImageView GetView() const { return m_view; } __fi Layout GetLayout() const { return m_layout; } - virtual bool IsUnorderedAccess() const override { return GetLayout() == Layout::ReadWriteImage; } + bool IsShaderWriteMode() const override { return GetLayout() == Layout::ReadWriteImage; } __fi VkFormat GetVkFormat() const { return m_vk_format; }