From bb0d63d9aec1685d3e6b41ec469a0f56114d13f8 Mon Sep 17 00:00:00 2001 From: lightningterror <18107717+lightningterror@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:11:51 +0100 Subject: [PATCH] GS/DX11: Implement shader copies for feedback loops. --- bin/resources/shaders/dx11/convert.fx | 21 ++++ pcsx2/GS/Renderers/DX11/GSDevice11.cpp | 131 ++++++++++++++++++++----- pcsx2/GS/Renderers/DX11/GSDevice11.h | 7 +- pcsx2/ShaderCacheVersion.h | 2 +- 4 files changed, 133 insertions(+), 28 deletions(-) diff --git a/bin/resources/shaders/dx11/convert.fx b/bin/resources/shaders/dx11/convert.fx index 119b18e680..6aa8ddb334 100644 --- a/bin/resources/shaders/dx11/convert.fx +++ b/bin/resources/shaders/dx11/convert.fx @@ -61,6 +61,22 @@ VS_OUTPUT vs_main(VS_INPUT input) return output; } +VS_OUTPUT vs_blit_1to1(uint vID : SV_VertexID) +{ + VS_OUTPUT output; + + // Hard-coded positions for the Oversized Triangle. + static const float2 positions[3] = { + float2(-1.0f, 1.0f), + float2( 3.0f, 1.0f), + float2(-1.0f, -3.0f) + }; + + output.p = float4(positions[vID], 0.0f, 1.0f); + + return output; +} + PS_OUTPUT ps_copy(PS_INPUT input) { PS_OUTPUT output; @@ -70,6 +86,11 @@ PS_OUTPUT ps_copy(PS_INPUT input) return output; } +float4 ps_blit_1to1(float4 pos : SV_Position) : SV_Target +{ + return Texture.Load(int3(pos.xy, 0)); +} + float ps_depth_copy(PS_INPUT input) : SV_Depth { return sample_c(input.t).r; diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index b741ca9078..78185227c9 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -222,6 +222,12 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) return false; } + if (!m_shader_cache.GetVertexShaderAndInputLayout(m_dev.get(), m_convert.vs_blit_1to1.put(), nullptr, + il_convert, std::size(il_convert), *convert_hlsl, nullptr, "vs_blit_1to1")) + { + return false; + } + for (size_t i = 0; i < std::size(m_convert.ps); i++) { m_convert.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *convert_hlsl, nullptr, shaderName(static_cast(i))); @@ -229,6 +235,10 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) return false; } + m_convert.ps_ps_blit_1to1 = m_shader_cache.GetPixelShader(m_dev.get(), *convert_hlsl, nullptr, "ps_blit_1to1"); + if (!m_convert.ps_ps_blit_1to1) + return false; + shader = ReadShaderSource("shaders/dx11/present.fx"); if (!shader.has_value()) return false; @@ -1370,7 +1380,6 @@ void GSDevice11::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTextur {GSVector4(right, bottom, 0.5f, 1.0f), GSVector2(sRect.z, sRect.w)}, }; - IASetVertexBuffer(vertices, sizeof(vertices[0]), std::size(vertices)); IASetInputLayout(m_convert.il.get()); IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); @@ -1390,6 +1399,48 @@ void GSDevice11::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTextur DrawPrimitive(); } +void GSDevice11::BlitOnetoOne(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r) +{ + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + + CommitClear(sTex); + + // preemptively bind srv if possible + + if (m_state.cached_rt_view != sTex && m_state.cached_dsv != sTex) + PSSetShaderResource(0, sTex); + + // ps unbind conflicting srvs + + PSUnbindConflictingSRVs(dTex); + + // om + + OMSetRenderTargets(dTex, nullptr, &r); + OMSetDepthStencilState(m_convert.dss.get(), 0); + OMSetBlendState(nullptr, 0); + + // ia + + IASetInputLayout(nullptr); + IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + // vs + + VSSetShader(m_convert.vs_blit_1to1.get(), nullptr); + + // ps + + PSSetShaderResource(0, sTex); + PSSetShader(m_convert.ps_ps_blit_1to1.get(), nullptr); + PSUpdateShaderState(true, false); + + // draw + + g_perfmon.Put(GSPerfMon::DrawCalls, 1); + m_ctx->Draw(3, 0); +} + void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear) { g_perfmon.Put(GSPerfMon::TextureCopies, 1); @@ -2416,6 +2467,21 @@ void GSDevice11::IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY topology) } } +D3D11_PRIMITIVE_TOPOLOGY GSDevice11::IAGetPrimitiveTopology(GSHWDrawConfig::Topology topology) +{ + switch (topology) + { + case GSHWDrawConfig::Topology::Point: + return D3D11_PRIMITIVE_TOPOLOGY_POINTLIST; + case GSHWDrawConfig::Topology::Line: + return D3D11_PRIMITIVE_TOPOLOGY_LINELIST; + case GSHWDrawConfig::Topology::Triangle: + return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + default: + return D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED; + } +} + void GSDevice11::VSSetShader(ID3D11VertexShader* vs, ID3D11Buffer* vs_cb) { if (m_state.vs != vs) @@ -2757,15 +2823,6 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) } } - D3D11_PRIMITIVE_TOPOLOGY topology = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED; - switch (config.topology) - { - case GSHWDrawConfig::Topology::Point: topology = D3D11_PRIMITIVE_TOPOLOGY_POINTLIST; break; - case GSHWDrawConfig::Topology::Line: topology = D3D11_PRIMITIVE_TOPOLOGY_LINELIST; break; - case GSHWDrawConfig::Topology::Triangle: topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; break; - } - IASetPrimitiveTopology(topology); - // Depth testing and sampling, bind resource as dsv read only and srv at the same time without the need of a copy. // TODO: On older feature levels read only depth is not supported, we can fallback to a copy but a better solution // is to wait for Depth feedback loops, in this case we can use it always, unbind the dsv and do testing in shader @@ -2800,22 +2857,21 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) if (config.pal) PSSetShaderResource(1, config.pal); - SetupVS(config.vs, &config.cb_vs); - SetupPS(config.ps, &config.cb_ps, config.sampler); - if (primid_texture) { OMDepthStencilSelector dss = config.depth; dss.zwe = 0; const OMBlendSelector blend(GSHWDrawConfig::ColorMaskSelector(1), GSHWDrawConfig::BlendState(true, CONST_ONE, CONST_ONE, 3 /* MIN */, CONST_ONE, CONST_ZERO, false, 0)); + IASetPrimitiveTopology(IAGetPrimitiveTopology(config.topology)); + SetupVS(config.vs, &config.cb_vs); + SetupPS(config.ps, &config.cb_ps, config.sampler); SetupOM(dss, blend, 0); OMSetRenderTargets(primid_texture, config.ds, &config.scissor, read_only_dsv); DrawIndexedPrimitive(); config.ps.date = 3; config.alpha_second_pass.ps.date = 3; - SetupPS(config.ps, nullptr, config.sampler); PSSetShaderResource(3, primid_texture); } @@ -2842,20 +2898,24 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) { // Requires a copy of the RT. // Used as "bind rt" flag when texture barrier is unsupported for tex is fb. - draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, draw_rt->GetFormat(), true); + + // Drawarea threshold, if the size exceeds 512 then prefer gpu copy, otherwise shader copy. + constexpr int area_size_x_y = 512 * 512; + const bool multipass = config.blend_multi_pass.enable || config.alpha_second_pass.enable; + if (multipass || (config.drawarea.width() * config.drawarea.height() > area_size_x_y)) + draw_rt_clone = CreateTexture(rtsize.x, rtsize.y,1, draw_rt->GetFormat(), true); + else + draw_rt_clone = CreateRenderTarget(rtsize.x, rtsize.y, draw_rt->GetFormat(), false, true); if (!draw_rt_clone) Console.Warning("D3D11: Failed to allocate temp texture for RT copy."); } - OMSetRenderTargets(draw_rt, draw_ds, &config.scissor, read_only_dsv); - SetupOM(config.depth, OMBlendSelector(config.colormask, config.blend), config.blend.constant); - // Clear stencil as close as possible to the RT bind, to avoid framebuffer swaps. if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne && multidraw_fb_copy) m_ctx->ClearDepthStencilView(*static_cast(draw_ds), D3D11_CLEAR_STENCIL, 0.0f, 1); - SendHWDraw(config, draw_rt_clone, draw_rt, config.require_one_barrier, config.require_full_barrier, false); + SendHWDraw(config, draw_rt_clone, draw_rt, draw_ds, read_only_dsv, config.require_one_barrier, config.require_full_barrier, false); if (config.blend_multi_pass.enable) { @@ -2881,7 +2941,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) } SetupOM(config.alpha_second_pass.depth, OMBlendSelector(config.alpha_second_pass.colormask, config.blend), config.blend.constant); - SendHWDraw(config, draw_rt_clone, draw_rt, config.alpha_second_pass.require_one_barrier, config.alpha_second_pass.require_full_barrier, true); + SendHWDraw(config, draw_rt_clone, draw_rt, draw_ds, read_only_dsv, config.alpha_second_pass.require_one_barrier, config.alpha_second_pass.require_full_barrier, true); } if (draw_rt_clone) @@ -2907,8 +2967,26 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) } } -void GSDevice11::SendHWDraw(const GSHWDrawConfig& config, GSTexture* draw_rt_clone, GSTexture* draw_rt, const bool one_barrier, const bool full_barrier, const bool skip_first_barrier) +void GSDevice11::SendHWDraw(GSHWDrawConfig& config, GSTexture* draw_rt_clone, GSTexture* draw_rt, GSTexture* draw_ds, ID3D11DepthStencilView* read_only_dsv, + const bool one_barrier, const bool full_barrier, const bool skip_first_barrier) { + auto SetupPipeline = [&]() { + IASetPrimitiveTopology(IAGetPrimitiveTopology(config.topology)); + SetupVS(config.vs, &config.cb_vs); + SetupPS(config.ps, config.ps.date == 3 ? nullptr : &config.cb_ps, config.sampler); + SetupOM(config.depth, OMBlendSelector(config.colormask, config.blend), config.blend.constant); + + PSUnbindConflictingSRVs(draw_rt); + OMSetRenderTargets(draw_rt, draw_ds, &config.scissor, read_only_dsv); + + if (config.tex) + PSSetShaderResource(0, config.tex); + if (config.tex && config.tex == draw_rt) + PSSetShaderResource(0, draw_rt_clone); + if (one_barrier || full_barrier) + PSSetShaderResource(2, draw_rt_clone); + }; + if (draw_rt_clone) { #ifdef PCSX2_DEVBUILD @@ -2917,11 +2995,10 @@ void GSDevice11::SendHWDraw(const GSHWDrawConfig& config, GSTexture* draw_rt_clo #endif auto CopyAndBind = [&](GSVector4i drawarea) { - CopyRect(draw_rt, draw_rt_clone, drawarea, drawarea.left, drawarea.top); - if (one_barrier || full_barrier) - PSSetShaderResource(2, draw_rt_clone); - if (config.tex && config.tex == config.rt) - PSSetShaderResource(0, draw_rt_clone); + if (draw_rt_clone->GetType() == GSTexture::Type::Texture) + CopyRect(draw_rt, draw_rt_clone, drawarea, drawarea.left, drawarea.top); + else + BlitOnetoOne(draw_rt, draw_rt_clone, drawarea); }; const GSVector4i rtsize(0, 0, draw_rt->GetWidth(), draw_rt->GetHeight()); @@ -2941,6 +3018,7 @@ void GSDevice11::SendHWDraw(const GSHWDrawConfig& config, GSTexture* draw_rt_clo const GSVector4i original_bbox = (*config.drawlist_bbox)[n].rintersect(config.drawarea); CopyAndBind(ProcessCopyArea(rtsize, original_bbox)); + SetupPipeline(); DrawIndexedPrimitive(p, count); p += count; } @@ -2953,5 +3031,6 @@ void GSDevice11::SendHWDraw(const GSHWDrawConfig& config, GSTexture* draw_rt_clo CopyAndBind(ProcessCopyArea(rtsize, config.drawarea)); } + SetupPipeline(); DrawIndexedPrimitive(); } diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.h b/pcsx2/GS/Renderers/DX11/GSDevice11.h index a7191393c1..c5a5016b96 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.h +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.h @@ -177,7 +177,9 @@ private: { wil::com_ptr_nothrow il; wil::com_ptr_nothrow vs; + wil::com_ptr_nothrow vs_blit_1to1; wil::com_ptr_nothrow ps[static_cast(ShaderConvert::Count)]; + wil::com_ptr_nothrow ps_ps_blit_1to1; wil::com_ptr_nothrow ln; wil::com_ptr_nothrow pt; wil::com_ptr_nothrow dss; @@ -308,6 +310,7 @@ public: void CommitClear(GSTexture* t); void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) override; + void BlitOnetoOne(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r); void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, bool linear); void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, ID3D11BlendState* bs, bool linear); @@ -332,6 +335,7 @@ public: void IASetInputLayout(ID3D11InputLayout* layout); void IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY topology); + D3D11_PRIMITIVE_TOPOLOGY IAGetPrimitiveTopology(GSHWDrawConfig::Topology topology); void VSSetShader(ID3D11VertexShader* vs, ID3D11Buffer* vs_cb); @@ -354,7 +358,8 @@ public: void SetupOM(OMDepthStencilSelector dssel, OMBlendSelector bsel, u8 afix); void RenderHW(GSHWDrawConfig& config) override; - void SendHWDraw(const GSHWDrawConfig& config, GSTexture* draw_rt_clone, GSTexture* draw_rt, const bool one_barrier, const bool full_barrier, const bool skip_first_barrier); + void SendHWDraw(GSHWDrawConfig& config, GSTexture* draw_rt_clone, GSTexture* draw_rt, GSTexture* draw_ds, ID3D11DepthStencilView* read_only_dsv, + const bool one_barrier, const bool full_barrier, const bool skip_first_barrier); void ClearSamplerCache() override; diff --git a/pcsx2/ShaderCacheVersion.h b/pcsx2/ShaderCacheVersion.h index 401eeed40b..e7f59bafef 100644 --- a/pcsx2/ShaderCacheVersion.h +++ b/pcsx2/ShaderCacheVersion.h @@ -3,4 +3,4 @@ /// Version number for GS and other shaders. Increment whenever any of the contents of the /// shaders change, to invalidate the cache. -static constexpr u32 SHADER_CACHE_VERSION = 83; +static constexpr u32 SHADER_CACHE_VERSION = 84;