From bbcd5ec25e89b92a966727e61f99f5333c64d163 Mon Sep 17 00:00:00 2001 From: TJnotJT Date: Wed, 13 May 2026 20:13:19 -0400 Subject: [PATCH] GS/HW: ROV support. --- bin/resources/shaders/dx11/tfx.fx | 235 +++++++--- bin/resources/shaders/opengl/tfx_fs.glsl | 168 ++++++-- bin/resources/shaders/vulkan/tfx.glsl | 137 ++++-- pcsx2-gsrunner/Main.cpp | 12 + .../Settings/GraphicsAdvancedSettingsTab.ui | 8 + .../GraphicsHardwareRenderingSettingsTab.ui | 8 + pcsx2-qt/Settings/GraphicsSettingsWidget.cpp | 5 + pcsx2/Config.h | 3 + pcsx2/GS/GS.cpp | 40 +- pcsx2/GS/GSGL.h | 5 + pcsx2/GS/GSPerfMon.h | 3 + pcsx2/GS/GSRegs.h | 4 +- pcsx2/GS/GSState.cpp | 39 +- pcsx2/GS/GSState.h | 6 +- pcsx2/GS/Renderers/Common/GSDevice.cpp | 41 +- pcsx2/GS/Renderers/Common/GSDevice.h | 67 ++- pcsx2/GS/Renderers/Common/GSShaderEnums.h | 7 + pcsx2/GS/Renderers/Common/GSTexture.h | 14 + pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp | 1 + pcsx2/GS/Renderers/DX11/GSDevice11.cpp | 130 ++++-- pcsx2/GS/Renderers/DX11/GSDevice11.h | 17 +- pcsx2/GS/Renderers/DX11/GSTexture11.cpp | 2 + pcsx2/GS/Renderers/DX11/GSTexture11.h | 1 - pcsx2/GS/Renderers/DX12/D3D12Builders.cpp | 25 ++ pcsx2/GS/Renderers/DX12/D3D12Builders.h | 2 + pcsx2/GS/Renderers/DX12/GSDevice12.cpp | 284 ++++++++---- pcsx2/GS/Renderers/DX12/GSDevice12.h | 64 ++- pcsx2/GS/Renderers/DX12/GSTexture12.cpp | 58 ++- pcsx2/GS/Renderers/DX12/GSTexture12.h | 2 + pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 403 +++++++++++++++++- pcsx2/GS/Renderers/HW/GSRendererHW.h | 9 +- pcsx2/GS/Renderers/Metal/GSDeviceMTL.h | 2 +- pcsx2/GS/Renderers/OpenGL/GLState.cpp | 18 +- pcsx2/GS/Renderers/OpenGL/GLState.h | 9 +- pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp | 201 +++++++-- pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h | 25 +- pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp | 33 +- pcsx2/GS/Renderers/OpenGL/GSTextureOGL.h | 3 + pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp | 256 +++++++++-- pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h | 65 ++- pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp | 42 +- pcsx2/GS/Renderers/Vulkan/GSTextureVK.h | 4 + pcsx2/GS/Renderers/Vulkan/VKBuilders.cpp | 4 +- pcsx2/GS/Renderers/Vulkan/VKBuilders.h | 2 +- pcsx2/ImGui/ImGuiOverlays.cpp | 6 + pcsx2/Pcsx2Config.cpp | 6 + pcsx2/ShaderCacheVersion.h | 2 +- 47 files changed, 2051 insertions(+), 427 deletions(-) diff --git a/bin/resources/shaders/dx11/tfx.fx b/bin/resources/shaders/dx11/tfx.fx index 667aa8acd3..77385cdfc1 100644 --- a/bin/resources/shaders/dx11/tfx.fx +++ b/bin/resources/shaders/dx11/tfx.fx @@ -50,6 +50,12 @@ #define PS_AA1_TRIANGLE_SW_Z 3 #endif +#ifndef PS_ROV_DEPTH_NONE +#define PS_ROV_DEPTH_NONE 0 +#define PS_ROV_DEPTH_READ_WRITE 1 +#define PS_ROV_DEPTH_READ_ONLY 2 +#endif + #ifndef PS_FST #define PS_IIP 0 #define PS_FST 0 @@ -111,6 +117,8 @@ #define PS_TEX_IS_FB 0 #define PS_AA1 0 #define PS_ABE 0 +#define PS_ROV_COLOR 0 +#define PS_ROV_DEPTH 0 #endif #ifndef VS_EXPAND_NONE @@ -132,6 +140,12 @@ #define SW_DEPTH (NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST || NEEDS_DEPTH_FOR_AA1) #define ZWRITE (PS_ZFLOOR || PS_ZCLAMP || SW_DEPTH) +#define PS_RETURN_COLOR_ROV (!PS_NO_COLOR && PS_ROV_COLOR) +#define PS_RETURN_COLOR (!PS_NO_COLOR && !PS_ROV_COLOR) +#define PS_RETURN_DEPTH_ROV (PS_ROV_DEPTH == PS_ROV_DEPTH_READ_WRITE) +#define PS_RETURN_DEPTH (ZWRITE && !PS_ROV_DEPTH) +#define PS_ROV_EARLYDEPTHSTENCIL (PS_ROV_COLOR && !PS_ROV_DEPTH && !ZWRITE) + struct VS_INPUT { float2 st : TEXCOORD0; @@ -178,22 +192,27 @@ struct PS_INPUT #ifdef PIXEL_SHADER -struct PS_OUTPUT +struct PS_OUTPUT_REAL { #define NUM_RTS 0 -#if !PS_NO_COLOR -#if PS_DATE == 1 || PS_DATE == 2 - float c : SV_Target; -#else - float4 c0 : SV_Target0; - #undef NUM_RTS - #define NUM_RTS 1 -#if !PS_NO_COLOR1 - float4 c1 : SV_Target1; + +#if PS_RETURN_COLOR + #if PS_DATE == 1 || PS_DATE == 2 + float c : SV_Target; + #else + + float4 c0 : SV_Target0; + + #undef NUM_RTS + #define NUM_RTS 1 + + #if !PS_NO_COLOR1 + float4 c1 : SV_Target1; + #endif + #endif #endif -#endif -#endif -#if ZWRITE + +#if PS_RETURN_DEPTH // In DX12 we do depth feedback loops with a color copy. #if SW_DEPTH && PS_NO_COLOR1 && DX12 #if NUM_RTS > 0 @@ -208,16 +227,45 @@ struct PS_OUTPUT float depth : SV_Depth; #endif #endif + #undef NUM_RTS }; +struct PS_OUTPUT +{ +#if !PS_NO_COLOR + #if PS_DATE == 1 || PS_DATE == 2 + float c; + #else + float4 c0; + #if !PS_NO_COLOR1 + float4 c1; + #endif + #endif +#endif +}; + Texture2D Texture : register(t0); Texture2D Palette : register(t1); +#if !PS_ROV_COLOR Texture2D RtTexture : register(t2); +#endif Texture2D PrimMinTexture : register(t3); +#if !PS_ROV_DEPTH Texture2D DepthTexture : register(t4); +#endif SamplerState TextureSampler : register(s0); +#if PS_ROV_COLOR +RasterizerOrderedTexture2D RtTextureRov : register(u0); +static float4 rov_rt_value; +#endif + +#if PS_ROV_DEPTH +RasterizerOrderedTexture2D DepthTextureRov : register(u1); +static float rov_depth_value; +#endif + #ifdef DX12 cbuffer cb1 : register(b1) #else @@ -244,6 +292,38 @@ cbuffer cb1 float RcpScaleFactor; }; +float4 RtLoad(int2 xy) +{ +#if PS_ROV_COLOR + return rov_rt_value; +#else + return RtTexture.Load(int3(int2(xy), 0)); +#endif +} + +float DepthLoad(int2 xy) +{ +#if PS_ROV_DEPTH + return rov_depth_value; +#else + return DepthTexture.Load(int3(int2(xy), 0)); +#endif +} + +void RtWrite(int2 xy, float4 c) +{ +#if PS_ROV_COLOR + RtTextureRov[xy] = c; +#endif +} + +void DepthWrite(int2 xy, float d) +{ +#if PS_ROV_DEPTH + DepthTextureRov[xy] = d; +#endif +} + #if (PS_AUTOMATIC_LOD != 1) && (PS_MANUAL_LOD == 1) float manual_lod(float uv_w) { @@ -407,7 +487,7 @@ float4 sample_c_af(float2 uv, float uv_w) float4 sample_c(float2 uv, float uv_w, int2 xy) { #if PS_TEX_IS_FB == 1 - return RtTexture.Load(int3(int2(xy), 0)); + return RtLoad(xy); #elif PS_REGION_RECT == 1 return Texture.Load(int3(int2(uv), 0)); #else @@ -602,7 +682,7 @@ float4x4 sample_4p(uint4 u) uint fetch_raw_depth(int2 xy) { #if PS_TEX_IS_FB == 1 - float4 col = RtTexture.Load(int3(xy, 0)); + float4 col = RtLoad(xy); #else float4 col = Texture.Load(int3(xy, 0)); #endif @@ -612,7 +692,7 @@ uint fetch_raw_depth(int2 xy) float4 fetch_raw_color(int2 xy) { #if PS_TEX_IS_FB == 1 - return RtTexture.Load(int3(xy, 0)); + return RtLoad(xy); #else return Texture.Load(int3(xy, 0)); #endif @@ -621,7 +701,7 @@ float4 fetch_raw_color(int2 xy) float4 fetch_c(int2 uv) { #if PS_TEX_IS_FB == 1 - return RtTexture.Load(int3(uv, 0)); + return RtLoad(uv); #else return Texture.Load(int3(uv, 0)); #endif @@ -1027,7 +1107,7 @@ void ps_fbmask(inout float4 C, float2 pos_xy) if (PS_FBMASK) { float multi = PS_COLCLIP_HW ? 65535.0f : 255.0f; - float4 RT = trunc(RtTexture.Load(int3(pos_xy, 0)) * multi + 0.1f); + float4 RT = trunc(RtLoad(int2(pos_xy)) * multi + 0.1f); C = (float4)(((uint4)C & ~FbMask) | ((uint4)RT & FbMask)); } } @@ -1103,7 +1183,7 @@ void ps_blend(inout float4 Color, inout float4 As_rgba, float2 pos_xy) As_rgba.rgb = (float3)1.0f; } - float4 RT = SW_BLEND_NEEDS_RT ? RtTexture.Load(int3(pos_xy, 0)) : (float4)0.0f; + float4 RT = SW_BLEND_NEEDS_RT ? RtLoad(int2(pos_xy)) : (float4)0.0f; if (PS_SHUFFLE && SW_BLEND_NEEDS_RT) { @@ -1236,20 +1316,49 @@ void ps_blend(inout float4 Color, inout float4 As_rgba, float2 pos_xy) } } -PS_OUTPUT ps_main(PS_INPUT input) +#if PS_ROV_EARLYDEPTHSTENCIL +[earlydepthstencil] +#endif + +#if PS_ROV_COLOR || PS_ROV_DEPTH +#define DISCARD rov_discard = true +#else +#define DISCARD discard +#endif + +#if (PS_RETURN_COLOR || PS_RETURN_DEPTH) +PS_OUTPUT_REAL ps_main(PS_INPUT input) +#else +void ps_main(PS_INPUT input) +#endif { // Must floor before depth testing. #if PS_ZFLOOR input.p.z = floor(input.p.z * exp2(32.0f)) * exp2(-32.0f); #endif -#if PS_ZTST == ZTST_GEQUAL - if (input.p.z < DepthTexture.Load(int3(input.p.xy, 0)).r) - discard; -#elif PS_ZTST == ZTST_GREATER - if (input.p.z <= DepthTexture.Load(int3(input.p.xy, 0)).r) - discard; +#if PS_ROV_COLOR + rov_rt_value = RtTextureRov[input.p.xy]; #endif + +#if PS_ROV_DEPTH + rov_depth_value = DepthTextureRov[input.p.xy]; +#endif + +#if PS_ROV_COLOR || PS_ROV_DEPTH + bool rov_discard = false; +#endif + + // Use ROV discard macro for since we cannot do + // conditional discard based on value read from ROV. +#if PS_ZTST == ZTST_GEQUAL + if (input.p.z < DepthLoad(input.p.xy)) + DISCARD; +#elif PS_ZTST == ZTST_GREATER + if (input.p.z <= DepthLoad(input.p.xy)) + DISCARD; +#endif + float4 C = ps_color(input); #if PS_AA1 @@ -1267,13 +1376,11 @@ PS_OUTPUT ps_main(PS_INPUT input) bool atst_pass = atst(C); -#if PS_AFAIL == AFAIL_KEEP +#if PS_ATST != PS_ATST_NONE && PS_AFAIL == AFAIL_KEEP if (!atst_pass) discard; #endif - PS_OUTPUT output; - if (PS_SCANMSK & 2) { // fail depth test on prohibited lines @@ -1284,7 +1391,7 @@ PS_OUTPUT ps_main(PS_INPUT input) float4 alpha_blend = (float4)0.0f; if (SW_AD_TO_HW) { - float4 RT = PS_RTA_CORRECTION ? trunc(RtTexture.Load(int3(input.p.xy, 0)) * 128.0f + 0.1f) : trunc(RtTexture.Load(int3(input.p.xy, 0)) * 255.0f + 0.1f); + float4 RT = PS_RTA_CORRECTION ? trunc(RtLoad(input.p.xy) * 128.0f + 0.1f) : trunc(RtLoad(input.p.xy) * 255.0f + 0.1f); alpha_blend = (float4)(RT.a / 128.0f); } else @@ -1308,9 +1415,9 @@ PS_OUTPUT ps_main(PS_INPUT input) #if PS_WRITE_RG == 1 // Pseudo 16 bits access. - float rt_a = RtTexture.Load(int3(input.p.xy, 0)).g; + float rt_a = RtLoad(input.p.xy).g; #else - float rt_a = RtTexture.Load(int3(input.p.xy, 0)).a; + float rt_a = RtLoad(input.p.xy).a; #endif #if (PS_DATE & 3) == 1 @@ -1329,9 +1436,8 @@ PS_OUTPUT ps_main(PS_INPUT input) #endif #endif - if (bad) - discard; - +if (bad) + discard; #endif #if PS_DATE == 3 @@ -1342,6 +1448,8 @@ PS_OUTPUT ps_main(PS_INPUT input) discard; #endif + PS_OUTPUT output; + // Get first primitive that will write a failling alpha value #if PS_DATE == 1 // DATM == 0 @@ -1427,32 +1535,32 @@ PS_OUTPUT ps_main(PS_INPUT input) alpha_blend.a = float(atst_pass); #endif + // Output color scaling #if !PS_NO_COLOR output.c0.a = PS_RTA_CORRECTION ? C.a / 128.0f : C.a / 255.0f; output.c0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.0f) : C.rgb / 255.0f; #if !PS_NO_COLOR1 output.c1 = alpha_blend; #endif +#endif // !PS_NO_COLOR // Alpha test with feedback #if PS_AFAIL == AFAIL_FB_ONLY if (!atst_pass) - input.p.z = DepthTexture.Load(int3(input.p.xy, 0)).r; + input.p.z = DepthLoad(input.p.xy); #elif PS_AFAIL == AFAIL_ZB_ONLY if (!atst_pass) - output.c0 = RtTexture.Load(int3(input.p.xy, 0)); + output.c0 = RtLoad(input.p.xy); #elif PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z if (!atst_pass) { - output.c0.a = RtTexture.Load(int3(input.p.xy, 0)).a; + output.c0.a = RtLoad(input.p.xy).a; #if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z - input.p.z = DepthTexture.Load(int3(input.p.xy, 0)).r; + input.p.z = DepthLoad(input.p.xy); #endif } #endif -#endif // !PS_NO_COLOR - #endif // PS_DATE != 1/2 #if PS_ZCLAMP @@ -1461,18 +1569,47 @@ PS_OUTPUT ps_main(PS_INPUT input) #if PS_AA1 == PS_AA1_TRIANGLE_SW_Z if (!bool(input.interior)) - input.p.z = DepthTexture.Load(int3(input.p.xy, 0)).r; // No depth update for triangle edges. + input.p.z = DepthLoad(input.p.xy); // No depth update for triangle edges. #endif -#if ZWRITE -#if SW_DEPTH && PS_NO_COLOR1 && DX12 - // Output color clone for feedback as well as real depth. - output.depth_color = input.p.z; -#endif - output.depth = input.p.z; +#if (PS_RETURN_COLOR || PS_RETURN_DEPTH) + // Output struct with the actual system values output semantics. + PS_OUTPUT_REAL output_real; #endif - return output; + // Color write back +#if PS_RETURN_COLOR + #if PS_DATE == 1 || PS_DATE == 2 + output_real.c = output.c; + #else + output_real.c0 = output.c0; + #if !PS_NO_COLOR1 + output_real.c1 = output.c1; + #endif + #endif +#elif PS_RETURN_COLOR_ROV + output.c0 = (rov_discard | (FbMask == 0xFFu)) ? RtLoad(input.p.xy) : output.c0; + + RtWrite(input.p.xy, output.c0); +#endif + + // Depth write back +#if PS_RETURN_DEPTH + output_real.depth = input.p.z; + #if SW_DEPTH && PS_NO_COLOR1 && DX12 + // Output color clone for feedback. + output_real.depth_color = input.p.z; + #endif +#elif PS_RETURN_DEPTH_ROV + #if SW_DEPTH + input.p.z = rov_discard ? DepthLoad(input.p.xy) : input.p.z; + #endif + DepthWrite(input.p.xy, input.p.z); +#endif + +#if (PS_RETURN_COLOR || PS_RETURN_DEPTH) + return output_real; +#endif } #endif // PIXEL_SHADER diff --git a/bin/resources/shaders/opengl/tfx_fs.glsl b/bin/resources/shaders/opengl/tfx_fs.glsl index 5be4258cf5..528a392d3b 100644 --- a/bin/resources/shaders/opengl/tfx_fs.glsl +++ b/bin/resources/shaders/opengl/tfx_fs.glsl @@ -40,6 +40,12 @@ #define PS_AA1_TRIANGLE_SW_Z 3 #endif +#ifndef PS_ROV_DEPTH_NONE +#define PS_ROV_DEPTH_NONE 0 +#define PS_ROV_DEPTH_READ_WRITE 1 +#define PS_ROV_DEPTH_READ_ONLY 2 +#endif + // TEX_COORD_DEBUG output the uv coordinate as color. It is useful // to detect bad sampling due to upscaling //#define TEX_COORD_DEBUG @@ -59,11 +65,17 @@ #define NEEDS_DEPTH_FOR_ZTST (PS_ZTST == ZTST_GEQUAL || PS_ZTST == ZTST_GREATER) #define NEEDS_DEPTH_FOR_AA1 (PS_AA1 == PS_AA1_TRIANGLE_SW_Z) -#define NEEDS_RT (NEEDS_RT_EARLY || NEEDS_RT_FOR_AFAIL || (!PS_PRIMID_INIT && (PS_FBMASK || SW_BLEND_NEEDS_RT || SW_AD_TO_HW)) || PS_COLOR_FEEDBACK) +#define NEEDS_RT (NEEDS_RT_EARLY || NEEDS_RT_FOR_AFAIL || (!PS_PRIMID_INIT && (PS_FBMASK || SW_BLEND_NEEDS_RT || SW_AD_TO_HW))) #define NEEDS_TEX (PS_TFX != 4) #define SW_DEPTH (NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST || NEEDS_DEPTH_FOR_AA1) #define ZWRITE (SW_DEPTH || PS_ZCLAMP || PS_ZFLOOR) +#define PS_RETURN_COLOR_ROV (!PS_NO_COLOR && PS_ROV_COLOR) +#define PS_RETURN_COLOR (!PS_NO_COLOR && !PS_ROV_COLOR) +#define PS_RETURN_DEPTH_ROV (PS_ROV_DEPTH == PS_ROV_DEPTH_READ_WRITE) +#define PS_RETURN_DEPTH (ZWRITE && !PS_ROV_DEPTH) +#define PS_ROV_EARLYDEPTHSTENCIL (PS_ROV_COLOR && !PS_ROV_DEPTH && !ZWRITE) + layout(std140, binding = 0) uniform cb21 { vec3 FogColor; @@ -121,28 +133,42 @@ in SHADER #if defined(GL_EXT_shader_framebuffer_fetch) #undef TARGET_0_QUALIFIER #define TARGET_0_QUALIFIER inout - #define LAST_FRAG_COLOR SV_Target0 + #define LAST_FRAG_COLOR o_col0 #elif defined(GL_ARM_shader_framebuffer_fetch) #define LAST_FRAG_COLOR gl_LastFragColorARM #endif #endif -#if !PS_NO_COLOR && !PS_NO_COLOR1 - // Same buffer but 2 colors for dual source blending - layout(location = 0, index = 0) TARGET_0_QUALIFIER vec4 SV_Target0; - layout(location = 0, index = 1) out vec4 SV_Target1; -#elif !PS_NO_COLOR - layout(location = 0) TARGET_0_QUALIFIER vec4 SV_Target0; +#if PS_RETURN_COLOR + #if !PS_NO_COLOR && !PS_NO_COLOR1 + // Same buffer but 2 colors for dual source blending + layout(location = 0, index = 0) TARGET_0_QUALIFIER vec4 o_col0; + layout(location = 0, index = 1) out vec4 o_col1; + #elif !PS_NO_COLOR + layout(location = 0) TARGET_0_QUALIFIER vec4 o_col0; + #endif +#elif PS_RETURN_COLOR_ROV + vec4 o_col0; #endif // Depth feedback mode 2 is for depth as color. // Use FB fetch for the feedback if it's available. #if SW_DEPTH && PS_NO_COLOR1 && (DEPTH_FEEDBACK_SUPPORT == 2) -#if HAS_FRAMEBUFFER_FETCH - layout(location = 1) inout float SV_Target1; -#else - layout(location = 1) out float SV_Target1; + #if HAS_FRAMEBUFFER_FETCH + layout(location = 1) inout float o_col1; + #else + layout(location = 1) out float o_col1; + #endif #endif + +#if PS_ROV_COLOR + layout(binding = 0, rgba8) uniform restrict coherent image2D RtImageRov; + vec4 rov_rt_value = vec4(0.0f, 0.0f, 0.0f, 1.0f); +#endif + +#if PS_ROV_DEPTH + layout(binding = 1, r32f) uniform restrict coherent image2D DepthImageRov; + float rov_depth_value = 0.0f; #endif #if NEEDS_TEX @@ -161,17 +187,19 @@ layout(binding = 3) uniform sampler2D img_prim_min; // Depth feedback mode 1 binds depth buffer directly as a texture. // Depth feedback mode 2 (depth as color) can use FB fetch for the feedback, // in which case we don't need to explicitly bind depth as a texture. -#if (DEPTH_FEEDBACK_SUPPORT == 1 || (DEPTH_FEEDBACK_SUPPORT == 2 && !HAS_FRAMEBUFFER_FETCH)) && SW_DEPTH +#if (DEPTH_FEEDBACK_SUPPORT == 1 || (DEPTH_FEEDBACK_SUPPORT == 2 && !HAS_FRAMEBUFFER_FETCH)) && SW_DEPTH && !PS_ROV_DEPTH layout(binding = 4) uniform sampler2D DepthSampler; #endif -#if ZWRITE && PS_HAS_CONSERVATIVE_DEPTH && !SW_DEPTH +#if ZWRITE && PS_HAS_CONSERVATIVE_DEPTH && !SW_DEPTH && !PS_ROV_DEPTH layout(depth_less) out float gl_FragDepth; #endif vec4 sample_from_rt() { -#if !NEEDS_RT +#if PS_ROV_COLOR + return rov_rt_value; +#elif !NEEDS_RT return vec4(0.0); #elif HAS_FRAMEBUFFER_FETCH return LAST_FRAG_COLOR; @@ -180,14 +208,16 @@ vec4 sample_from_rt() #endif } -vec4 sample_from_depth() +float sample_from_depth() { -#if !SW_DEPTH - return vec4(0.0); +#if PS_ROV_DEPTH + return rov_depth_value; +#elif !SW_DEPTH + return 0.0f; #elif HAS_FRAMEBUFFER_FETCH && (DEPTH_FEEDBACK_SUPPORT == 2) - return SV_Target1; + return o_col1; #else - return texelFetch(DepthSampler, ivec2(gl_FragCoord.xy), 0); + return texelFetch(DepthSampler, ivec2(gl_FragCoord.xy), 0).r; #endif } @@ -1183,6 +1213,20 @@ float As = As_rgba.a; #endif } +#if (PS_ROV_COLOR || PS_ROV_DEPTH) && (USE_ARB_FSI || USE_NV_FSI) +layout(pixel_interlock_ordered) in; +#endif + +#if PS_ROV_EARLYDEPTHSTENCIL +layout(early_fragment_tests) in; +#endif + +#if PS_ROV_COLOR || PS_ROV_DEPTH +#define DISCARD rov_discard = true +#else +#define DISCARD discard +#endif + void ps_main() { float input_z = gl_FragCoord.z; @@ -1192,18 +1236,40 @@ void ps_main() input_z = floor(input_z * exp2(32.0f)) * exp2(-32.0f); #endif +#if PS_ROV_COLOR || PS_ROV_DEPTH + #if USE_ARB_FSI + beginInvocationInterlockARB(); + #elif USE_NV_FSI + beginInvocationInterlockNV(); + #elif USE_INTEL_FSI + beginFragmentShaderOrderingINTEL(); + #endif +#endif + +#if PS_ROV_COLOR + rov_rt_value = imageLoad(RtImageRov, ivec2(gl_FragCoord.xy)); +#endif + +#if PS_ROV_DEPTH + rov_depth_value = imageLoad(DepthImageRov, ivec2(gl_FragCoord.xy)).r; +#endif + +#if PS_ROV_COLOR || PS_ROV_DEPTH + bool rov_discard = false; +#endif + #if PS_ZTST == ZTST_GEQUAL - if (input_z < sample_from_depth().r) - discard; + if (input_z < sample_from_depth()) + DISCARD; #elif PS_ZTST == ZTST_GREATER - if (input_z <= sample_from_depth().r) - discard; + if (input_z <= sample_from_depth()) + DISCARD; #endif #if PS_SCANMSK & 2 // fail depth test on prohibited lines if ((int(gl_FragCoord.y) & 1) == (PS_SCANMSK & 1)) - discard; + DISCARD; #endif #if PS_DATE >= 5 @@ -1232,7 +1298,7 @@ void ps_main() #endif if (bad) { - discard; + DISCARD; } #endif @@ -1243,7 +1309,7 @@ void ps_main() // the bad alpha value so we must keep it. if (gl_PrimitiveID > stencil_ceil) { - discard; + DISCARD; } #endif @@ -1264,9 +1330,9 @@ void ps_main() bool atst_pass = atst(C); -#if PS_AFAIL == AFAIL_KEEP +#if PS_ATST != PS_ATST_NONE && PS_AFAIL == AFAIL_KEEP if (!atst_pass) - discard; + DISCARD; #endif #if SW_AD_TO_HW @@ -1293,12 +1359,12 @@ void ps_main() #if PS_DATE == 1 // DATM == 0 // Pixel with alpha equal to 1 will failed (128-255) - SV_Target0 = (C.a > 127.5f) ? vec4(gl_PrimitiveID) : vec4(0x7FFFFFFF); + o_col0 = (C.a > 127.5f) ? vec4(gl_PrimitiveID) : vec4(0x7FFFFFFF); return; #elif PS_DATE == 2 // DATM == 1 // Pixel with alpha equal to 0 will failed (0-127) - SV_Target0 = (C.a < 127.5f) ? vec4(gl_PrimitiveID) : vec4(0x7FFFFFFF); + o_col0 = (C.a < 127.5f) ? vec4(gl_PrimitiveID) : vec4(0x7FFFFFFF); return; #endif @@ -1372,7 +1438,7 @@ void ps_main() // Alpha test with feedback #if PS_AFAIL == AFAIL_FB_ONLY if (!atst_pass) - input_z = sample_from_depth().r; + input_z = sample_from_depth(); #elif PS_AFAIL == AFAIL_ZB_ONLY if (!atst_pass) C = sample_from_rt(); @@ -1381,17 +1447,17 @@ void ps_main() { C.a = sample_from_rt().a; #if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z - input_z = sample_from_depth().r; + input_z = sample_from_depth(); #endif } #endif - // Warning: do not write SV_Target0 until the end since the value might be needed for + // Warning: do not write o_col0 until the end since the value might be needed for // FB fetch in sample_from_rt(). - SV_Target0 = C; + o_col0 = C; #if !PS_NO_COLOR1 - SV_Target1 = alpha_blend; + o_col1 = alpha_blend; #endif #endif @@ -1401,18 +1467,40 @@ void ps_main() #if PS_AA1 == PS_AA1_TRIANGLE_SW_Z if (!bool(PSin.interior)) - input_z = sample_from_depth().r; // No depth update for triangle edges. + input_z = sample_from_depth(); // No depth update for triangle edges. #endif -#if ZWRITE +// Writing back color (result already written to o_col0 for non-ROV) +#if PS_RETURN_COLOR_ROV + bvec4 discard_channels = bvec4(uvec4(rov_discard) | uvec4(equal(FbMask, uvec4(0xFFu)))); + o_col0 = mix(o_col0, sample_from_rt(), discard_channels); + + imageStore(RtImageRov, ivec2(gl_FragCoord.xy), o_col0); +#endif + +// Writing back depth +#if PS_RETURN_DEPTH #if SW_DEPTH && PS_NO_COLOR1 && (DEPTH_FEEDBACK_SUPPORT == 2) // Depth as color write. For depth as color feedback we write to both // color copy and real depth to avoid having to copy back to real depth. - // Warning: do not write SV_Target1 until the end since the value might + // Warning: do not write o_col1 until the end since the value might // be needed for FB fetch in sample_from_depth(). - SV_Target1 = input_z; + o_col1 = input_z; #endif // Standard depth write. gl_FragDepth = input_z; +#elif PS_RETURN_DEPTH_ROV + input_z = rov_discard ? sample_from_depth() : input_z; + + imageStore(DepthImageRov, ivec2(gl_FragCoord.xy), vec4(input_z, 0, 0, 1.0f)); +#endif + +#if (PS_ROV_COLOR || PS_ROV_DEPTH) + #if USE_ARB_FSI + endInvocationInterlockARB(); + #elif USE_NV_FSI + endInvocationInterlockNV(); + #endif + // No end invocation for Intel fragment shader ordering. #endif } diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl index 706ee1face..110e9ee61a 100644 --- a/bin/resources/shaders/vulkan/tfx.glsl +++ b/bin/resources/shaders/vulkan/tfx.glsl @@ -522,6 +522,12 @@ void main() #define PS_AA1_TRIANGLE_SW_Z 3 #endif +#ifndef PS_ROV_DEPTH_NONE +#define PS_ROV_DEPTH_NONE 0 +#define PS_ROV_DEPTH_READ_WRITE 1 +#define PS_ROV_DEPTH_READ_ONLY 2 +#endif + #ifndef PS_FST #define PS_FST 0 #define PS_WMS 0 @@ -574,6 +580,8 @@ void main() #define PS_NO_COLOR1 0 #define PS_DATE 0 #define PS_TEX_IS_FB 0 +#define PS_ROV_COLOR 0 +#define PS_ROV_DEPTH 0 #endif #define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D) @@ -586,7 +594,13 @@ void main() #define PS_FEEDBACK_LOOP_IS_NEEDED_RT (PS_TEX_IS_FB == 1 || AFAIL_NEEDS_RT || PS_FBMASK || SW_BLEND_NEEDS_RT || SW_AD_TO_HW || (PS_DATE >= 5)) #define PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH (AFAIL_NEEDS_DEPTH || ZTST_NEEDS_DEPTH || AA1_NEEDS_DEPTH) -#define ZWRITE (PS_ZCLAMP || PS_ZFLOOR || PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH) +#define ZWRITE (PS_ZCLAMP || PS_ZFLOOR || SW_DEPTH || PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH) + +#define PS_RETURN_COLOR_ROV (!PS_NO_COLOR && PS_ROV_COLOR) +#define PS_RETURN_COLOR (!PS_NO_COLOR && !PS_ROV_COLOR) +#define PS_RETURN_DEPTH_ROV (PS_ROV_DEPTH == PS_ROV_DEPTH_READ_WRITE) +#define PS_RETURN_DEPTH (ZWRITE && !PS_ROV_DEPTH) +#define PS_ROV_EARLYDEPTHSTENCIL (PS_ROV_COLOR && !PS_ROV_DEPTH && !ZWRITE) #define NEEDS_TEX (PS_TFX != 4) @@ -625,11 +639,27 @@ layout(location = 0) in VSOutput flat uint interior; // 1 for triangle interior; 0 for edge; } vsIn; -#if !PS_NO_COLOR && !PS_NO_COLOR1 -layout(location = 0, index = 0) out vec4 o_col0; -layout(location = 0, index = 1) out vec4 o_col1; -#elif !PS_NO_COLOR -layout(location = 0) out vec4 o_col0; +#if PS_RETURN_COLOR + #if !PS_NO_COLOR1 + layout(location = 0, index = 0) out vec4 o_col0; + layout(location = 0, index = 1) out vec4 o_col1; + #elif !PS_NO_COLOR + layout(location = 0) out vec4 o_col0; + #endif +#elif PS_RETURN_COLOR_ROV + vec4 o_col0; +#endif + +#if PS_ROV_COLOR + layout(set = 1, binding = 5, rgba8) uniform restrict coherent image2D RtImageRov; + vec4 rov_rt_value; + vec4 sample_from_rt() { return rov_rt_value; } +#endif + +#if PS_ROV_DEPTH + layout(set = 1, binding = 6, r32f) uniform restrict coherent image2D DepthImageRov; + float rov_depth_value; + float sample_from_depth() { return rov_depth_value; } #endif #if NEEDS_TEX @@ -639,27 +669,27 @@ layout(set = 1, binding = 1) uniform texture2D Palette; #if PS_FEEDBACK_LOOP_IS_NEEDED_RT || PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH #if defined(DISABLE_TEXTURE_BARRIER) || defined(HAS_FEEDBACK_LOOP_LAYOUT) - #if PS_FEEDBACK_LOOP_IS_NEEDED_RT + #if (PS_FEEDBACK_LOOP_IS_NEEDED_RT && !PS_ROV_COLOR) layout(set = 1, binding = 2) uniform texture2D RtSampler; vec4 sample_from_rt() { return texelFetch(RtSampler, ivec2(gl_FragCoord.xy), 0); } #endif - #if PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH + #if (PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH && !PS_ROV_DEPTH) layout(set = 1, binding = 4) uniform texture2D DepthSampler; - vec4 sample_from_depth() { return texelFetch(DepthSampler, ivec2(gl_FragCoord.xy), 0); } + float sample_from_depth() { return texelFetch(DepthSampler, ivec2(gl_FragCoord.xy), 0).r; } #endif #else // Must consider each case separately since the input attachment indices must be consecutive. - #if PS_FEEDBACK_LOOP_IS_NEEDED_RT && PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH + #if (PS_FEEDBACK_LOOP_IS_NEEDED_RT && !PS_ROV_COLOR) && (PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH && !PS_ROV_DEPTH) layout(input_attachment_index = 0, set = 1, binding = 2) uniform subpassInput RtSampler; layout(input_attachment_index = 1, set = 1, binding = 4) uniform subpassInput DepthSampler; vec4 sample_from_rt() { return subpassLoad(RtSampler); } - vec4 sample_from_depth() { return subpassLoad(DepthSampler); } - #elif PS_FEEDBACK_LOOP_IS_NEEDED_RT + float sample_from_depth() { return subpassLoad(DepthSampler).r; } + #elif (PS_FEEDBACK_LOOP_IS_NEEDED_RT && !PS_ROV_COLOR) layout(input_attachment_index = 0, set = 1, binding = 2) uniform subpassInput RtSampler; vec4 sample_from_rt() { return subpassLoad(RtSampler); } - #elif PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH + #elif (PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH && !PS_ROV_DEPTH) layout(input_attachment_index = 0, set = 1, binding = 4) uniform subpassInput DepthSampler; - vec4 sample_from_depth() { return subpassLoad(DepthSampler); } + float sample_from_depth() { return subpassLoad(DepthSampler).r; } #endif #endif #endif @@ -1604,7 +1634,7 @@ void ps_blend(inout vec4 Color, inout vec4 As_rgba) #elif PS_BLEND_MIX == 1 Color.rgb = ((A - B) * C_clamped + D) - (124.0f/256.0f); #else - Color.rgb = trunc((A - B) * C + D); + Color.rgb = trunc((A - B) * C + D); #endif #if PS_BLEND_HW == 1 @@ -1678,6 +1708,20 @@ void ps_blend(inout vec4 Color, inout vec4 As_rgba) #endif } +#if PS_ROV_COLOR || PS_ROV_DEPTH +layout(pixel_interlock_ordered) in; +#endif + +#if PS_ROV_EARLYDEPTHSTENCIL +layout(early_fragment_tests) in; +#endif + +#if PS_ROV_COLOR || PS_ROV_DEPTH +#define DISCARD rov_discard = true +#else +#define DISCARD discard +#endif + void main() { float input_z = gl_FragCoord.z; @@ -1687,18 +1731,34 @@ void main() input_z = floor(input_z * exp2(32.0f)) * exp2(-32.0f); #endif +#if PS_ROV_COLOR || PS_ROV_DEPTH + beginInvocationInterlockARB(); +#endif + +#if PS_ROV_COLOR + rov_rt_value = imageLoad(RtImageRov, ivec2(gl_FragCoord.xy)); +#endif + +#if PS_ROV_DEPTH + rov_depth_value = imageLoad(DepthImageRov, ivec2(gl_FragCoord.xy)).r; +#endif + +#if PS_ROV_COLOR || PS_ROV_DEPTH + bool rov_discard = gl_HelperInvocation; +#endif + #if PS_ZTST == ZTST_GEQUAL - if (input_z < sample_from_depth().r) - discard; + if (input_z < sample_from_depth()) + DISCARD; #elif PS_ZTST == ZTST_GREATER - if (input_z <= sample_from_depth().r) - discard; + if (input_z <= sample_from_depth()) + DISCARD; #endif #if PS_SCANMSK & 2 // fail depth test on prohibited lines if ((int(gl_FragCoord.y) & 1) == (PS_SCANMSK & 1)) - discard; + DISCARD; #endif #if PS_DATE >= 5 @@ -1726,7 +1786,7 @@ void main() #endif if (bad) { - discard; + DISCARD; } #endif // PS_DATE >= 5 @@ -1737,7 +1797,7 @@ void main() // the bad alpha value so we must keep it. if (gl_PrimitiveID > stencil_ceil) { - discard; + DISCARD; } #endif @@ -1758,9 +1818,10 @@ void main() bool atst_pass = atst(C); -#if PS_AFAIL == AFAIL_KEEP - if (!atst_pass) - discard; +#if PS_ATST != PS_ATST_NONE && PS_AFAIL == AFAIL_KEEP + if (!atst_pass) { + DISCARD; + } #endif #if SW_AD_TO_HW @@ -1852,6 +1913,7 @@ void main() alpha_blend.a = float(atst_pass); #endif + // Output color scaling #if !PS_NO_COLOR #if PS_RTA_CORRECTION o_col0.a = C.a / 128.0f; @@ -1870,7 +1932,7 @@ void main() // Alpha test with feedback #if PS_AFAIL == AFAIL_FB_ONLY if (!atst_pass) - input_z = sample_from_depth().r; + input_z = sample_from_depth(); #elif PS_AFAIL == AFAIL_ZB_ONLY if (!atst_pass) o_col0 = sample_from_rt(); @@ -1879,7 +1941,7 @@ void main() { o_col0.a = sample_from_rt().a; #if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z - input_z = sample_from_depth().r; + input_z = sample_from_depth(); #endif } #endif @@ -1891,11 +1953,28 @@ void main() #if PS_AA1 == PS_AA1_TRIANGLE_SW_Z if (!bool(vsIn.interior)) - input_z = sample_from_depth().r; // No depth update for triangle edges. + input_z = sample_from_depth(); // No depth update for triangle edges. #endif - #if ZWRITE + // Writing back color (result already written to o_col0 for non-ROV) + #if PS_RETURN_COLOR_ROV + bvec4 discard_channels = bvec4(uvec4(rov_discard) | uvec4(equal(FbMask, uvec4(0xFFu)))); + o_col0 = mix(o_col0, sample_from_rt(), discard_channels); + + imageStore(RtImageRov, ivec2(gl_FragCoord.xy), o_col0); + #endif + + // Writing back depth + #if PS_RETURN_DEPTH gl_FragDepth = input_z; + #elif PS_RETURN_DEPTH_ROV + input_z = rov_discard ? sample_from_depth() : input_z; + + imageStore(DepthImageRov, ivec2(gl_FragCoord.xy), vec4(input_z, 0, 0, 1.0f)); + #endif + + #if PS_ROV_COLOR || PS_ROV_DEPTH + endInvocationInterlockARB(); #endif #endif // PS_DATE } diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index 375985c848..88c4fdbd61 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -91,6 +91,9 @@ static double s_last_barriers = 0; static double s_last_copies = 0; static double s_last_uploads = 0; static double s_last_readbacks = 0; +static double s_last_depth_copies_rov = 0; +static double s_last_draws_rov = 0; +static double s_last_barriers_rov = 0; static u64 s_total_internal_draws = 0; static u64 s_total_draws = 0; static u64 s_total_render_passes = 0; @@ -98,6 +101,9 @@ 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_draws_rov = 0; +static u64 s_total_barriers_rov = 0; static u32 s_total_frames = 0; static u32 s_total_drawn_frames = 0; @@ -287,6 +293,9 @@ 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::DrawCallsROV, s_total_draws_rov, s_last_draws_rov); + update_stat(GSPerfMon::BarriersROV, s_total_barriers_rov, s_last_barriers_rov); const bool idle_frame = s_total_frames && (last_draws == s_total_internal_draws && last_uploads == s_total_uploads); @@ -913,6 +922,9 @@ 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@ 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) { Console.WriteLn(fmt::format("@HWSTAT@ Minimum Frame Time: {:.3f} ms ({:.3f} FPS)", PerformanceMetrics::GetMinimumFrameTime(), 1000.0f / PerformanceMetrics::GetMinimumFrameTime())); diff --git a/pcsx2-qt/Settings/GraphicsAdvancedSettingsTab.ui b/pcsx2-qt/Settings/GraphicsAdvancedSettingsTab.ui index aac9621e11..a1b419283e 100644 --- a/pcsx2-qt/Settings/GraphicsAdvancedSettingsTab.ui +++ b/pcsx2-qt/Settings/GraphicsAdvancedSettingsTab.ui @@ -126,6 +126,13 @@ + + + + ROV Barriers Vulkan + + + @@ -353,6 +360,7 @@ gsDumpCompression texturePreloading exclusiveFullscreenControl + rovBarriersVK useBlitSwapChain extendedUpscales disableMailboxPresentation diff --git a/pcsx2-qt/Settings/GraphicsHardwareRenderingSettingsTab.ui b/pcsx2-qt/Settings/GraphicsHardwareRenderingSettingsTab.ui index 952f829eb4..c0f22996df 100644 --- a/pcsx2-qt/Settings/GraphicsHardwareRenderingSettingsTab.ui +++ b/pcsx2-qt/Settings/GraphicsHardwareRenderingSettingsTab.ui @@ -72,6 +72,13 @@ + + + ROV + + + + Manual Hardware Renderer Fixes @@ -243,6 +250,7 @@ mipmapping accurateAlphaTest hwAA1 + rov enableHWFixes diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp index 455be1a21e..955abcb425 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -113,6 +113,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog, SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_hw.mipmapping, "EmuCore/GS", "hw_mipmap", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_hw.accurateAlphaTest, "EmuCore/GS", "HWAccurateAlphaTest", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_hw.hwAA1, "EmuCore/GS", "HWAA1", false); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_hw.rov, "EmuCore/GS", "HWROV", false); SettingWidgetBinder::BindWidgetToIntSetting( sif, m_hw.blending, "EmuCore/GS", "accurate_blending_unit", static_cast(AccBlendLevel::Basic)); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_hw.enableHWFixes, "EmuCore/GS", "UserHacks", false); @@ -239,6 +240,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog, SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_advanced.palFrameRate, "EmuCore/GS", "FrameRatePAL", 50.00f); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_advanced.spinCPUDuringReadbacks, "EmuCore/GS", "HWSpinCPUForReadbacks", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_advanced.spinGPUDuringReadbacks, "EmuCore/GS", "HWSpinGPUForReadbacks", false); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_advanced.rovBarriersVK, "EmuCore/GS", "HWROVBarriersVK", false); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_advanced.texturePreloading, "EmuCore/GS", "texture_preloading", static_cast(TexturePreloadingLevel::Off)); setTabVisible(m_advanced_tab, QtHost::ShouldShowAdvancedSettings()); @@ -497,6 +499,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog, dialog()->registerWidgetHelp( m_hw.hwAA1, tr("AA1"), tr("Unchecked"), tr("Enables AA1 (PS2 antialiasing), which some games require to render correctly. This may result in a heavy performance penalty.")); + dialog()->registerWidgetHelp( + m_hw.rov, tr("ROV"), tr("Unchecked"), tr("Enables ROV (Rasterizer Ordered View), which allows feedback loops to be executed with fewer draw calls. Can improve performance in feedback heavy games with higher accuracy settings.")); + dialog()->registerWidgetHelp( m_hw.textureFiltering, tr("Texture Filtering"), tr("Bilinear (PS2)"), tr("Changes what filtering algorithm is used to map textures to surfaces.
" diff --git a/pcsx2/Config.h b/pcsx2/Config.h index c88ca7eff6..9a83bf6ec7 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -793,6 +793,9 @@ struct Pcsx2Config HWMipmap : 1, HWAccurateAlphaTest : 1, HWAA1 : 1, + HWROV : 1, + HWROVLogging : 1, + HWROVBarriersVK : 1, ManualUserHacks : 1, UserHacks_AlignSpriteX : 1, UserHacks_CPUFBConversion : 1, diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index e320f17a3f..219dc5dfd7 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -701,16 +701,36 @@ void GSgetStats(SmallStringBase& info) } else { - info.format("{} HW | {} PRIM | {} DRW | {} DRWC | {} BAR | {} RP | {} RB | {} TC | {} TU", - api_name, - (int)pm.Get(GSPerfMon::Prim), - (int)pm.Get(GSPerfMon::Draw), - (int)std::ceil(pm.Get(GSPerfMon::DrawCalls)), - (int)std::ceil(pm.Get(GSPerfMon::Barriers)), - (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::TextureUploads))); + if (!GSConfig.HWROV) + { + info.format("{} HW | {} PRIM | {} DRW | {} DRWC | {} BAR | {} RP | {} RB | {} TC | {} TU", + api_name, + (int)pm.Get(GSPerfMon::Prim), + (int)pm.Get(GSPerfMon::Draw), + (int)std::ceil(pm.Get(GSPerfMon::DrawCalls)), + (int)std::ceil(pm.Get(GSPerfMon::Barriers)), + (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::TextureUploads))); + } + else + { + // Add ROV stats along standard stats. + info.format("{} HW | {} PRIM | {} DRW | {}/{} DRWC | {}/{} BAR | {} RP | {} RB | {}/{} TC | {} TU", + api_name, + (int)pm.Get(GSPerfMon::Prim), + (int)pm.Get(GSPerfMon::Draw), + (int)std::ceil(pm.Get(GSPerfMon::DrawCalls)), + (int)std::ceil(pm.Get(GSPerfMon::DrawCallsROV)), + (int)std::ceil(pm.Get(GSPerfMon::Barriers)), + (int)std::ceil(pm.Get(GSPerfMon::BarriersROV)), + (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::TextureUploads))); + } } } diff --git a/pcsx2/GS/GSGL.h b/pcsx2/GS/GSGL.h index b891421967..e8c8cb7bbb 100644 --- a/pcsx2/GS/GSGL.h +++ b/pcsx2/GS/GSGL.h @@ -46,10 +46,15 @@ #define GL_POP() g_gs_device->PopDebugGroup() #define GL_INS(...) g_gs_device->InsertDebugMessage(GSDevice::DebugMessageCategory::Message, __VA_ARGS__) #define GL_PERF(...) g_gs_device->InsertDebugMessage(GSDevice::DebugMessageCategory::Performance, __VA_ARGS__) + #define GL_ROV(...) \ + if (GSConfig.HWROVLogging) \ + Console.Warning(__VA_ARGS__); \ + g_gs_device->InsertDebugMessage(GSDevice::DebugMessageCategory::Message, __VA_ARGS__); #else #define GL_PUSH_(...) (void)(0) #define GL_PUSH(...) (void)(0) #define GL_POP() (void)(0) #define GL_INS(...) (void)(0) #define GL_PERF(...) (void)(0) + #define GL_ROV(...) (void)(0) #endif diff --git a/pcsx2/GS/GSPerfMon.h b/pcsx2/GS/GSPerfMon.h index 68dbe6026a..928eb3ec68 100644 --- a/pcsx2/GS/GSPerfMon.h +++ b/pcsx2/GS/GSPerfMon.h @@ -23,6 +23,9 @@ public: SyncPoint, Barriers, RenderPasses, + DepthCopiesROV, // Overlaps with regular texture copies. + DrawCallsROV, // Overlaps with regular draw calls. + BarriersROV, // Overlaps with regular barriers. CounterLast, // Reused counters for HW. diff --git a/pcsx2/GS/GSRegs.h b/pcsx2/GS/GSRegs.h index 6e5bbab3b5..0541c9e201 100644 --- a/pcsx2/GS/GSRegs.h +++ b/pcsx2/GS/GSRegs.h @@ -203,8 +203,8 @@ enum GS_AFAIL enum GS_ALPHA_BITS { - ALPHA_ABC_CS = 0, - ALPHA_ABC_CD = 1, + ALPHA_ABD_CS = 0, + ALPHA_ABD_CD = 1, ALPHA_C_AS = 0, ALPHA_C_AD = 1, ALPHA_C_FIX = 2, diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index ead3cfc336..7a4521a76b 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -4226,7 +4226,7 @@ bool GSState::TrianglesAreQuads(bool shuffle_check) } template -GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlist, bool save_bbox, float bbox_scale) +GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlist, bool save_bbox, float bbox_scale, u32* max_size) { const GSVector4i xyof = m_context->scissor.xyof.xyxy(); @@ -4274,7 +4274,14 @@ GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlis if (primclass == GS_TRIANGLE_CLASS && m_quad_check_valid && m_are_quads && !using_aa1) { // The triangles-are-quads check already ensures that there is no overlap. - m_drawlist.push_back(m_index->tail / n); + if (save_drawlist) + { + m_drawlist.push_back(m_index->tail / n); + } + else if (max_size) + { + *max_size = 1; + } if (save_bbox) { const GSVector4i draw_area = GSVector4i(m_vt.m_min.p.upld(m_vt.m_max.p) * GSVector4(16.0f)) + xyof; @@ -4286,6 +4293,7 @@ GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlis PRIM_OVERLAP overlap = PRIM_OVERLAP_NO; bool check_quads = (primclass == GS_TRIANGLE_CLASS) && !using_aa1; + u32 drawlist_size = 0; u32 i = 0; u32 skip = 0; // Number of indices to skip if we have the bbox from the previous iteration. @@ -4683,6 +4691,17 @@ GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlis { m_drawlist.push_back((j - i) / n); // Prim count } + else if (max_size) + { + // If the max size pointer is passed it means we just want to peek at + // the drawlist size up to the given limit to avoid unecessary work. + drawlist_size++; + if (drawlist_size >= *max_size) + { + *max_size = drawlist_size; + return drawlist_size > 0 ? PRIM_OVERLAP_YES : PRIM_OVERLAP_UNKNOW; + } + } else if (j < count) { return PRIM_OVERLAP_YES; // Early exit if not saving drawlist. @@ -4696,21 +4715,27 @@ GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlis all = bbox; i = j; } + + if (max_size) + { + *max_size = drawlist_size; + } + return overlap; } -GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlist(bool save_drawlist, bool save_bbox, float bbox_scale) +GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlist(bool save_drawlist, bool save_bbox, float bbox_scale, u32* max_size) { switch (m_vt.m_primclass) { case GS_POINT_CLASS: - return GetPrimitiveOverlapDrawlistImpl(save_drawlist, save_bbox, bbox_scale); + return GetPrimitiveOverlapDrawlistImpl(save_drawlist, save_bbox, bbox_scale, max_size); case GS_LINE_CLASS: - return GetPrimitiveOverlapDrawlistImpl(save_drawlist, save_bbox, bbox_scale); + return GetPrimitiveOverlapDrawlistImpl(save_drawlist, save_bbox, bbox_scale, max_size); case GS_TRIANGLE_CLASS: - return GetPrimitiveOverlapDrawlistImpl(save_drawlist, save_bbox, bbox_scale); + return GetPrimitiveOverlapDrawlistImpl(save_drawlist, save_bbox, bbox_scale, max_size); case GS_SPRITE_CLASS: - return GetPrimitiveOverlapDrawlistImpl(save_drawlist, save_bbox, bbox_scale); + return GetPrimitiveOverlapDrawlistImpl(save_drawlist, save_bbox, bbox_scale, max_size); default: pxFail("Invalid primclass."); // Impossible. return PRIM_OVERLAP_UNKNOW; diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index 15bd230a72..f96fcc0db1 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -533,8 +533,10 @@ public: bool TrianglesAreQuadsImpl(); bool TrianglesAreQuads(bool shuffle_check = false); template - PRIM_OVERLAP GetPrimitiveOverlapDrawlistImpl(bool save_drawlist = false, bool save_bbox = false, float bbox_scale = 1.0f); - PRIM_OVERLAP GetPrimitiveOverlapDrawlist(bool save_drawlist = false, bool save_bbox = false, float bbox_scale = 1.0f); + PRIM_OVERLAP GetPrimitiveOverlapDrawlistImpl(bool save_drawlist = false, bool save_bbox = false, + float bbox_scale = 1.0f, u32* max_size = nullptr); + PRIM_OVERLAP GetPrimitiveOverlapDrawlist(bool save_drawlist = false, bool save_bbox = false, + float bbox_scale = 1.0f, u32* max_size = nullptr); PRIM_OVERLAP PrimitiveOverlap(bool save_drawlist = false); template void GetPrimitiveOverlapDrawlistTextureBBoxImpl(float bbox_scale = 1.0f); diff --git a/pcsx2/GS/Renderers/Common/GSDevice.cpp b/pcsx2/GS/Renderers/Common/GSDevice.cpp index 5f338162d3..9653b99301 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.cpp +++ b/pcsx2/GS/Renderers/Common/GSDevice.cpp @@ -693,6 +693,12 @@ void GSDevice::Recycle(GSTexture* t) t->SetLastFrameUsed(m_frame); + t->ClearUnorderedAccess(); + +#ifdef PCSX2_DEVBUILD + t->SetDebugName(""); +#endif + FastList& pool = m_pool[!t->IsTexture()]; pool.push_front(t); m_pool_memory_usage += t->GetMemUsage(); @@ -776,6 +782,18 @@ GSTexture* GSDevice::CreateDepthStencil(const GSVector2i& size, bool clear, bool clear, !prefer_reuse); } +GSTexture* GSDevice::CreateDepthColor(int w, int h, bool clear, bool prefer_reuse) +{ + return FetchSurface(GSTexture::Type::RenderTarget, w, h, 1, GSTexture::Format::DepthColor, + clear, !prefer_reuse); +} + +GSTexture* GSDevice::CreateDepthColor(const GSVector2i& size, bool clear, bool prefer_reuse) +{ + return FetchSurface(GSTexture::Type::RenderTarget, size.x, size.y, 1, GSTexture::Format::DepthColor, + clear, !prefer_reuse); +} + GSTexture* GSDevice::CreateTexture(int w, int h, int mipmap_levels, GSTexture::Format format, bool prefer_reuse /* = false */) { pxAssert(mipmap_levels != 0 && (mipmap_levels < 0 || mipmap_levels <= GetMipmapLevelsForSize(w, h))); @@ -1486,9 +1504,9 @@ static const char* GetSetDATMName(SetDATM datm) return "Unknown"; } -static const char* GetPSAA1Name(u32 aa1) +static const char* GetPSAA1Name(GSHWDrawConfig::PS_AA1 aa1) { - switch (static_cast(aa1)) + switch (aa1) { case GSHWDrawConfig::PS_AA1::NONE: return "NONE"; case GSHWDrawConfig::PS_AA1::LINE: return "LINE"; @@ -1509,6 +1527,17 @@ static const char* GetTexHazardName(u32 tex_hazard) return "Unknown"; } +static const char* GetPSROVDepthname(GSHWDrawConfig::PS_ROV_DEPTH rov_depth) +{ + switch (rov_depth) + { + case GSHWDrawConfig::PS_ROV_DEPTH::NONE: return "NONE"; + case GSHWDrawConfig::PS_ROV_DEPTH::READ_ONLY: return "READ_ONLY"; + case GSHWDrawConfig::PS_ROV_DEPTH::READ_WRITE: return "READ_WRITE"; + } + return "Unknown"; +} + static void DumpPSSelector(DrawConfigWriter& out, const GSHWDrawConfig::PSSelector& ps) { out.WriteLn("aem_fmt: {}", ps.aem_fmt); @@ -1564,9 +1593,13 @@ static void DumpPSSelector(DrawConfigWriter& out, const GSHWDrawConfig::PSSelect out.WriteLn("point_sampler: {}", ps.point_sampler); out.WriteLn("region_rect: {}", ps.region_rect); out.WriteLn("scanmsk: {} ({})", GSUtil::GetSCANMSKName(ps.scanmsk), ps.scanmsk); - out.WriteLn("aa1: {} ({})", static_cast(ps.aa1), GetPSAA1Name(static_cast(ps.aa1))); + out.WriteLn("aa1: {} ({})", GetPSAA1Name(ps.aa1), static_cast(ps.aa1)); out.WriteLn("abe: {}", static_cast(ps.abe)); out.WriteLn("sw_aniso: {}", ps.sw_aniso); + out.WriteLn("rov_color: {}", ps.rov_color); + out.WriteLn("rov_depth: {} ({})", GetPSROVDepthname(ps.rov_depth), static_cast(ps.rov_depth)); + out.WriteLn("ztst: {} ({})", GSUtil::GetZTSTName(ps.ztst), static_cast(ps.ztst)); + out.WriteLn("zfloor: {}", static_cast(ps.zfloor)); } static void DumpVSSelector(DrawConfigWriter& out, const GSHWDrawConfig::VSSelector& vs) @@ -1690,6 +1723,8 @@ static void DumpConfig(DrawConfigWriter& out, const GSHWDrawConfig& conf, out.WriteLn("topology: {} ({})", GetTopologyName(conf.topology), static_cast(conf.topology)); out.WriteLn("require_one_barrier: {}", conf.require_one_barrier); out.WriteLn("require_full_barrier: {}", conf.require_full_barrier); + DumpVector4(out, "drawarea", conf.drawarea); + DumpVector4(out, "samplearea", conf.samplearea); out.WriteLn("tex_hazard: {}", GetTexHazardName(conf.tex_hazard)); out.WriteLn("destination_alpha: {} ({})", GetDestinationAlphaModeName(conf.destination_alpha), static_cast(conf.destination_alpha)); diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index 3b40b5c0fc..f3d90eb296 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -666,6 +666,7 @@ struct alignas(16) GSHWDrawConfig using PS_ATST = GSShader::PS_ATST; using PS_AFAIL = GSShader::PS_AFAIL; using PS_AA1 = GSShader::PS_AA1; + using PS_ROV_DEPTH = GSShader::PS_ROV_DEPTH; #pragma pack(push, 1) struct VSSelector { @@ -786,12 +787,16 @@ struct alignas(16) GSHWDrawConfig // Anisotropic filtering u32 sw_aniso : 5; + + // ROVs + u32 rov_color : 1; + PS_ROV_DEPTH rov_depth : 2; }; struct { u64 key_lo; - u32 key_hi; + u64 key_hi; }; }; __fi PSSelector() : key_lo(0), key_hi(0) {} @@ -800,6 +805,21 @@ struct alignas(16) GSHWDrawConfig __fi bool operator!=(const PSSelector& rhs) const { return (key_lo != rhs.key_lo || key_hi != rhs.key_hi); } __fi bool operator<(const PSSelector& rhs) const { return (key_lo < rhs.key_lo || key_hi < rhs.key_hi); } + __fi bool IsSWBlending() const + { + return blend_a || blend_b || blend_d; + } + + __fi bool IsZTesting() const + { + return ztst == ZTST_GEQUAL || ztst == ZTST_GREATER; + } + + __fi bool IsAlphaTesting() const + { + return atst != PS_ATST::NONE; + } + __fi bool IsFeedbackLoopRT() const { const u32 sw_blend_bits = blend_a | blend_b | blend_d; @@ -816,6 +836,11 @@ struct alignas(16) GSHWDrawConfig return afail_needs_depth || ztst_needs_depth || aa1_needs_depth; } + __fi bool HasShaderDiscard() const + { + return (IsAlphaTesting() && afail == PS_AFAIL::KEEP) || scanmsk || date || IsZTesting(); + } + /// Disables color output from the pixel shader, this is done when all channels are masked. __fi void DisableColorOutput() { @@ -844,9 +869,39 @@ struct alignas(16) GSHWDrawConfig { aa1 = PS_AA1::TRIANGLE; } + + if (rov_depth == PS_ROV_DEPTH::READ_WRITE) + { + rov_depth = PS_ROV_DEPTH::READ_ONLY; + } + } + + __fi bool HasColorOutput() const + { + return !no_color; + } + + __fi bool HasDepthOutput() const + { + return zfloor || zclamp || IsFeedbackLoopDepth() || (rov_depth == PS_ROV_DEPTH::READ_WRITE); + } + + __fi bool HasColorROV() const + { + return rov_color != 0; + } + + __fi bool HasDepthROV() const + { + return rov_depth == PS_ROV_DEPTH::READ_ONLY || rov_depth == PS_ROV_DEPTH::READ_WRITE; + } + + __fi bool HasDepthROVWrite() const + { + return rov_depth == PS_ROV_DEPTH::READ_WRITE; } }; - static_assert(sizeof(PSSelector) == 12, "PSSelector is 12 bytes"); + static_assert(sizeof(PSSelector) == 16, "PSSelector is 12 bytes"); #pragma pack(pop) struct PSSelectorHash { @@ -1255,6 +1310,11 @@ struct alignas(16) GSHWDrawConfig { return ps.IsFeedbackLoopDepth() || (tex_hazard == TEX_HAZARD_DEPTH); } + + bool IsBlending() + { + return blend.enable || blend_multi_pass.enable || ps.IsSWBlending(); + } // Dumping static void DumpConfig(const std::string& path, const GSHWDrawConfig& conf, @@ -1330,6 +1390,7 @@ public: bool test_and_sample_depth: 1; ///< Supports concurrently binding the depth-stencil buffer for sampling and depth testing. bool depth_feedback : 1; ///< Depth feedback loops can be done with DS directly (otherwise need to copy to separate RT). Implies `feedback_loops`. bool aa1 : 1; ///< Supports the GS AA1 feature. + bool rov : 1; ///< Supports rasterizer ordered views for both depth and color. FeatureSupport() { memset(this, 0, sizeof(*this)); @@ -1563,9 +1624,11 @@ public: GSTexture* CreateRenderTarget(int w, int h, GSTexture::Format format, bool clear = true, bool prefer_reuse = true); GSTexture* CreateDepthStencil(int w, int h, bool clear = true, bool prefer_reuse = true); + GSTexture* CreateDepthColor(int w, int h, bool clear = true, bool prefer_reuse = true); GSTexture* CreateTexture(int w, int h, int mipmap_levels, GSTexture::Format format, bool prefer_reuse = false); GSTexture* CreateRenderTarget(const GSVector2i& size, GSTexture::Format format, bool clear = true, bool prefer_reuse = true); GSTexture* CreateDepthStencil(const GSVector2i& size, bool clear = true, bool prefer_reuse = true); + GSTexture* CreateDepthColor(const GSVector2i& size, bool clear = true, bool prefer_reuse = true); GSTexture* CreateTexture(const GSVector2i& size, int mipmap_levels, GSTexture::Format format, bool prefer_reuse = false); GSTexture* CreateCompatible(GSTexture* tex, bool clear = true, bool prefer_reuse = true); GSTexture* CreateCompatible(GSTexture* tex, const GSVector2i& size, bool clear = true, bool prefer_reuse = true); diff --git a/pcsx2/GS/Renderers/Common/GSShaderEnums.h b/pcsx2/GS/Renderers/Common/GSShaderEnums.h index 381a947fb8..394bab8ee4 100644 --- a/pcsx2/GS/Renderers/Common/GSShaderEnums.h +++ b/pcsx2/GS/Renderers/Common/GSShaderEnums.h @@ -56,4 +56,11 @@ enum class PS_AA1 : uint32_t TRIANGLE_SW_Z = 3, ///< AA1 triangles with software Z discard }; +enum class PS_ROV_DEPTH : uint32_t +{ + NONE = 0, + READ_WRITE = 1, + READ_ONLY = 2, +}; + } // namespace GSShader diff --git a/pcsx2/GS/Renderers/Common/GSTexture.h b/pcsx2/GS/Renderers/Common/GSTexture.h index c913dd8cb3..a4fd38c808 100644 --- a/pcsx2/GS/Renderers/Common/GSTexture.h +++ b/pcsx2/GS/Renderers/Common/GSTexture.h @@ -73,6 +73,13 @@ 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 public: GSTexture(); virtual ~GSTexture(); @@ -87,6 +94,7 @@ public: #ifdef PCSX2_DEVBUILD virtual void SetDebugName(std::string_view name) = 0; + const std::string& GetDebugName() { return m_debug_name; } #endif bool Save(const std::string& fn); @@ -173,6 +181,12 @@ 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 UseUnorderedAccess() { 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/D3D11ShaderCache.cpp b/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp index 4c360529cd..c0a5dc4873 100644 --- a/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp +++ b/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp @@ -65,6 +65,7 @@ bool D3D11ShaderCache::Open(D3D_FEATURE_LEVEL feature_level, bool debug) m_shader_model = D3D::ShaderModel::SM41; break; case D3D_FEATURE_LEVEL_11_0: + case D3D_FEATURE_LEVEL_11_1: m_shader_model = D3D::ShaderModel::SM50; break; default: diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index a081a7ef87..e14106de40 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -109,7 +109,8 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) wil::com_ptr_nothrow dxgi_adapter = D3D::GetAdapterByName(m_dxgi_factory.get(), GSConfig.Adapter); - static constexpr std::array requested_feature_levels = {{ + static constexpr std::array requested_feature_levels = {{ + D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, }}; @@ -592,11 +593,18 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) Host::OSD_WARNING_DURATION); } + // 1x1 dummy texture. + m_null_texture = CreateSurface(GSTexture::Type::RenderTarget, 1, 1, 1, GSTexture::Format::Color); + if (!m_null_texture) + return false; + return true; } void GSDevice11::Destroy() { + delete m_null_texture; + GSDevice::Destroy(); DestroySwapChain(); DestroyTimestampQueries(); @@ -683,6 +691,12 @@ void GSDevice11::SetFeatures(IDXGIAdapter1* adapter) m_conservative_depth = (m_feature_level >= D3D_FEATURE_LEVEL_11_0); m_rgba16_unorm_hw_blend = IsTextureFormatHWBlendable(m_dev.get(), DXGI_FORMAT_R16G16B16A16_UNORM); + m_uav_texture = (m_feature_level >= D3D_FEATURE_LEVEL_11_0); + + D3D11_FEATURE_DATA_D3D11_OPTIONS2 options2{}; + m_dev->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS2, &options2, sizeof(options2)); + m_features.rov = m_uav_texture && options2.ROVsSupported; + // Let the user know if said features are available. Console.WriteLnFmt("D3D11: DXTn Texture Compression: {}", m_features.dxt_textures ? "Supported" : "Not Supported"); Console.WriteLnFmt("D3D11: BC6/7 Texture Compression: {}", m_features.bptc_textures ? "Supported" : "Not Supported"); @@ -1322,6 +1336,8 @@ GSTexture* GSDevice11::CreateSurface(GSTexture::Type type, int width, int height { case GSTexture::Type::RenderTarget: desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + if (m_uav_texture) + desc.BindFlags |= D3D11_BIND_UNORDERED_ACCESS; break; case GSTexture::Type::DepthStencil: desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE; @@ -1331,6 +1347,7 @@ GSTexture* GSDevice11::CreateSurface(GSTexture::Type type, int width, int height desc.MiscFlags = (levels > 1 && !GSTexture::IsCompressedFormat(format)) ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0; break; case GSTexture::Type::RWTexture: + pxAssert(m_uav_texture); desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE; break; default: @@ -1482,7 +1499,7 @@ void GSDevice11::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTextur // ps - PSSetShaderResource(0, sTex); + PSSetShaderResource(TEXTURE_TEXTURE, sTex); PSSetSamplerState(filter == Biln ? m_convert.ln.get() : m_convert.pt.get()); PSSetShader(ps, ps_cb); @@ -1547,7 +1564,7 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* // ps - PSSetShaderResource(0, sTex); + PSSetShaderResource(TEXTURE_TEXTURE, sTex); PSSetSamplerState(filter == Biln ? m_convert.ln.get() : m_convert.pt.get()); PSSetShader(m_present.ps[static_cast(shader)].get(), m_present.ps_cb.get()); @@ -1698,7 +1715,7 @@ void GSDevice11::DoMultiStretchRects(const MultiStretchRect* rects, u32 num_rect IASetIndexBuffer(m_ib.get()); CommitClear(rects[0].src); - PSSetShaderResource(0, rects[0].src); + PSSetShaderResource(TEXTURE_TEXTURE, rects[0].src); PSSetSamplerState(rects[0].filter == Biln ? m_convert.ln.get() : m_convert.pt.get()); OMSetBlendState(m_convert.bs[rects[0].wmask.wrgba].get(), 0.0f); @@ -1921,6 +1938,8 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant sm.AddMacro("PS_AA1", static_cast(sel.aa1)); sm.AddMacro("PS_ABE", sel.abe); sm.AddMacro("PS_ANISOTROPIC_FILTERING", sel.sw_aniso); + sm.AddMacro("PS_ROV_COLOR", sel.rov_color); + sm.AddMacro("PS_ROV_DEPTH", static_cast(sel.rov_depth)); wil::com_ptr_nothrow ps = m_shader_cache.GetPixelShader(m_dev.get(), m_tfx_source, sm.GetPtr(), "ps_main"); i = m_ps.try_emplace(sel, std::move(ps)).first; @@ -2339,7 +2358,7 @@ void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GSV // ps - PSSetShaderResource(0, rt); + PSSetShaderResource(TEXTURE_TEXTURE, rt); PSSetSamplerState(m_convert.pt.get()); PSSetShader(GetConvertShader(SetDATMShader(datm)), nullptr); @@ -2707,10 +2726,16 @@ void GSDevice11::OMSetBlendState(ID3D11BlendState* bs, u8 bf) } } -void GSDevice11::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i* scissor, ID3D11DepthStencilView* read_only_dsv) +void GSDevice11::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, GSTexture* rt_uav_tex, GSTexture* ds_uav_tex, + const GSVector4i* scissor, ID3D11DepthStencilView* read_only_dsv) { + if (!(rt || rt_uav_tex) && ds_uav_tex) + rt_uav_tex = m_null_texture; // Fill in the first UAV slot with the null texture. + ID3D11RenderTargetView* rtv = nullptr; ID3D11DepthStencilView* dsv = nullptr; + ID3D11UnorderedAccessView* rt_uav = nullptr; + ID3D11UnorderedAccessView* ds_uav = nullptr; if (rt) { @@ -2722,9 +2747,21 @@ void GSDevice11::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector CommitClear(ds); dsv = read_only_dsv ? read_only_dsv : *static_cast(ds); } + if (rt_uav_tex) + { + CommitClear(rt_uav_tex); + rt_uav = *static_cast(rt_uav_tex); + } + if (ds_uav_tex) + { + CommitClear(ds_uav_tex); + ds_uav = *static_cast(ds_uav_tex); + } - const bool changed = (m_state.rtv != rtv || m_state.dsv != dsv); - g_perfmon.Put(GSPerfMon::RenderPasses, static_cast(changed)); + const bool changed = (m_state.rtv != rtv || m_state.dsv != dsv || m_state.rt_uav != rt_uav || m_state.ds_uav != ds_uav); + + if (changed) + g_perfmon.Put(GSPerfMon::RenderPasses, 1.0); if (m_state.rtv != rtv) { @@ -2744,15 +2781,49 @@ void GSDevice11::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector m_state.dsv = dsv; m_state.current_ds = ds; } + if (m_state.rt_uav != rt_uav) + { + if (m_state.rt_uav) + m_state.rt_uav->Release(); + if (rt_uav) + rt_uav->AddRef(); + m_state.rt_uav = rt_uav; + m_state.current_rt_uav = rt_uav_tex; + } + if (m_state.ds_uav != ds_uav) + { + if (m_state.ds_uav) + m_state.ds_uav->Release(); + if (ds_uav) + ds_uav->AddRef(); + m_state.ds_uav = ds_uav; + m_state.current_ds_uav = ds_uav_tex; + } PSUnbindConflictingSRVs(m_state.current_rt, read_only_dsv ? nullptr : m_state.current_ds); + PSUnbindConflictingSRVs(m_state.current_rt_uav, m_state.current_ds_uav); if (changed) - m_ctx->OMSetRenderTargets(1, &rtv, dsv); - - if (rt || ds) { - const GSVector2i size = rt ? rt->GetSize() : ds->GetSize(); + // OM targets and UAVs share the same namespace in DX11, so we need to pack them into contiguous slots. + u32 num_rtvs = rt ? 1 : 0; + u32 num_uavs = 0; + ID3D11UnorderedAccessView* uavs[2]; + if (rt_uav) + uavs[num_uavs++] = rt_uav; + if (ds_uav) + uavs[num_uavs++] = ds_uav; + ID3D11RenderTargetView* rtvs[] = { rtv }; + m_ctx->OMSetRenderTargetsAndUnorderedAccessViews(num_rtvs, rtvs, dsv, num_rtvs, num_uavs, uavs, nullptr); + } + + if (rt || ds || rt_uav_tex || ds_uav_tex) + { + const GSVector2i size = + rt ? rt->GetSize() : + ds ? ds->GetSize() : + (rt_uav_tex && rt_uav_tex != m_null_texture) ? rt_uav_tex->GetSize() : + ds_uav_tex->GetSize(); SetViewport(size); SetScissor(scissor ? *scissor : GSVector4i::loadh(size)); } @@ -2815,6 +2886,10 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) { const GSVector2i rtsize = (config.rt ? config.rt : config.ds)->GetSize(); GSTexture* colclip_rt = g_gs_device->GetColorClipTexture(); + GSTexture* draw_rt = config.ps.HasColorROV() ? nullptr : config.rt; + GSTexture* draw_ds = config.ps.HasDepthROV() ? nullptr : config.ds; + GSTexture* draw_rt_rov = config.ps.HasColorROV() ? config.rt : nullptr; + GSTexture* draw_ds_rov = config.ps.HasDepthROV() ? config.ds : nullptr; GSTexture* draw_rt_clone = nullptr; GSTexture* draw_ds_clone = nullptr; GSTexture* primid_texture = nullptr; @@ -2865,6 +2940,8 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) const GSVector4 sRect = dRect / GSVector4(rtsize.x, rtsize.y).xyxy(); StretchRect(config.rt, sRect, colclip_rt, dRect, ShaderConvert::COLCLIP_INIT, Nearest); } + + draw_rt = colclip_rt ? colclip_rt : draw_rt; } // Destination Alpha Setup @@ -2950,7 +3027,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) const OMBlendSelector blend(GSHWDrawConfig::ColorMaskSelector(1), GSHWDrawConfig::BlendState(true, CONST_ONE, CONST_ONE, 3 /* MIN */, CONST_ONE, CONST_ZERO, false, 0)); SetupOM(dss, blend, 0); - OMSetRenderTargets(primid_texture, config.ds, &config.scissor, read_only_dsv); + OMSetRenderTargets(primid_texture, config.ds, nullptr, nullptr, &config.scissor, read_only_dsv); SetRenderHWShaderResources(config, nullptr); Draw(config); @@ -2960,16 +3037,14 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) } // Avoid changing framebuffer just to switch from rt+depth to rt and vice versa. - GSTexture* draw_rt = colclip_rt ? colclip_rt : config.rt; - GSTexture* draw_ds = config.ds; // Make sure no tex is bound as both rtv and srv at the same time. // All conflicts should've been taken care of by PSUnbindConflictingSRVs. - if (!draw_rt && draw_ds && m_state.rtv && m_state.current_rt && m_state.rtv == *static_cast(m_state.current_rt) && + if (!(draw_rt || draw_rt_rov) && draw_ds && m_state.rtv && m_state.current_rt && m_state.rtv == *static_cast(m_state.current_rt) && m_state.current_ds == draw_ds && config.tex != m_state.current_rt && m_state.current_rt->GetSize() == draw_ds->GetSize()) { draw_rt = m_state.current_rt; } - else if (!draw_ds && draw_rt && m_state.dsv && m_state.current_ds && m_state.dsv == *static_cast(m_state.current_ds) && + else if (!(draw_ds || draw_ds_rov) && draw_rt && m_state.dsv && m_state.current_ds && m_state.dsv == *static_cast(m_state.current_ds) && m_state.current_rt == draw_rt && config.tex != m_state.current_ds && m_state.current_ds->GetSize() == draw_rt->GetSize()) { draw_ds = m_state.current_ds; @@ -2977,7 +3052,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) const bool rt_feedbackloop_pass1 = config.IsFeedbackLoopRT(config.ps); const bool rt_feedbackloop_pass2 = config.IsFeedbackLoopRT(config.alpha_second_pass.ps); - if (draw_rt && (((config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) && + if (draw_rt && !draw_rt_rov && (((config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) && (rt_feedbackloop_pass1 || rt_feedbackloop_pass2)))) { // Requires a copy of the RT. @@ -2989,7 +3064,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) const bool ds_feedbackloop_pass1 = config.IsFeedbackLoopDepth(config.ps); const bool ds_feedbackloop_pass2 = config.IsFeedbackLoopDepth(config.alpha_second_pass.ps); - if (draw_ds && m_features.depth_feedback && (config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) && + if (draw_ds && !draw_ds_rov && m_features.depth_feedback && (config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) && (ds_feedbackloop_pass1 || ds_feedbackloop_pass2)) { // Requires a copy of the DS. @@ -2999,7 +3074,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) Console.Warning("D3D11: Failed to allocate temp texture for DS copy."); } - OMSetRenderTargets(draw_rt, draw_ds, &config.scissor, read_only_dsv); + OMSetRenderTargets(draw_rt, draw_ds, draw_rt_rov, draw_ds_rov, &config.scissor, read_only_dsv); SetRenderHWShaderResources(config, primid_texture); SetupOM(config.depth, OMBlendSelector(config.colormask, config.blend), config.blend.constant); @@ -3008,7 +3083,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) m_ctx->ClearDepthStencilView(*static_cast(draw_ds), D3D11_CLEAR_STENCIL, 0.0f, 1); SendHWDraw(config, rt_feedbackloop_pass1 ? draw_rt_clone : nullptr, draw_rt, ds_feedbackloop_pass1 ? draw_ds_clone : nullptr, draw_ds, - config.require_one_barrier, config.require_full_barrier); + config.require_one_barrier, config.require_full_barrier, rtsize); if (config.blend_multi_pass.enable) { @@ -3036,7 +3111,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) const bool one_barrier = config.alpha_second_pass.require_one_barrier && m_features.multidraw_fb_copy; SetupOM(config.alpha_second_pass.depth, OMBlendSelector(config.alpha_second_pass.colormask, config.blend), config.blend.constant); SendHWDraw(config, rt_feedbackloop_pass2 ? draw_rt_clone : nullptr, draw_rt, ds_feedbackloop_pass2 ? draw_ds_clone : nullptr, draw_ds, - one_barrier, config.alpha_second_pass.require_full_barrier); + one_barrier, config.alpha_second_pass.require_full_barrier, rtsize); } if (colclip_rt) @@ -3110,7 +3185,7 @@ void GSDevice11::FeedbackCopyAndBind(const GSHWDrawConfig& config, void GSDevice11::SendHWDraw(const GSHWDrawConfig& config, GSTexture* draw_rt_clone, GSTexture* draw_rt, GSTexture* draw_ds_clone, GSTexture* draw_ds, - const bool one_barrier, const bool full_barrier) + const bool one_barrier, const bool full_barrier, GSVector2i rtsize) { #ifdef PCSX2_DEVBUILD if ((one_barrier || full_barrier) && !(config.IsFeedbackLoopRT(config.ps) || config.IsFeedbackLoopDepth(config.ps))) [[unlikely]] @@ -3149,6 +3224,9 @@ void GSDevice11::SendHWDraw(const GSHWDrawConfig& config, } Draw(config); + + if (config.ps.HasColorROV() || config.ps.HasDepthROV()) + g_perfmon.Put(GSPerfMon::DrawCallsROV, 1); } void GSDevice11::SetRenderHWShaderResources(const GSHWDrawConfig& config, GSTexture* primid_texture) @@ -3156,13 +3234,13 @@ void GSDevice11::SetRenderHWShaderResources(const GSHWDrawConfig& config, GSText if (config.tex && config.tex_hazard == GSHWDrawConfig::TEX_HAZARD_NONE) { CommitClear(config.tex); - PSSetShaderResource(0, config.tex); + PSSetShaderResource(TEXTURE_TEXTURE, config.tex); } if (config.pal) { CommitClear(config.pal); - PSSetShaderResource(1, config.pal); + PSSetShaderResource(TEXTURE_PALETTE, config.pal); } if (primid_texture) - PSSetShaderResource(3, primid_texture); + PSSetShaderResource(TEXTURE_PRIMID, primid_texture); } diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.h b/pcsx2/GS/Renderers/DX11/GSDevice11.h index 5eb69195d2..8f828585e8 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.h +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.h @@ -83,6 +83,11 @@ public: private: enum : u32 { + TEXTURE_TEXTURE = 0, + TEXTURE_PALETTE = 1, + TEXTURE_RT = 2, + TEXTURE_PRIMID = 3, + TEXTURE_DEPTH = 4, MAX_TEXTURES = 5, MAX_SAMPLERS = 1, VERTEX_BUFFER_SIZE = 32 * 1024 * 1024, @@ -129,6 +134,8 @@ private: wil::com_ptr_nothrow m_expand_vb_srv; wil::com_ptr_nothrow m_expand_ib_vs_srv; + GSTexture* m_null_texture; + D3D_FEATURE_LEVEL m_feature_level = D3D_FEATURE_LEVEL_10_0; u32 m_vb_pos = 0; // bytes u32 m_ib_pos = 0; // indices/sizeof(u16) @@ -141,6 +148,7 @@ private: bool m_is_exclusive_fullscreen = false; bool m_conservative_depth = false; bool m_rgba16_unorm_hw_blend = false; + bool m_uav_texture = false; struct { @@ -166,8 +174,12 @@ private: u8 bf; ID3D11RenderTargetView* rtv; ID3D11DepthStencilView* dsv; + ID3D11UnorderedAccessView* rt_uav; + ID3D11UnorderedAccessView* ds_uav; GSTexture* current_rt; GSTexture* current_ds; + GSTexture* current_rt_uav; + GSTexture* current_ds_uav; } m_state; std::array, 3>, NUM_TIMESTAMP_QUERIES> m_timestamp_queries = {}; @@ -371,7 +383,8 @@ public: void OMSetDepthStencilState(ID3D11DepthStencilState* dss, u8 sref); void OMSetBlendState(ID3D11BlendState* bs, u8 bf); - void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i* scissor = nullptr, ID3D11DepthStencilView* read_only_dsv = nullptr); + void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, GSTexture* rt_uav = nullptr, GSTexture* ds_uav = nullptr, + const GSVector4i* scissor = nullptr, ID3D11DepthStencilView* read_only_dsv = nullptr); void SetViewport(const GSVector2i& viewport); void SetScissor(const GSVector4i& scissor); @@ -390,7 +403,7 @@ public: const GSVector4i& copyarea, const GSVector4i& samplearea); void SendHWDraw(const GSHWDrawConfig& config, GSTexture* draw_rt_clone, GSTexture* draw_rt, GSTexture* draw_ds_clone, GSTexture* draw_ds, - const bool one_barrier, const bool full_barrier); + const bool one_barrier, const bool full_barrier, GSVector2i rtsize); void SetRenderHWShaderResources(const GSHWDrawConfig& config, GSTexture* primid_texture); void ClearSamplerCache() override; diff --git a/pcsx2/GS/Renderers/DX11/GSTexture11.cpp b/pcsx2/GS/Renderers/DX11/GSTexture11.cpp index 95305cfd3c..5486f41350 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)); + + m_debug_name = name; } #endif diff --git a/pcsx2/GS/Renderers/DX11/GSTexture11.h b/pcsx2/GS/Renderers/DX11/GSTexture11.h index 06c38cac8e..6e50ccbe61 100644 --- a/pcsx2/GS/Renderers/DX11/GSTexture11.h +++ b/pcsx2/GS/Renderers/DX11/GSTexture11.h @@ -19,7 +19,6 @@ class GSTexture11 final : public GSTexture wil::com_ptr_nothrow m_uav; wil::com_ptr_nothrow m_read_only_dsv; D3D11_TEXTURE2D_DESC m_desc; - public: explicit GSTexture11(wil::com_ptr_nothrow texture, const D3D11_TEXTURE2D_DESC& desc, GSTexture::Type type, GSTexture::Format format); diff --git a/pcsx2/GS/Renderers/DX12/D3D12Builders.cpp b/pcsx2/GS/Renderers/DX12/D3D12Builders.cpp index 5c664a7187..4faf189eb1 100644 --- a/pcsx2/GS/Renderers/DX12/D3D12Builders.cpp +++ b/pcsx2/GS/Renderers/DX12/D3D12Builders.cpp @@ -354,6 +354,31 @@ u32 D3D12::RootSignatureBuilder::AddDescriptorTable( return index; } +// Allows using ranges of non-contiguous shader registers in a single descriptor table. +u32 D3D12::RootSignatureBuilder::AddDescriptorTableMultiRange(u32 num_ranges, + D3D12_DESCRIPTOR_RANGE_TYPE* rt, u32* start_shader_reg, u32* num_shader_regs, D3D12_SHADER_VISIBILITY visibility) +{ + const u32 index = m_desc.NumParameters++; + const u32 dr_index = m_num_descriptor_ranges; + m_num_descriptor_ranges += num_ranges; + + for (u32 i = 0; i < num_ranges; i++) + { + m_descriptor_ranges[dr_index + i].RangeType = rt[i]; + m_descriptor_ranges[dr_index + i].NumDescriptors = num_shader_regs[i]; + m_descriptor_ranges[dr_index + i].BaseShaderRegister = start_shader_reg[i]; + m_descriptor_ranges[dr_index + i].RegisterSpace = 0; + m_descriptor_ranges[dr_index + i].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + } + + m_params[index].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + m_params[index].DescriptorTable.pDescriptorRanges = &m_descriptor_ranges[dr_index]; + m_params[index].DescriptorTable.NumDescriptorRanges = num_ranges; + m_params[index].ShaderVisibility = visibility; + + return index; +} + #ifdef PCSX2_DEVBUILD #include "common/StringUtil.h" diff --git a/pcsx2/GS/Renderers/DX12/D3D12Builders.h b/pcsx2/GS/Renderers/DX12/D3D12Builders.h index ba6e2eb857..97e4634f90 100644 --- a/pcsx2/GS/Renderers/DX12/D3D12Builders.h +++ b/pcsx2/GS/Renderers/DX12/D3D12Builders.h @@ -37,6 +37,8 @@ namespace D3D12 u32 AddSRVParameter(u32 shader_reg, D3D12_SHADER_VISIBILITY visibility); u32 AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE rt, u32 start_shader_reg, u32 num_shader_regs, D3D12_SHADER_VISIBILITY visibility); + u32 AddDescriptorTableMultiRange(u32 num_ranges, D3D12_DESCRIPTOR_RANGE_TYPE* rt, u32* start_shader_reg, + u32* num_shader_regs, D3D12_SHADER_VISIBILITY visibility); private: D3D12_ROOT_SIGNATURE_DESC m_desc{}; diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index f4bd0cb22b..9b60194e10 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -397,17 +397,6 @@ bool GSDevice12::CreateDescriptorHeaps() return false; } - // Allocate null SRV descriptor for unbound textures. - constexpr D3D12_SHADER_RESOURCE_VIEW_DESC null_srv_desc = { - DXGI_FORMAT_R8G8B8A8_UNORM, D3D12_SRV_DIMENSION_TEXTURE2D, D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING}; - - if (!m_descriptor_heap_manager.Allocate(&m_null_srv_descriptor)) - { - pxFailRel("Failed to allocate null descriptor"); - return false; - } - - m_device->CreateShaderResourceView(nullptr, &null_srv_desc, m_null_srv_descriptor.cpu_handle); return true; } @@ -1449,6 +1438,10 @@ bool GSDevice12::CheckFeatures(const u32& vendor_id) m_enhanced_barriers = false; } + 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; + return true; } @@ -1505,30 +1498,30 @@ void GSDevice12::Draw(const GSHWDrawConfig& config) } void GSDevice12::LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_format, DXGI_FORMAT* srv_format, - DXGI_FORMAT* rtv_format, DXGI_FORMAT* dsv_format) const + DXGI_FORMAT* rtv_format, DXGI_FORMAT* dsv_format, DXGI_FORMAT* uav_format) const { - static constexpr std::array, static_cast(GSTexture::Format::Last) + 1> + static constexpr std::array, static_cast(GSTexture::Format::Last) + 1> s_format_mapping = {{ {DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // Invalid {DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, - DXGI_FORMAT_UNKNOWN}, // Color + DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8G8B8A8_UNORM}, // Color {DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_R10G10B10A2_UNORM, - DXGI_FORMAT_UNKNOWN}, // ColorHQ + DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R10G10B10A2_UNORM}, // ColorHQ {DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R16G16B16A16_FLOAT, - DXGI_FORMAT_UNKNOWN}, // ColorHDR + DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16G16B16A16_FLOAT}, // ColorHDR {DXGI_FORMAT_R16G16B16A16_UNORM, DXGI_FORMAT_R16G16B16A16_UNORM, DXGI_FORMAT_R16G16B16A16_UNORM, - DXGI_FORMAT_UNKNOWN}, // ColorClip + DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16G16B16A16_UNORM}, // ColorClip {DXGI_FORMAT_D32_FLOAT_S8X24_UINT, DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS, DXGI_FORMAT_UNKNOWN, - DXGI_FORMAT_D32_FLOAT_S8X24_UINT}, // DepthStencil - {DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_UNKNOWN}, // DepthColor - {DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_UNKNOWN}, // UNorm8 - {DXGI_FORMAT_R16_UINT, DXGI_FORMAT_R16_UINT, DXGI_FORMAT_R16_UINT, DXGI_FORMAT_UNKNOWN}, // UInt16 - {DXGI_FORMAT_R32_UINT, DXGI_FORMAT_R32_UINT, DXGI_FORMAT_R32_UINT, DXGI_FORMAT_UNKNOWN}, // UInt32 - {DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_UNKNOWN}, // Int32 - {DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // BC1 - {DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // BC2 - {DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // BC3 - {DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // BC7 + DXGI_FORMAT_D32_FLOAT_S8X24_UINT, DXGI_FORMAT_R32_FLOAT}, // DepthStencil + {DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R32_FLOAT}, // DepthColor + {DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_A8_UNORM}, // UNorm8 + {DXGI_FORMAT_R16_UINT, DXGI_FORMAT_R16_UINT, DXGI_FORMAT_R16_UINT, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16_UINT}, // UInt16 + {DXGI_FORMAT_R32_UINT, DXGI_FORMAT_R32_UINT, DXGI_FORMAT_R32_UINT, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R32_UINT}, // UInt32 + {DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R32_FLOAT}, // Int32 + {DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // BC1 + {DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // BC2 + {DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // BC3 + {DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // BC7 }}; const auto& mapping = s_format_mapping[static_cast(format)]; @@ -1540,14 +1533,17 @@ void GSDevice12::LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_f *rtv_format = mapping[2]; if (dsv_format) *dsv_format = mapping[3]; + if (uav_format) + *uav_format = mapping[4]; } GSTexture* GSDevice12::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) { - DXGI_FORMAT dxgi_format, srv_format, rtv_format, dsv_format; - LookupNativeFormat(format, &dxgi_format, &srv_format, &rtv_format, &dsv_format); + DXGI_FORMAT dxgi_format, srv_format, rtv_format, dsv_format, uav_format; + LookupNativeFormat(format, &dxgi_format, &srv_format, &rtv_format, &dsv_format, &uav_format); - const DXGI_FORMAT uav_format = (type == GSTexture::Type::RWTexture) ? dxgi_format : DXGI_FORMAT_UNKNOWN; + if (type != GSTexture::Type::RWTexture && type != GSTexture::Type::RenderTarget) + uav_format = DXGI_FORMAT_UNKNOWN; // We don't need the UAV descriptor. std::unique_ptr tex(GSTexture12::Create(type, format, width, height, levels, dxgi_format, srv_format, rtv_format, dsv_format, uav_format)); @@ -2446,24 +2442,23 @@ void GSDevice12::VSSetIndexBuffer(const void* index, size_t count) } void GSDevice12::OMSetRenderTargets(GSTexture* rt, GSTexture* ds_as_rt, GSTexture* ds, const GSVector4i& scissor, - bool depth_read) + bool depth_read, const GSVector2i& viewport_size) { GSTexture12* d12Rt = static_cast(rt); GSTexture12* d12DsRt = static_cast(ds_as_rt); GSTexture12* d12Ds = static_cast(ds); - pxAssert(d12Rt || d12DsRt || d12Ds); - if (m_current_render_target != d12Rt || m_current_depth_render_target != d12DsRt || m_current_depth_target != d12Ds || - m_current_depth_read_only != depth_read) + // Check if framebuffer changed + if (m_current_render_target != d12Rt || m_current_depth_render_target != d12DsRt || + m_current_depth_target != d12Ds || m_current_depth_read_only != depth_read) { - // framebuffer change EndRenderPass(); } else if (InRenderPass()) { // Framebuffer unchanged, but check for clears. Have to restart render pass, unlike Vulkan. // We'll take care of issuing the actual clear there, because we have to start one anyway. - for (GSTexture12* tex : std::array{d12Rt, d12DsRt, d12Ds}) + for (GSTexture12* tex : std::array{ d12Rt, d12DsRt, d12Ds }) { if (tex && tex->GetState() != GSTexture::State::Dirty) { @@ -2491,7 +2486,8 @@ void GSDevice12::OMSetRenderTargets(GSTexture* rt, GSTexture* ds_as_rt, GSTextur } // This is used to set/initialize the framebuffer for tfx rendering. - const GSVector2i size = d12Rt ? d12Rt->GetSize() : (d12DsRt ? d12DsRt->GetSize() : d12Ds->GetSize()); + const GSVector2i size = d12Rt ? d12Rt->GetSize() : + (d12DsRt ? d12DsRt->GetSize() : (d12Ds ? d12Ds->GetSize() : viewport_size)); const D3D12_VIEWPORT vp{0.0f, 0.0f, static_cast(size.x), static_cast(size.y), 0.0f, 1.0f}; SetViewport(vp); @@ -2608,8 +2604,8 @@ GSDevice12::ComPtr GSDevice12::GetUtilityPixelShader(const std::string bool GSDevice12::CreateNullTexture() { m_null_texture = - GSTexture12::Create(GSTexture::Type::Texture, GSTexture::Format::Color, 1, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM, - DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN); + GSTexture12::Create(GSTexture::Type::RenderTarget, 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); if (!m_null_texture) return false; @@ -2689,9 +2685,13 @@ bool GSDevice12::CreateRootSignatures() rsb.AddCBVParameter(1, D3D12_SHADER_VISIBILITY_PIXEL); rsb.AddSRVParameter(0, D3D12_SHADER_VISIBILITY_VERTEX); rsb.AddSRVParameter(5, D3D12_SHADER_VISIBILITY_VERTEX); - rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 2, D3D12_SHADER_VISIBILITY_PIXEL); // Source / Palette + rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 2, D3D12_SHADER_VISIBILITY_PIXEL); // Source (t0) / Palette (t2) rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, NUM_TFX_SAMPLERS, D3D12_SHADER_VISIBILITY_PIXEL); - rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 2, 3, D3D12_SHADER_VISIBILITY_PIXEL); // RT / PrimID / Depth + // RT (t2) / PrimID (t3) / Depth (t4) / RT UAV (u0) / Depth UAV (u1) + D3D12_DESCRIPTOR_RANGE_TYPE rt_types[2] = { D3D12_DESCRIPTOR_RANGE_TYPE_SRV, D3D12_DESCRIPTOR_RANGE_TYPE_UAV }; + u32 rt_start_regs[2] = { 2, 0 }; + u32 rt_num_regs[2] = { 3, 2 }; + rsb.AddDescriptorTableMultiRange(2, rt_types, rt_start_regs, rt_num_regs, D3D12_SHADER_VISIBILITY_PIXEL); rsb.Add32BitConstants(2, sizeof(m_vs_pc_cache) / sizeof(u32), D3D12_SHADER_VISIBILITY_VERTEX); if (!(m_tfx_root_signature = rsb.Create())) return false; @@ -2728,7 +2728,7 @@ bool GSDevice12::CompileConvertPipelines() GSTexture::Format format = shader.OutputFormat(); DXGI_FORMAT dxgi_format; - LookupNativeFormat(format, nullptr, nullptr, &dxgi_format, nullptr); + LookupNativeFormat(format, nullptr, nullptr, &dxgi_format, nullptr, nullptr); if (shader.DATMConvertShader() || shader.DepthOutput()) { @@ -3067,7 +3067,6 @@ void GSDevice12::DestroyResources() m_shader_cache.Close(); - m_descriptor_heap_manager.Free(&m_null_srv_descriptor); m_timestamp_query_buffer.reset(); m_timestamp_query_allocation.reset(); m_sampler_heap_manager.Destroy(); @@ -3178,6 +3177,8 @@ const ID3DBlob* GSDevice12::GetTFXPixelShader(const GSHWDrawConfig::PSSelector& sm.AddMacro("PS_AA1", static_cast(sel.aa1)); sm.AddMacro("PS_ABE", sel.abe); sm.AddMacro("PS_ANISOTROPIC_FILTERING", sel.sw_aniso); + sm.AddMacro("PS_ROV_COLOR", sel.rov_color); + sm.AddMacro("PS_ROV_DEPTH", static_cast(sel.rov_depth)); ComPtr ps(m_shader_cache.GetPixelShader(m_tfx_source, sm.GetPtr(), "ps_main")); it = m_tfx_pixel_shaders.emplace(sel, std::move(ps)).first; @@ -3216,11 +3217,11 @@ GSDevice12::ComPtr GSDevice12::CreateTFXPipeline(const Pipe if (p.rt) { const GSTexture::Format format = IsDATEModePrimIDInit(p.ps.date) ? - GSTexture::Format::PrimID : - (p.ps.colclip_hw ? GSTexture::Format::ColorClip : GSTexture::Format::Color); + GSTexture::Format::PrimID : + (p.ps.colclip_hw ? GSTexture::Format::ColorClip : GSTexture::Format::Color); DXGI_FORMAT native_format; - LookupNativeFormat(format, nullptr, nullptr, &native_format, nullptr); + LookupNativeFormat(format, nullptr, nullptr, &native_format, nullptr, nullptr); gpb.SetRenderTarget(num_rts++, native_format); } if (p.ds_as_rt) @@ -3335,8 +3336,10 @@ bool GSDevice12::BindDrawPipeline(const PipelineSelector& p) void GSDevice12::InitializeState() { - for (u32 i = 0; i < NUM_TOTAL_TFX_TEXTURES; i++) + for (u32 i = 0; i < TEXTURE_RT_UAV; i++) m_tfx_textures[i] = m_null_texture->GetSRVDescriptor(); + for (u32 i = TEXTURE_RT_UAV; i < NUM_TOTAL_TFX_TEXTURES; i++) + m_tfx_textures[i] = m_null_texture->GetUAVDescriptor(); m_tfx_sampler_sel = GSHWDrawConfig::SamplerSelector::Point().key; InvalidateCachedState(); @@ -3475,25 +3478,30 @@ void GSDevice12::SetStencilRef(u8 ref) m_dirty_flags |= DIRTY_FLAG_STENCIL_REF; } -void GSDevice12::PSSetShaderResource(int i, GSTexture* sr, bool check_state, bool feedback) +void GSDevice12::PSSetShaderResource(int i, GSTexture* sr, bool check_state, ResourceType type) { + pxAssert(i < NUM_TFX_TEXTURES + NUM_TFX_RT_TEXTURES + NUM_TFX_UAV_TEXTURES); + D3D12DescriptorHandle handle; if (sr) { + const GSTexture12::ResourceState state = GetResourceState(type); + GSTexture12* dtex = static_cast(sr); if (check_state) { - if (dtex->GetResourceState() != GSTexture12::ResourceState::PixelShaderResource && InRenderPass()) + if (dtex->GetResourceState() != state && InRenderPass()) { GL_INS("Ending render pass due to resource transition"); EndRenderPass(); } dtex->CommitClear(); - dtex->TransitionToState(GSTexture12::ResourceState::PixelShaderResource); + dtex->TransitionToState(state); } dtex->SetUseFenceCounter(GetCurrentFenceValue()); - handle = (feedback && !m_enhanced_barriers) ? dtex->GetFBLDescriptor() : dtex->GetSRVDescriptor(); + + handle = GetResourceDescriptor(dtex, type); } else { @@ -3504,7 +3512,7 @@ void GSDevice12::PSSetShaderResource(int i, GSTexture* sr, bool check_state, boo return; m_tfx_textures[i] = handle; - m_dirty_flags |= (i < 2) ? DIRTY_FLAG_TFX_TEXTURES : DIRTY_FLAG_TFX_RT_TEXTURES; + m_dirty_flags |= (i < NUM_TFX_TEXTURES) ? DIRTY_FLAG_TFX_TEXTURES : DIRTY_FLAG_TFX_RT_TEXTURES; } void GSDevice12::PSSetSampler(GSHWDrawConfig::SamplerSelector sel) @@ -3517,6 +3525,68 @@ 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) +{ + GSTexture12* d12Rt = static_cast(rt); + GSTexture12* d12Ds = static_cast(ds); + GSTexture12* oldD12Rt = m_tfx_textures_uav[0] != m_null_texture.get() ? m_tfx_textures_uav[0] : nullptr; + GSTexture12* oldD12Ds = m_tfx_textures_uav[1] != m_null_texture.get() ? m_tfx_textures_uav[1] : nullptr; + + if (!(d12Rt || d12Ds || oldD12Rt || oldD12Ds)) + return; + + pxAssert(!(d12Rt || d12Ds) || m_features.rov); + + if (d12Rt) + { + PSSetShaderResource(TEXTURE_RT_UAV, d12Rt, true, ResourceType::UAV); + + // Unbind conflicting RT texture + PSSetShaderResource(TEXTURE_RT, nullptr, false); + + // Unbind conflicting source texture + if (m_tfx_textures[TEXTURE_TEXTURE] == d12Rt->GetSRVDescriptor() || + m_tfx_textures[TEXTURE_TEXTURE] == d12Rt->GetFBLDescriptor()) + { + PSSetShaderResource(TEXTURE_TEXTURE, nullptr, false); + } + + if (write_rt) + { + d12Rt->SetState(GSTexture::State::Dirty); + } + } + else + { + // Unbind to avoid conflicts with OM targets. + PSSetShaderResource(TEXTURE_RT_UAV, nullptr, false); + } + + if (d12Ds) + { + PSSetShaderResource(TEXTURE_DEPTH_UAV, d12Ds, true, ResourceType::UAV); + + // Unbind conflicting depth texture + PSSetShaderResource(TEXTURE_DEPTH, nullptr, false); + + // Unbind conflicting source texture + if (m_tfx_textures[TEXTURE_TEXTURE] == d12Ds->GetSRVDescriptor()) + { + PSSetShaderResource(TEXTURE_TEXTURE, nullptr, false); + } + + if (write_ds) + { + d12Ds->SetState(GSTexture::State::Dirty); + } + } + else + { + // Unbind to avoid conflicts with OM targets. + PSSetShaderResource(TEXTURE_DEPTH_UAV, nullptr, false); + } +} + void GSDevice12::SetUtilityRootSignature() { if (m_current_root_signature == RootSignature::Utility) @@ -3578,7 +3648,8 @@ void GSDevice12::SetUtilityPushConstants(const void* data, u32 size) void GSDevice12::UnbindTexture(GSTexture12* tex) { - for (u32 i = 0; i < NUM_TOTAL_TFX_TEXTURES; i++) + // Source / palette + for (u32 i = 0; i <= TEXTURE_PALETTE; i++) { if (m_tfx_textures[i] == tex->GetSRVDescriptor() || m_tfx_textures[i] == tex->GetFBLDescriptor()) { @@ -3586,6 +3657,28 @@ void GSDevice12::UnbindTexture(GSTexture12* tex) m_dirty_flags |= DIRTY_FLAG_TFX_TEXTURES; } } + + // RT / primid / depth + for (u32 i = TEXTURE_RT; i <= TEXTURE_DEPTH; i++) + { + if (m_tfx_textures[i] == tex->GetSRVDescriptor() || m_tfx_textures[i] == tex->GetFBLDescriptor()) + { + m_tfx_textures[i] = m_null_texture->GetSRVDescriptor(); + m_dirty_flags |= DIRTY_FLAG_TFX_RT_TEXTURES; + } + } + + // RT UAV / depth UAV + for (u32 i = TEXTURE_RT_UAV; i <= TEXTURE_DEPTH_UAV; i++) + { + if (m_tfx_textures_uav[i - TEXTURE_RT_UAV] == tex) + { + m_tfx_textures_uav[i - TEXTURE_RT_UAV] = nullptr; + m_tfx_textures[i] = m_null_texture->GetUAVDescriptor(); + m_dirty_flags |= DIRTY_FLAG_TFX_RT_TEXTURES; + } + } + if (m_current_render_target == tex) { EndRenderPass(); @@ -3714,7 +3807,7 @@ void GSDevice12::BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE color_b if (color_begin == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR) { LookupNativeFormat(m_current_render_target->GetFormat(), nullptr, - &rt[num_rts].BeginningAccess.Clear.ClearValue.Format, nullptr, nullptr); + &rt[num_rts].BeginningAccess.Clear.ClearValue.Format, nullptr, nullptr, nullptr); GSVector4::store(rt[num_rts].BeginningAccess.Clear.ClearValue.Color, clear_color); } num_rts++; @@ -3737,7 +3830,7 @@ void GSDevice12::BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE color_b if (depth_begin == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR) { LookupNativeFormat(m_current_depth_target->GetFormat(), nullptr, nullptr, nullptr, - &ds.DepthBeginningAccess.Clear.ClearValue.Format); + &ds.DepthBeginningAccess.Clear.ClearValue.Format, nullptr); ds.DepthBeginningAccess.Clear.ClearValue.DepthStencil.Depth = clear_depth; } ds.StencilEndingAccess.Type = stencil_end; @@ -3745,7 +3838,7 @@ void GSDevice12::BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE color_b if (stencil_begin == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR) { LookupNativeFormat(m_current_depth_target->GetFormat(), nullptr, nullptr, nullptr, - &ds.StencilBeginningAccess.Clear.ClearValue.Format); + &ds.StencilBeginningAccess.Clear.ClearValue.Format, nullptr); ds.StencilBeginningAccess.Clear.ClearValue.DepthStencil.Stencil = clear_stencil; } } @@ -3847,6 +3940,10 @@ __ri void GSDevice12::ApplyBaseState(u32 flags, ID3D12GraphicsCommandList* cmdli { cmdlist->OMSetRenderTargets(0, nullptr, FALSE, m_current_depth_read_only ? &m_current_depth_target->GetReadDepthViewDescriptor().cpu_handle : &m_current_depth_target->GetWriteDescriptor().cpu_handle); } + else + { + cmdlist->OMSetRenderTargets(0, nullptr, FALSE, nullptr); + } } } @@ -3925,7 +4022,7 @@ bool GSDevice12::ApplyTFXState(bool already_execed) if (flags & DIRTY_FLAG_TFX_RT_TEXTURES) { - if (!GetTextureGroupDescriptors(&m_tfx_rt_textures_handle_gpu, m_tfx_textures.data() + 2, 3)) + if (!GetTextureGroupDescriptors(&m_tfx_rt_textures_handle_gpu, m_tfx_textures.data() + 2, NUM_TFX_RT_TEXTURES + NUM_TFX_UAV_TEXTURES)) { ExecuteCommandListAndRestartRenderPass(false, "Ran out of TFX RT descriptor descriptor groups"); return ApplyTFXState(true); @@ -4161,10 +4258,14 @@ void GSDevice12::FeedbackBarrier(const GSTexture12* texture) void GSDevice12::RenderHW(GSHWDrawConfig& config) { GSTexture12* colclip_rt = static_cast(g_gs_device->GetColorClipTexture()); - GSTexture12* draw_rt = static_cast(config.rt); - GSTexture12* draw_ds = static_cast(config.ds); + GSTexture12* draw_rt = config.ps.HasColorROV() ? nullptr : static_cast(config.rt); + GSTexture12* draw_ds = config.ps.HasDepthROV() ? nullptr : static_cast(config.ds); + GSTexture12* draw_rt_rov = config.ps.HasColorROV() ? static_cast(config.rt) : nullptr; + GSTexture12* draw_ds_rov = config.ps.HasDepthROV() ? static_cast(config.ds) : nullptr; GSTexture12* draw_rt_clone = nullptr; + const bool feedback = draw_rt && (config.require_one_barrier || (config.require_full_barrier && m_features.texture_barrier) || (config.tex && config.tex == config.rt)); + // Align the render area to 128x128, hopefully avoiding render pass restarts for small render area changes (e.g. Ratchet and Clank). const GSVector2i rtsize(config.rt ? config.rt->GetSize() : config.ds->GetSize()); @@ -4200,6 +4301,9 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) Recycle(colclip_rt); g_gs_device->SetColorClipTexture(nullptr); + + colclip_rt = nullptr; + draw_rt = config.ps.HasColorROV() ? nullptr : static_cast(config.rt); } else { @@ -4314,18 +4418,18 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) { // avoid restarting the render pass just to switch from rt+depth to rt and vice versa // keep the depth even if doing colclip hw draws, because the next draw will probably re-enable depth - if (!draw_rt && m_current_render_target && config.tex != m_current_render_target && - m_current_render_target->GetSize() == draw_ds->GetSize()) + if (!(draw_rt || draw_rt_rov) && m_current_render_target && config.tex != m_current_render_target && + draw_ds && m_current_render_target->GetSize() == draw_ds->GetSize()) { draw_rt = m_current_render_target; m_pipeline_selector.rt = true; } - else if (!draw_ds && m_current_depth_target && config.tex != m_current_depth_target && - m_current_depth_target->GetSize() == draw_rt->GetSize()) - { - draw_ds = m_current_depth_target; - m_pipeline_selector.ds = true; - } + } + else if (!(draw_ds || draw_ds_rov) && m_current_depth_target && config.tex != m_current_depth_target && + draw_rt && m_current_depth_target->GetSize() == draw_rt->GetSize()) + { + draw_ds = m_current_depth_target; + m_pipeline_selector.ds = true; } GSTexture12* draw_ds_as_rt = static_cast(m_ds_as_rt); @@ -4377,10 +4481,13 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) 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()); + // 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, - config.tex && (config.tex == config.ds || config.ps.IsFeedbackLoopDepth()) && !config.depth.zwe); + config.tex && (config.tex == draw_ds || config.ps.IsFeedbackLoopDepth()) && !config.depth.zwe && !config.ps.HasDepthROV(), + config.rt ? config.rt->GetSize() : config.ds->GetSize()); // DX12 equivalent of vkCmdClearAttachments for StencilOne if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne) @@ -4435,14 +4542,15 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) // Restore original scissor, not sure if needed since the render pass has already been started. But to be safe. OMSetRenderTargets(draw_rt, nullptr, draw_ds, config.scissor); } + // VB/IB upload, if we did DATE setup and it's not colclip hw this has already been done SetPrimitiveTopology(s_primitive_topology_mapping[static_cast(config.topology)]); if (!date_image || colclip_rt) UploadHWDrawVerticesAndIndices(config); // now we can do the actual draw - SendHWDraw(pipe, config, draw_rt, draw_ds_as_rt, feedback_rt, feedback_depth, - config.require_one_barrier, config.require_full_barrier); + SendHWDraw(pipe, config, draw_rt, draw_ds_as_rt, draw_rt_rov, draw_ds_rov, + feedback_rt, feedback_depth, config.require_one_barrier, config.require_full_barrier); // blend second pass if (config.blend_multi_pass.enable) @@ -4472,8 +4580,9 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) pipe.cms = config.alpha_second_pass.colormask; pipe.dss = config.alpha_second_pass.depth; pipe.bs = config.blend; - SendHWDraw(pipe, config, draw_rt, draw_ds_as_rt, feedback_rt, feedback_depth, - config.alpha_second_pass.require_one_barrier, config.alpha_second_pass.require_full_barrier); + SendHWDraw(pipe, config, draw_rt, draw_ds_as_rt, draw_rt_rov, draw_ds_rov, + feedback_rt, feedback_depth, config.alpha_second_pass.require_one_barrier, + config.alpha_second_pass.require_full_barrier); } if (date_image) @@ -4516,9 +4625,13 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) } void GSDevice12::SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig& config, GSTexture12* draw_rt, - GSTexture12* draw_ds, const bool feedback_rt, const bool feedback_depth, const bool one_barrier, - const bool full_barrier) + GSTexture12* draw_ds, GSTexture12* draw_rt_rov, GSTexture12* draw_ds_rov, + const bool feedback_rt, const bool feedback_depth, + const bool one_barrier, const bool full_barrier) { + // Should not be mixing ROVs with barriers. + pxAssert(!(draw_rt_rov || draw_ds_rov) || !(one_barrier || full_barrier)); + const int n_barriers = static_cast(feedback_rt) + static_cast(feedback_depth); if (!m_features.texture_barrier) [[unlikely]] @@ -4535,11 +4648,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, true); + PSSetShaderResource(2, draw_rt, false, ResourceType::FBL); if (config.tex_hazard == GSHWDrawConfig::TEX_HAZARD_RT) - PSSetShaderResource(0, draw_rt, false, true); + PSSetShaderResource(0, draw_rt, false, ResourceType::FBL); if ((one_barrier || full_barrier) && feedback_depth) - PSSetShaderResource(4, draw_ds, false, true); + PSSetShaderResource(4, draw_ds, false, ResourceType::FBL); if (full_barrier) { @@ -4580,6 +4693,9 @@ void GSDevice12::SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig& if (BindDrawPipeline(pipe)) Draw(config); + + if (config.ps.HasColorROV() || config.ps.HasDepthROV()) + g_perfmon.Put(GSPerfMon::DrawCallsROV, 1); } void GSDevice12::UpdateHWPipelineSelector(GSHWDrawConfig& config) @@ -4587,14 +4703,14 @@ void GSDevice12::UpdateHWPipelineSelector(GSHWDrawConfig& config) m_pipeline_selector.vs.key = config.vs.key; m_pipeline_selector.ps.key_hi = config.ps.key_hi; m_pipeline_selector.ps.key_lo = config.ps.key_lo; - m_pipeline_selector.dss.key = config.depth.key; - m_pipeline_selector.bs.key = config.blend.key; + m_pipeline_selector.dss.key = config.ps.HasDepthROV() ? GSHWDrawConfig::DepthStencilSelector::NoDepth().key : config.depth.key; + m_pipeline_selector.bs.key = config.ps.HasColorROV() ? GSHWDrawConfig::BlendState().key : config.blend.key; m_pipeline_selector.bs.constant = 0; // don't dupe states with different alpha values - m_pipeline_selector.cms.key = config.colormask.key; + m_pipeline_selector.cms.key = config.ps.HasColorROV() ? GSHWDrawConfig::ColorMaskSelector().key : config.colormask.key; m_pipeline_selector.topology = static_cast(config.topology); - m_pipeline_selector.rt = config.rt != nullptr; - m_pipeline_selector.ds = config.ds != nullptr; - m_pipeline_selector.ds_as_rt = m_ds_as_rt != nullptr; + m_pipeline_selector.rt = config.rt != nullptr && !config.ps.HasColorROV(); + m_pipeline_selector.ds = config.ds != nullptr && !config.ps.HasDepthROV(); + m_pipeline_selector.ds_as_rt = m_ds_as_rt != nullptr && !config.ps.HasDepthROV(); } void GSDevice12::UploadHWDrawVerticesAndIndices(GSHWDrawConfig& config) diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.h b/pcsx2/GS/Renderers/DX12/GSDevice12.h index e32ef2c519..0a65fd3e17 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.h +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.h @@ -56,6 +56,44 @@ public: D3D12_RESOURCE_DESC desc; }; + enum class ResourceType + { + SRV, // Shader resource view + FBL, // Feedback loop + UAV, // Unordered access + }; + + D3D12DescriptorHandle GetResourceDescriptor(GSTexture12* tex, ResourceType type) const + { + switch (type) + { + case ResourceType::SRV: + return tex->GetSRVDescriptor(); + case ResourceType::FBL: + return m_enhanced_barriers ? tex->GetSRVDescriptor() : tex->GetFBLDescriptor(); + case ResourceType::UAV: + return tex->GetUAVDescriptor(); + default: + pxFailRel("Impossible."); + return D3D12DescriptorHandle{ 0, 0 }; + } + } + + static constexpr GSTexture12::ResourceState GetResourceState(ResourceType type) + { + switch (type) + { + case ResourceType::SRV: + case ResourceType::FBL: + return GSTexture12::ResourceState::PixelShaderResource; + case ResourceType::UAV: + return GSTexture12::ResourceState::PixelShaderUAV; + default: + pxFailRel("Impossible."); + return static_cast(-1); + } + } + __fi IDXGIAdapter1* GetAdapter() const { return m_adapter.get(); } __fi ID3D12Device* GetDevice() const { return m_device.get(); } __fi ID3D12CommandQueue* GetCommandQueue() const { return m_command_queue.get(); } @@ -96,7 +134,6 @@ public: D3D12DescriptorHeapManager& GetRTVHeapManager() { return m_rtv_heap_manager; } D3D12DescriptorHeapManager& GetDSVHeapManager() { return m_dsv_heap_manager; } D3D12DescriptorHeapManager& GetSamplerHeapManager() { return m_sampler_heap_manager; } - const D3D12DescriptorHandle& GetNullSRVDescriptor() const { return m_null_srv_descriptor; } D3D12StreamBuffer& GetTextureStreamBuffer() { return m_texture_stream_buffer; } // Root signature access. @@ -198,7 +235,6 @@ private: D3D12DescriptorHeapManager m_rtv_heap_manager; D3D12DescriptorHeapManager m_dsv_heap_manager; D3D12DescriptorHeapManager m_sampler_heap_manager; - D3D12DescriptorHandle m_null_srv_descriptor; D3D_FEATURE_LEVEL m_feature_level = D3D_FEATURE_LEVEL_11_0; @@ -224,14 +260,13 @@ public: GSHWDrawConfig::VSSelector vs; GSHWDrawConfig::DepthStencilSelector dss; GSHWDrawConfig::ColorMaskSelector cms; - u8 pad; __fi bool operator==(const PipelineSelector& p) const { return BitEqual(*this, p); } __fi bool operator!=(const PipelineSelector& p) const { return !BitEqual(*this, p); } __fi PipelineSelector() { std::memset(this, 0, sizeof(*this)); } }; - static_assert(sizeof(PipelineSelector) == 24, "Pipeline selector is 24 bytes"); + static_assert(sizeof(PipelineSelector) == 32, "Pipeline selector is 32 bytes"); struct PipelineSelectorHash { @@ -282,11 +317,14 @@ public: TEXTURE_RT = 2, TEXTURE_PRIMID = 3, TEXTURE_DEPTH = 4, + TEXTURE_RT_UAV = 5, + TEXTURE_DEPTH_UAV = 6, NUM_TFX_CONSTANT_BUFFERS = 2, NUM_TFX_TEXTURES = 2, NUM_TFX_RT_TEXTURES = 3, - NUM_TOTAL_TFX_TEXTURES = NUM_TFX_TEXTURES + NUM_TFX_RT_TEXTURES, + NUM_TFX_UAV_TEXTURES = 2, + NUM_TOTAL_TFX_TEXTURES = NUM_TFX_TEXTURES + NUM_TFX_RT_TEXTURES + NUM_TFX_UAV_TEXTURES, NUM_TFX_SAMPLERS = 1, NUM_UTILITY_TEXTURES = 1, NUM_UTILITY_SAMPLERS = 1, @@ -385,7 +423,7 @@ private: std::string m_tfx_source; void LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_format, DXGI_FORMAT* srv_format, - DXGI_FORMAT* rtv_format, DXGI_FORMAT* dsv_format) const; + DXGI_FORMAT* rtv_format, DXGI_FORMAT* dsv_format, DXGI_FORMAT* uav_format) const; u32 GetSwapChainBufferCount() const; bool CreateSwapChain(); @@ -513,11 +551,12 @@ public: void IASetIndexBuffer(const void* index, size_t count); void VSSetIndexBuffer(const void* index, size_t count); - void PSSetShaderResource(int i, GSTexture* sr, bool check_state, bool feedback = false); + 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 OMSetRenderTargets(GSTexture* rt, GSTexture* ds, GSTexture* ds_as_rt, const GSVector4i& scissor, - bool depth_read = false); + bool depth_read = false, const GSVector2i& viewport_size = {}); void SetVSConstantBuffer(const GSHWDrawConfig::VSConstantBuffer& cb); void SetPSConstantBuffer(const GSHWDrawConfig::PSConstantBuffer& cb); @@ -526,8 +565,8 @@ public: void RenderHW(GSHWDrawConfig& config) override; void SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig& config, GSTexture12* draw_rt, - GSTexture12* draw_ds, const bool feedback_rt, const bool feedback_depth, const bool one_barrier, - const bool full_barrier); + GSTexture12* draw_ds, GSTexture12* draw_rt_rov, GSTexture12* draw_ds_rov, + const bool feedback_rt, const bool feedback_depth, const bool one_barrier, const bool full_barrier); void UpdateHWPipelineSelector(GSHWDrawConfig& config); void UploadHWDrawVerticesAndIndices(GSHWDrawConfig& config); @@ -593,8 +632,8 @@ private: DIRTY_FLAG_PS_CONSTANT_BUFFER_BINDING = (1 << 6), DIRTY_FLAG_VS_VERTEX_BUFFER_BINDING = (1 << 7), DIRTY_FLAG_VS_INDEX_BUFFER_BINDING = (1 << 8), - DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE = (1 << 9), - DIRTY_FLAG_SAMPLERS_DESCRIPTOR_TABLE = (1 << 10), + DIRTY_FLAG_SAMPLERS_DESCRIPTOR_TABLE = (1 << 9), + DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE = (1 << 10), DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE_2 = (1 << 11), DIRTY_FLAG_VS_PUSH_CONSTANTS = (1 << 12), @@ -657,6 +696,7 @@ private: std::array m_tfx_constant_buffers{}; std::array m_tfx_textures{}; + std::array m_tfx_textures_uav{}; D3D12DescriptorHandle m_tfx_sampler; u32 m_tfx_sampler_sel = 0; D3D12DescriptorHandle m_tfx_textures_handle_gpu; diff --git a/pcsx2/GS/Renderers/DX12/GSTexture12.cpp b/pcsx2/GS/Renderers/DX12/GSTexture12.cpp index 82573684a5..39b0be0d66 100644 --- a/pcsx2/GS/Renderers/DX12/GSTexture12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSTexture12.cpp @@ -110,6 +110,10 @@ void GSTexture12::Destroy(bool defer) } m_write_descriptor_type = WriteDescriptorType::None; + +#ifdef PCSX2_DEVBUILD + m_debug_name.clear(); +#endif } // For use with non-simultaneous textures only. @@ -203,6 +207,7 @@ std::unique_ptr GSTexture12::Create(Type type, Format format, int w desc.desc1.Flags = (levels > 1 && !IsCompressedFormat(format)) ? D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET : D3D12_RESOURCE_FLAG_NONE; state = ResourceState::CopyDst; + pxAssert(uav_format == DXGI_FORMAT_UNKNOWN); } break; @@ -217,6 +222,10 @@ std::unique_ptr GSTexture12::Create(Type type, Format format, int w desc.desc1.Layout = D3D12_TEXTURE_LAYOUT_64KB_UNDEFINED_SWIZZLE; optimized_clear_value.Format = rtv_format; state = ResourceState::RenderTarget; + if (uav_format != DXGI_FORMAT_UNKNOWN) + { + desc.desc1.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + } } break; @@ -227,6 +236,7 @@ std::unique_ptr GSTexture12::Create(Type type, Format format, int w desc.desc1.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; optimized_clear_value.Format = dsv_format; state = ResourceState::DepthWriteStencil; + pxAssert(uav_format == DXGI_FORMAT_UNKNOWN); } break; @@ -235,6 +245,8 @@ std::unique_ptr GSTexture12::Create(Type type, Format format, int w pxAssert(levels == 1); allocationDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED; state = ResourceState::PixelShaderResource; + pxAssert(uav_format != DXGI_FORMAT_UNKNOWN); + desc.desc1.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; } break; @@ -242,9 +254,6 @@ std::unique_ptr GSTexture12::Create(Type type, Format format, int w return {}; } - if (uav_format != DXGI_FORMAT_UNKNOWN) - desc.desc1.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; - wil::com_ptr_nothrow resource; wil::com_ptr_nothrow resource_fbl; wil::com_ptr_nothrow allocation; @@ -330,6 +339,12 @@ std::unique_ptr GSTexture12::Create(Type type, Format format, int w dev->GetDescriptorHeapManager().Free(&srv_descriptor); return {}; } + if (uav_format != DXGI_FORMAT_UNKNOWN && !CreateUAVDescriptor(resource.get(), uav_format, &uav_descriptor)) + { + dev->GetRTVHeapManager().Free(&write_descriptor); + dev->GetDescriptorHeapManager().Free(&srv_descriptor); + return {}; + } } break; @@ -350,26 +365,15 @@ std::unique_ptr GSTexture12::Create(Type type, Format format, int w } break; - default: - break; - } - - if (uav_format != DXGI_FORMAT_UNKNOWN && !CreateUAVDescriptor(resource.get(), dsv_format, &uav_descriptor)) - { - switch (write_descriptor_type) + case Type::RWTexture: { - case WriteDescriptorType::RTV: - dev->GetRTVHeapManager().Free(&write_descriptor); - break; - case WriteDescriptorType::DSV: - dev->GetDSVHeapManager().Free(&ro_dsv_descriptor); - dev->GetDSVHeapManager().Free(&write_descriptor); - break; - default: - break; + if (uav_format != DXGI_FORMAT_UNKNOWN && !CreateUAVDescriptor(resource.get(), uav_format, &uav_descriptor)) + { + dev->GetDescriptorHeapManager().Free(&srv_descriptor); + return {}; + } } - dev->GetDescriptorHeapManager().Free(&srv_descriptor); - return {}; + break; } // Feedback descriptor used with legacy barriers @@ -442,7 +446,7 @@ std::unique_ptr GSTexture12::Adopt(wil::com_ptr_nothrowResourceBarrier(num_barriers, barriers); } + + // Count as a UAV barrier if we transition to/from UAV. + if (IsRenderTargetOrDepthStencil() && + (before_state == ResourceState::PixelShaderUAV || after_state == ResourceState::PixelShaderUAV)) + { + g_perfmon.Put(GSPerfMon::Barriers, 1); + g_perfmon.Put(GSPerfMon::BarriersROV, 1); + } } void GSTexture12::CommitClear() diff --git a/pcsx2/GS/Renderers/DX12/GSTexture12.h b/pcsx2/GS/Renderers/DX12/GSTexture12.h index 14139931c7..ec47e305df 100644 --- a/pcsx2/GS/Renderers/DX12/GSTexture12.h +++ b/pcsx2/GS/Renderers/DX12/GSTexture12.h @@ -56,6 +56,8 @@ 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; } + void* GetNativeHandle() const override; bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0) override; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 85fcb82505..14616e26d6 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -6825,6 +6825,13 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, DATEOptio m_conf.ps.blend_d = 2; } + // Save in case needed for ROV setup. + m_optimized_blend.A = m_conf.ps.blend_a; + m_optimized_blend.B = m_conf.ps.blend_b; + m_optimized_blend.C = m_conf.ps.blend_c; + m_optimized_blend.D = m_conf.ps.blend_d; + m_optimized_blend.FIX = AFIX; + // TODO: blend_ad_alpha_masked, as well as other blend cases can be optimized on dx11/dx12/gl to use // blend multipass more which might be faster, vk likely won't benefit as barriers are already fast. @@ -7478,6 +7485,388 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, DATEOptio } } +// In certain cases using a ROV with depth or color will force the other one +// because of how color and depth interact via testing. +__fi void GSRendererHW::GetForcedROVUsage(bool& rov_color, bool& rov_depth) +{ + // Cannot force if both are already enabled or disabled. + if (rov_color == rov_depth) + return; + + // If depth and color have feedback and one uses ROV, the other must also. + // We currently don't have a way of using barriers in one and ROV in the other. + if ((m_conf.ps.IsFeedbackLoopRT() && rov_depth) || (m_conf.ps.IsFeedbackLoopDepth() && rov_color)) + { + GL_INS("ROV: Feedback compatibility forces color and depth ROV"); + rov_color = true; + rov_depth = true; + } + + // If we use color ROV with discard or the pixel shader writes to depth, + // we cannot use early depth stencil, so must use depth ROV with feedback. + // Same applies in reverse for depth ROV forcing color ROV with feedback. + const bool color_write = (m_conf.colormask.wrgba != 0); + const bool depth_test = m_cached_ctx.DepthRead(); + + // Separate flag for DATE since they are many methods and the interaction with ROV is not clear. + const bool date = m_cached_ctx.TEST.DATE; + + if (m_conf.ds && rov_color && (m_conf.ps.HasShaderDiscard() || m_conf.ps.HasDepthOutput() || date)) + { + GL_INS("ROV: Color ROV with shader discard/depth write forces depth ROV"); + rov_depth = true; + } + else if (m_conf.rt && color_write && rov_depth && (m_conf.ps.HasShaderDiscard() || depth_test || date)) + { + GL_INS("ROV: Depth ROV with shader discard forces color ROV"); + rov_color = true; + } +} + +void GSRendererHW::DetermineROVUsage(GSTextureCache::Target* rt, GSTextureCache::Target* ds) +{ + const GSDevice::FeatureSupport& features = g_gs_device->Features(); + + if (!(GSConfig.HWROV && features.rov)) + return; + + GL_PUSH("HW: ROV Setup"); + + if (features.framebuffer_fetch) + { + GL_INS("ROV: Disabled because have FB-fetch"); + return; + } + + if (rt && rt->m_texture == m_conf.tex && !m_conf.ps.tex_is_fb) + { + GL_INS("ROV: Disabled because tex is RT and not sampling from current pixel"); + return; + } + + if (ds && ds->m_texture == m_conf.tex) + { + GL_INS("ROV: Disabled because tex is depth"); + return; + } + + const bool color_write = rt && m_conf.colormask.wrgba != 0; + const bool depth_write = ds && m_cached_ctx.DepthWrite(); + + const u32 colormask = GSUtil::GetChannelMask(m_cached_ctx.FRAME.PSM) & m_conf.colormask.wrgba; + const bool colormask_needs_rt = colormask != 0xF; + + const u32 ate = m_cached_ctx.TEST.ATE; + const u32 atst = m_cached_ctx.TEST.ATST; + const u32 afail = m_cached_ctx.TEST.AFAIL; + + const bool afail_needs_rt = ate && ((afail == AFAIL_ZB_ONLY) || (afail == AFAIL_RGB_ONLY)); + const bool afail_needs_depth = ate && ((afail == AFAIL_FB_ONLY) || (afail == AFAIL_RGB_ONLY)); + + const bool blend = m_conf.IsBlending(); + const bool blend_needs_rt = blend && + (m_optimized_blend.A == ALPHA_ABD_CD || m_optimized_blend.B == ALPHA_ABD_CD || + m_optimized_blend.C == ALPHA_C_AD || m_optimized_blend.D == ALPHA_ABD_CD); + + const bool two_pass_alpha = GSHWDrawConfig::HasAlphaTestSecondPass(m_conf.alpha_test); + + const bool date = m_conf.destination_alpha != GSHWDrawConfig::DestinationAlphaMode::Off; + + const bool ztst = m_cached_ctx.DepthRead(); + + const bool full_barrier = m_conf.require_full_barrier; + + // Heuristically determine what ROVs would be needed to eliminate passes based on the current config. + const bool multipass_color = (full_barrier && m_conf.ps.IsFeedbackLoopRT()) || + two_pass_alpha || + m_conf.blend_multi_pass.enable; + + 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 depth_is_rov = ds && ds->m_texture->IsDepthColor(); + + bool use_rov_color = (color_write && multipass_color) || color_is_rov; + bool use_rov_depth = (depth_write && multipass_depth) || depth_is_rov; + + // In certain cases, ROV in color or depth will force ROV in the other for correctness. + GetForcedROVUsage(use_rov_color, use_rov_depth); + + // Get the number of barriers that would be used with the current config. + u32 barriers = 1; + if (full_barrier) + { + if (m_drawlist.size() > 0) + { + barriers = static_cast(m_drawlist.size()); // Already computed + } + else + { + barriers = 2; // Tells drawlist computation to stop after reaching 2. + GetPrimitiveOverlapDrawlist(false, false, 1.0f, &barriers); + } + } + + const u32 multiplier = m_conf.alpha_second_pass.enable ? 2 : 1; // Alpha second pass doubles the barriers. + barriers *= multiplier; + + // Heuristic: only activate ROV if we save at least one draw call by doing so. + const bool activate = (use_rov_color != color_is_rov || use_rov_depth != depth_is_rov) && barriers >= 2; + + if (!color_is_rov && !depth_is_rov && !activate) + { + // Not enough barriers or no feedback to activate, and ROV is not already active. + GL_ROV("No ROV usage: Draw=%05lld | C=%016p | D=%016p | BAR=%d.", + s_n, rt ? rt->m_texture : nullptr, ds ? ds->m_texture : nullptr, barriers); + return; + } + + if (activate) + { + GL_ROV("ROV activated: Draw=%05lld | C=%016p | D=%016p | BAR=%d | C=[%d=>%d] | D=[%d=>%d].", + s_n, rt ? rt->m_texture : nullptr, ds ? ds->m_texture : nullptr, barriers, + color_is_rov, use_rov_color, depth_is_rov, use_rov_depth); + } + else + { + GL_ROV("ROV continued: Draw=%05lld | C=%016p | D=%016p | BAR=%d | C=%d | D=%d.", + s_n, rt ? rt->m_texture : nullptr, ds ? ds->m_texture : nullptr, barriers, + color_is_rov, depth_is_rov); + } + + GL_INS("ROV: Color ROV %s / depth ROV %s", + use_rov_color ? "enabled" : "disabled", use_rov_depth ? "enabled" : "disabled"); + + // Set unordered access flag for backends that don't explicitly track resource state (DX11/GL). + // Used only for heuristic above. Not used for depth, since we check for depth color instead. + if (use_rov_color) + rt->m_texture->UseUnorderedAccess(); + + // Do the actual pipeline config. + ConfigureROV(use_rov_color, use_rov_depth); +} + +void GSRendererHW::ConfigureROV(bool color_rov, bool depth_rov) +{ + // Do the actual config for depth. + if (depth_rov) + { + m_conf.depth = GSHWDrawConfig::DepthStencilSelector::NoDepth(); // Disable real depth. + const bool depth_write = m_cached_ctx.DepthWrite(); + GL_INS("ROV: Using %s depth ROV", depth_write ? "read/write" : "read-only"); + ConfigureDepthFeedback(true); + m_conf.ps.rov_depth = depth_write ? GSHWDrawConfig::PS_ROV_DEPTH::READ_WRITE : GSHWDrawConfig::PS_ROV_DEPTH::READ_ONLY; + } + + // Do the actual config for color. + if (color_rov) + { + // FbMask setup + if (m_conf.colormask.wrgba != 0) + { + const GSVector4i fbmask = GSVector4i(m_conf.colormask.wr ? 0 : 0xFF, m_conf.colormask.wg ? 0 : 0xFF, + m_conf.colormask.wb ? 0 : 0xFF, m_conf.colormask.wa ? 0 : 0xFF); + if (!m_conf.ps.fbmask) + { + // Don't enable FB mask emulation, just use the mask for ROV. + m_conf.cb_ps.FbMask = fbmask; + } + else + { + m_conf.cb_ps.FbMask |= fbmask; + } + 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 + { + m_conf.ps.no_color = true; + } + + // Blend setup + if (m_conf.IsBlending()) + { + GL_INS("ROV: Using SW blend%s", m_conf.blend.enable ? " and disabling HW" : ""); + m_conf.ps.blend_a = m_optimized_blend.A; + m_conf.ps.blend_b = m_optimized_blend.B; + m_conf.ps.blend_c = m_optimized_blend.C; + m_conf.ps.blend_d = m_optimized_blend.D; + + if (m_conf.ps.blend_c == ALPHA_C_FIX) + m_conf.cb_ps.TA_MaxDepth_Af.a = m_optimized_blend.FIX / 128.0f; + + // Disable HW or mixed blend or multipass blend + m_conf.blend = {}; + m_conf.ps.blend_hw = false; + m_conf.ps.blend_mix = false; + m_conf.blend_multi_pass = {}; + + if (!m_conf.ps.no_color1) + { + // We should never need dual source with SW blend + GL_INS("ROV: Disabling dual source blending"); + m_conf.ps.no_color1 = true; + } + + // Only needed with HW blend. + m_conf.ps.round_inv = false; + m_conf.ps.a_masked = false; + } + + // Dither setup + if (m_conf.ps.dither) + { + // Only needed with HW blend. + m_conf.ps.dither_adjust = false; + } + + // Destination alpha test setup + if (m_conf.destination_alpha != GSHWDrawConfig::DestinationAlphaMode::Off) + { + GL_INS("ROV: Using DATE Full%s", + (m_conf.destination_alpha != GSHWDrawConfig::DestinationAlphaMode::Full) ? " and replace current method" : ""); + + if (m_conf.destination_alpha != GSHWDrawConfig::DestinationAlphaMode::Full) + { + m_conf.destination_alpha = GSHWDrawConfig::DestinationAlphaMode::Full; + m_conf.depth.date = false; // Don't use stencil with ROV + m_conf.depth.date_one = false; // Don't use stencil with ROV + m_conf.ps.date = 5 + m_cached_ctx.TEST.DATM; // Shader discard DATM. + m_conf.datm = static_cast(0); // Not needed + } + } + + // Colclip setup + if (m_conf.ps.colclip_hw) + { + // Remove HW colclip texture if needed + GL_INS("ROV: Replacing colclip HW with SW"); + const bool has_colclip_texture = g_gs_device->GetColorClipTexture() != nullptr; + m_conf.ps.colclip_hw = 0; + m_conf.ps.colclip = true; + m_conf.colclip_mode = has_colclip_texture ? GSHWDrawConfig::ColClipMode::EarlyResolve : GSHWDrawConfig::ColClipMode::NoModify; + } + + // PABE setup + const bool PABE = m_draw_env->PABE.PABE && GetAlphaMinMax().min < 128; + if (m_conf.IsBlending() && PABE && !m_conf.ps.pabe) + { + GL_INS("ROV: Enabling PABE"); + m_conf.ps.pabe = true; + } + + // Alpha test setup. KEEP will already be fine. + if (m_cached_ctx.TEST.ATE && m_conf.alpha_test != GSHWDrawConfig::AlphaTestMode::KEEP) + { + GL_INS("ROV: Using SW feedback alpha test%s", m_conf.alpha_second_pass.enable ? + " and disabling alpha second pass" : ""); + + m_conf.alpha_test = GSHWDrawConfig::AlphaTestMode::FEEDBACK; + + GSHWDrawConfig::PS_ATST ps_atst; + float ps_aref; + GetAlphaTestConfigPS(m_cached_ctx.TEST.ATST, m_cached_ctx.TEST.AREF, false, ps_atst, ps_aref); + m_conf.ps.atst = ps_atst; + m_conf.ps.afail = static_cast(m_cached_ctx.TEST.AFAIL); + if (m_cached_ctx.DepthWrite() && m_cached_ctx.TEST.AFAIL == AFAIL_RGB_ONLY) + { + pxAssert(depth_rov); // Should have enabled depth ROV for depth feedback loop. + m_conf.ps.afail = PS_AFAIL::RGB_ONLY_SW_Z; + } + m_conf.cb_ps.FogColor_AREF.a = ps_aref; + + GL_INS("ROV: Using ATST=%d, AFAIL=%d, AREF=%.2f", ps_atst, static_cast(m_conf.ps.afail), ps_aref); + + if (m_conf.alpha_second_pass.enable) + { + m_conf.alpha_second_pass = {}; + } + } + + m_conf.ps.rov_color = true; + } + + // Remove regular barriers. + if (color_rov || depth_rov) + { + m_conf.require_full_barrier = false; + m_conf.require_one_barrier = false; + } +} + +static void CopyDepthTextureROV(GSTexture* src, GSTexture* dst) +{ + switch (src->GetState()) + { + case GSTexture::State::Cleared: + g_gs_device->ClearDepth(dst, src->GetClearDepth()); + break; + case GSTexture::State::Invalidated: + g_gs_device->InvalidateRenderTarget(dst); + break; + case GSTexture::State::Dirty: + g_gs_device->StretchRectAuto(src, dst, Nearest); + 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->CreateDepthColor(ds_tex_old->GetSize(), 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()); +#endif + + // Fix up the texture cache. + if (ds->m_texture == ds_tex_old) + { + GL_CACHE("HW: Replaced texture for DS @ 0x%04x", ds->m_TEX0.TBP0); + ds->m_texture = ds_tex_new; + } + else + { + // Must be the temporary Z. + pxAssert(g_texture_cache->GetTemporaryZ() == ds_tex_old); + GL_CACHE("HW: Replaced texture for temporary Z @ 0x%04x", g_texture_cache->GetTemporaryZInfo().ZBP); + g_texture_cache->SetTemporaryZ(ds_tex_new); + } + + g_gs_device->Recycle(ds_tex_old); + + m_conf.ds = ds_tex_new; +} + __ri static constexpr bool IsRedundantClamp(u8 clamp, u32 clamp_min, u32 clamp_max, u32 tsize) { // Don't shader sample when the clamp/repeat is configured to the texture size. @@ -8780,14 +9169,14 @@ void GSRendererHW::EmulateAlphaTestSecondPass() } // Setup barriers and/or SW depth testing for depth feedback. -void GSRendererHW::ConfigureDepthFeedback() +void GSRendererHW::ConfigureDepthFeedback(bool rov_depth) { - if (m_conf.ps.IsFeedbackLoopDepth()) + if (m_conf.ps.IsFeedbackLoopDepth() || rov_depth) { const GSDevice::FeatureSupport& features = g_gs_device->Features(); - // We need barriers for the feedback. - if (features.feedback_loops()) + // We need barriers for the feedback (except with ROV). + if (features.feedback_loops() && !rov_depth) { m_conf.require_one_barrier |= (m_prim_overlap == PRIM_OVERLAP_NO); m_conf.require_full_barrier |= (m_prim_overlap != PRIM_OVERLAP_NO); @@ -9037,6 +9426,10 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta m_conf.ps.rta_correction = rt->m_rt_alpha_scale; } + // Call before computing the full drawlist in case ROV is used and we don't need it. + DetermineROVUsage(rt, ds); + ConvertDepthFormatROV(ds); + // Barriers must be determined before indices are modified via HandleProvokingVertexFirst/SetupIA. DetermineBarriers(rt, tex); @@ -9065,7 +9458,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta SetupIA(rtscale, vs_scale_x, vs_scale_y, m_channel_shuffle_width != 0, no_rt); - if (m_conf.ds && m_conf.ps.IsFeedbackLoopDepth() && !g_gs_device->Features().depth_feedback) + if (m_conf.ds && m_conf.ps.IsFeedbackLoopDepth() && !g_gs_device->Features().depth_feedback && !m_conf.ps.HasDepthROV()) { GL_PUSH("HW: Creating temporary R32 RT for depth feedback"); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index 5c7fc86b27..a33bc32028 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -236,7 +236,7 @@ private: static void GetAlphaTestConfigPS(const u32 atst, const u8 aref, const bool invert_test, PS_ATST& ps_atst_out, float& aref_out); void EmulateAlphaTest(DATEOptions& date_options); void EmulateAlphaTestSecondPass(); - void ConfigureDepthFeedback(); + void ConfigureDepthFeedback(bool rov_depth = false); void CalculateAlphaRange(GSTextureCache::Target* rt, GSTextureCache::Target* ds, DATEOptions& date_options, int& blend_alpha_min, int& blend_alpha_max, int& rt_new_alpha_min, int& rt_new_alpha_max); @@ -253,6 +253,11 @@ private: const GSVector2i& unscaled_size, float& vs_scale_x, float& vs_scale_y); void DetermineBarriers(GSTextureCache::Target* rt, GSTextureCache::Source* tex); + 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 ConvertDepthFormatROV(GSTextureCache::Target* ds); // Convert between depth and depth color if needed. + void SetTCOffset(); bool NextDrawColClip() const; bool IsPossibleChannelShuffle() const; @@ -330,6 +335,8 @@ private: GSVector2i m_lod = {}; // Min & Max level of detail + GIFRegALPHA m_optimized_blend = {}; // Save for ROV setup + GSHWDrawConfig m_conf = {}; HWCachedCtx m_cached_ctx; diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h index 2664dbee28..2b71f6abc2 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h @@ -101,7 +101,7 @@ struct PipelineSelectorMTL } }; -static_assert(sizeof(PipelineSelectorMTL) == 24); +static_assert(sizeof(PipelineSelectorMTL) == 28); template <> struct std::hash diff --git a/pcsx2/GS/Renderers/OpenGL/GLState.cpp b/pcsx2/GS/Renderers/OpenGL/GLState.cpp index 5e869d9f47..9686f3a111 100644 --- a/pcsx2/GS/Renderers/OpenGL/GLState.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GLState.cpp @@ -38,14 +38,21 @@ namespace GLState GSTextureOGL* ds_as_rt = nullptr; GSTextureOGL* ds = nullptr; + GSTextureOGL* rt_uav = nullptr; + GSTextureOGL* ds_uav = nullptr; + bool rt_written; bool ds_as_rt_written; bool ds_written; + bool rt_uav_written; + bool ds_uav_written; + u32 draw_buffers; GLuint tex_unit[8]; - GLuint64 tex_handle[8]; + + GLuint image_unit[8]; u32 UpdateDrawBuffers() { @@ -83,13 +90,20 @@ namespace GLState ds_as_rt = nullptr; ds = nullptr; + rt_uav = nullptr; + ds_uav = nullptr; + rt_written = false; ds_as_rt_written = false; ds_written = false; + rt_uav_written = false; + ds_uav_written = false; + draw_buffers = 0; std::fill(std::begin(tex_unit), std::end(tex_unit), 0); - std::fill(std::begin(tex_handle), std::end(tex_handle), 0); + + std::fill(std::begin(image_unit), std::end(image_unit), 0); } } // namespace GLState diff --git a/pcsx2/GS/Renderers/OpenGL/GLState.h b/pcsx2/GS/Renderers/OpenGL/GLState.h index 04854d8a5e..61038aadb6 100644 --- a/pcsx2/GS/Renderers/OpenGL/GLState.h +++ b/pcsx2/GS/Renderers/OpenGL/GLState.h @@ -42,14 +42,21 @@ namespace GLState extern GSTextureOGL* ds_as_rt; // Depth-Stencil as color extern GSTextureOGL* ds; // Depth-Stencil + extern GSTextureOGL* rt_uav; // Color UAV (storage image) + extern GSTextureOGL* ds_uav; // Depth UAV (storage image) + extern u32 draw_buffers; // Number of color attachments to framebuffer. extern bool rt_written; // Render Target written extern bool ds_as_rt_written; // Depth Stencil as RT written extern bool ds_written; // Depth Stencil written + extern bool rt_uav_written; // RT UAV written + extern bool ds_uav_written; // Depth UAV written + extern GLuint tex_unit[8]; // shader input texture - extern GLuint64 tex_handle[8]; // shader input texture + + extern GLuint image_unit[8]; // shader UAV texture extern u32 UpdateDrawBuffers(); extern void Clear(); diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index 050eaaeebf..1bf3161cc6 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -650,7 +650,7 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) // Basic to ensure structures are correctly packed static_assert(sizeof(VSSelector) == 1, "Wrong VSSelector size"); - static_assert(sizeof(PSSelector) == 12, "Wrong PSSelector size"); + static_assert(sizeof(PSSelector) == 16, "Wrong PSSelector size"); static_assert(sizeof(PSSamplerSelector) == 1, "Wrong PSSamplerSelector size"); static_assert(sizeof(OMDepthStencilSelector) == 1, "Wrong OMDepthStencilSelector size"); static_assert(sizeof(OMColorMaskSelector) == 1, "Wrong OMColorMaskSelector size"); @@ -901,6 +901,23 @@ bool GSDeviceOGL::CheckFeatures() m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand && m_features.feedback_loops(); + m_features.rov = (GLAD_GL_ARB_fragment_shader_interlock || GLAD_GL_NV_fragment_shader_interlock || + GLAD_GL_INTEL_fragment_shader_ordering) && GLAD_GL_ARB_shader_image_load_store && GLAD_GL_VERSION_4_5; + + m_features.rov &= !m_features.framebuffer_fetch; // Prefer FB fetch over ROV. + + m_features.rov &= GSConfig.HWROV; + +#if PCSX2_DEVBUILD + // Force enable even without FSI extensions (works for debugging on Mesa). + m_features.rov = GSConfig.HWROV && GLAD_GL_ARB_shader_image_load_store && GLAD_GL_VERSION_4_5; +#endif + + if (GSConfig.HWROV && !m_features.rov) + { + Console.Warning("GL: ROV is disabled because but the needed extensions are not present or framebuffer fetch is being used."); + } + return true; } @@ -1332,8 +1349,9 @@ void GSDeviceOGL::CommitClear(GSTexture* t, bool use_write_fbo) if (use_write_fbo) { glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, - (t->GetType() == GSTexture::Type::RenderTarget) ? GL_COLOR_ATTACHMENT0 : - (m_features.framebuffer_fetch ? GL_DEPTH_ATTACHMENT : GL_DEPTH_STENCIL_ATTACHMENT), + (t->GetType() == GSTexture::Type::RenderTarget) ? + GL_COLOR_ATTACHMENT0 : + (m_features.framebuffer_fetch ? GL_DEPTH_ATTACHMENT : GL_DEPTH_STENCIL_ATTACHMENT), GL_TEXTURE_2D, 0, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLState::fbo); } @@ -1439,9 +1457,14 @@ std::string GSDeviceOGL::GenGlslHeader(const std::string_view entry, GLenum type { std::string header; - // Intel's GL driver doesn't like the readonly qualifier with 3.3 GLSL. - if (m_features.vs_expand && GLAD_GL_VERSION_4_3) + if (m_features.rov) { + // FSI extensions need 4.5 GLSL + header = "#version 450 core\n"; + } + else if (m_features.vs_expand && GLAD_GL_VERSION_4_3) + { + // Intel's GL driver doesn't like the readonly qualifier with 3.3 GLSL. header = "#version 430 core\n"; } else @@ -1501,6 +1524,38 @@ std::string GSDeviceOGL::GenGlslHeader(const std::string_view entry, GLenum type pxAssert(0); } + if (m_features.rov && type == GL_FRAGMENT_SHADER) + { + header += "#extension GL_ARB_shader_image_load_store : require\n"; + if (GLAD_GL_ARB_fragment_shader_interlock) + { + header += "#extension GL_ARB_fragment_shader_interlock : require\n"; + header += "#define USE_ARB_FSI 1\n"; + header += "#define USE_NV_FSI 0\n"; + header += "#define USE_INTEL_FSI 0\n"; + } + else if (GLAD_GL_NV_fragment_shader_interlock) + { + header += "#extension GL_NV_fragment_shader_interlock : require\n"; + header += "#define USE_ARB_FSI 0\n"; + header += "#define USE_NV_FSI 1\n"; + header += "#define USE_INTEL_FSI 0\n"; + } + else if (GLAD_GL_INTEL_fragment_shader_ordering) + { + header += "#extension GL_INTEL_fragment_shader_ordering : require\n"; + header += "#define USE_ARB_FSI 0\n"; + header += "#define USE_NV_FSI 0\n"; + header += "#define USE_INTEL_FSI 1\n"; + } + } + else + { + header += "#define USE_ARB_FSI 0\n"; + header += "#define USE_NV_FSI 0\n"; + header += "#define USE_INTEL_FSI 0\n"; + } + // Don't remove this, the recursive macro breaks some Intel drivers. if (entry != "main") { @@ -1522,7 +1577,7 @@ std::string GSDeviceOGL::GetVSSource(VSSelector sel) std::string macro = fmt::format("#define VS_FST {}\n", static_cast(sel.fst)) + fmt::format("#define VS_IIP {}\n", static_cast(sel.iip)) + fmt::format("#define VS_POINT_SIZE {}\n", static_cast(sel.point_size)) - + fmt::format("#define VS_EXPAND {}\n", static_cast(sel.expand)); + + fmt::format("#define VS_EXPAND {}\n", static_cast(sel.expand)); std::string src = GenGlslHeader("vs_main", GL_VERTEX_SHADER, macro); src += m_shader_tfx_vgs; @@ -1592,7 +1647,9 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel) + fmt::format("#define PS_ZTST {}\n", sel.ztst) + fmt::format("#define PS_AA1 {}\n", static_cast(sel.aa1)) + fmt::format("#define PS_ABE {}\n", sel.abe) - + fmt::format("#define PS_ANISOTROPIC_FILTERING {}", sel.sw_aniso) + + fmt::format("#define PS_ANISOTROPIC_FILTERING {}\n", sel.sw_aniso) + + fmt::format("#define PS_ROV_COLOR {}\n", sel.rov_color) + + fmt::format("#define PS_ROV_DEPTH {}\n", static_cast(sel.rov_depth)) ; std::string src = GenGlslHeader("ps_main", GL_FRAGMENT_SHADER, macro); @@ -1619,7 +1676,7 @@ void GSDeviceOGL::BlitRect(GSTexture* sTex, const GSVector4i& r, const GSVector2 OMSetDepthStencilState(m_convert.dss); OMSetBlendState(); OMSetColorMaskState(); - PSSetShaderResource(0, sTex); + PSSetShaderResource(TEXTURE_TEXTURE, sTex); PSSetSamplerState(filter == Biln ? m_convert.ln : m_convert.pt); DrawStretchRect(float_r / (GSVector4(sTex->GetSize()).xyxy()), float_r, dsize); @@ -1722,7 +1779,7 @@ void GSDeviceOGL::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTextu // Texture // ************************************ - PSSetShaderResource(0, sTex); + PSSetShaderResource(TEXTURE_TEXTURE, sTex); PSSetSamplerState(filter == Biln ? m_convert.ln : m_convert.pt); // ************************************ @@ -1757,7 +1814,7 @@ void GSDeviceOGL::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture OMSetBlendState(false); OMSetColorMaskState(); - PSSetShaderResource(0, sTex); + PSSetShaderResource(TEXTURE_TEXTURE, sTex); PSSetSamplerState(filter == Biln ? m_convert.ln : m_convert.pt); // Flip y axis only when we render in the backbuffer @@ -1784,7 +1841,7 @@ void GSDeviceOGL::UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, OMSetColorMaskState(); OMSetRenderTargets(dTex, nullptr, nullptr); - PSSetShaderResource(0, sTex); + PSSetShaderResource(TEXTURE_TEXTURE, sTex); PSSetSamplerState(m_convert.pt); const GSVector4 dRect(0, 0, dSize, 1); @@ -1808,7 +1865,7 @@ void GSDeviceOGL::ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 off OMSetColorMaskState(); OMSetRenderTargets(dTex, nullptr, nullptr); - PSSetShaderResource(0, sTex); + PSSetShaderResource(TEXTURE_TEXTURE, sTex); PSSetSamplerState(m_convert.pt); const GSVector4 dRect(0, 0, dTex->GetWidth(), dTex->GetHeight()); @@ -1832,7 +1889,7 @@ void GSDeviceOGL::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u3 OMSetColorMaskState(); OMSetRenderTargets(dTex, nullptr, nullptr); - PSSetShaderResource(0, sTex); + PSSetShaderResource(TEXTURE_TEXTURE, sTex); PSSetSamplerState(m_convert.pt); //const GSVector4 dRect = GSVector4(dTex->GetRect()); @@ -1958,7 +2015,7 @@ void GSDeviceOGL::DoMultiStretchRects(const MultiStretchRect* rects, u32 num_rec m_vertex_stream_buffer->Unmap(vcount * sizeof(GSVertexPT1)); m_index_stream_buffer->Unmap(icount * sizeof(u16)); - PSSetShaderResource(0, rects[0].src); + PSSetShaderResource(TEXTURE_TEXTURE, rects[0].src); PSSetSamplerState(rects[0].filter == Biln ? m_convert.ln : m_convert.pt); OMSetColorMaskState(rects[0].wmask); DrawIndexedPrimitive(); @@ -2149,7 +2206,7 @@ void GSDeviceOGL::SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GS // Texture - PSSetShaderResource(0, rt); + PSSetShaderResource(TEXTURE_TEXTURE, rt); PSSetSamplerState(m_convert.pt); DrawPrimitive(); @@ -2233,6 +2290,45 @@ void GSDeviceOGL::IASetPrimitiveTopology(GLenum topology) m_draw_topology = topology; } +void GSDeviceOGL::PSBindImage(GLuint image_unit, GSTexture* tex) +{ + GLuint id = tex ? static_cast(tex)->GetID() : 0; + if (GLState::image_unit[image_unit] != id) + { + GLState::image_unit[image_unit] = id; + glBindImageTexture(image_unit, id, 0, GL_FALSE, 0, GL_READ_WRITE, + tex ? static_cast(tex)->GetGLFormat() : GL_R32F); + } +} + +void GSDeviceOGL::PSSetUnorderedAccess(GSTexture* rt, GSTexture* ds) +{ + const bool rt_changed = (rt != GLState::rt_uav); + const bool ds_changed = (ds != GLState::ds_uav); + + GLState::rt_uav_written = false; + GLState::ds_uav_written = false; + + PSBindImage(IMAGE_RT_ROV, rt); + if (rt) + { + CommitClear(rt, false); + GLState::rt_uav_written = rt_changed; + + PSSetShaderResource(TEXTURE_RT, nullptr); // Unbind to avoid conflicts. + } + + PSBindImage(IMAGE_DEPTH_ROV, ds); + if (ds) + { + pxAssert(ds->IsDepthColor()); + CommitClear(ds, false); + GLState::ds_uav_written = ds_changed; + + PSSetShaderResource(TEXTURE_DEPTH, nullptr); // Unbind to avoid conflicts. + } +} + void GSDeviceOGL::PSSetShaderResource(int i, GSTexture* sr) { pxAssert(i < static_cast(std::size(GLState::tex_unit))); @@ -2308,7 +2404,7 @@ bool GSDeviceOGL::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, con prog.Uniform4uiv(1, &constants[4]); prog.Uniform2iv(2, reinterpret_cast(&constants[8])); - PSSetShaderResource(0, sTex); + PSSetShaderResource(TEXTURE_TEXTURE, sTex); glBindImageTexture(0, static_cast(dTex)->GetID(), 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); static const int threadGroupWorkRegionDim = 16; @@ -2524,6 +2620,10 @@ void GSDeviceOGL::OMUnbindTexture(GSTextureOGL* tex) OMAttachDsAsRt(); if (GLState::ds == tex) OMAttachDs(); + if (GLState::rt_uav == tex) + PSBindImage(IMAGE_RT_ROV, nullptr); + if (GLState::ds_uav == tex) + PSBindImage(IMAGE_DEPTH_ROV, nullptr); } void GSDeviceOGL::OMSetBlendState(bool enable, GLenum src_factor, GLenum dst_factor, GLenum op, @@ -2570,7 +2670,8 @@ void GSDeviceOGL::OMSetBlendState(bool enable, GLenum src_factor, GLenum dst_fac } } -void GSDeviceOGL::OMSetRenderTargets(GSTexture* rt, GSTexture* ds_as_rt, GSTexture* ds, const GSVector4i* scissor) +void GSDeviceOGL::OMSetRenderTargets(GSTexture* rt, GSTexture* ds_as_rt, GSTexture* ds, const GSVector4i* scissor, + const GSVector2i* viewport) { const bool rt_changed = (rt != GLState::rt); const bool ds_as_rt_changed = (ds_as_rt != GLState::ds_as_rt); @@ -2613,12 +2714,9 @@ void GSDeviceOGL::OMSetRenderTargets(GSTexture* rt, GSTexture* ds_as_rt, GSTextu else OMAttachDs(); - if (rt || ds_as_rt || ds) - { - const GSVector2i size = rt ? rt->GetSize() : (ds_as_rt ? ds_as_rt->GetSize() : ds->GetSize()); - SetViewport(size); - SetScissor(scissor ? *scissor : GSVector4i::loadh(size)); - } + const GSVector2i size = rt ? rt->GetSize() : (ds_as_rt ? ds_as_rt->GetSize() : (ds ? ds->GetSize() : *viewport)); + SetViewport(size); + SetScissor(scissor ? *scissor : GSVector4i::loadh(size)); if (draw_buffers != GLState::UpdateDrawBuffers()) { @@ -2626,6 +2724,13 @@ void GSDeviceOGL::OMSetRenderTargets(GSTexture* rt, GSTexture* ds_as_rt, GSTextu static constexpr GLenum target[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; glDrawBuffers(GLState::draw_buffers, target); } + + if (!(rt || ds_as_rt || ds)) + { + // Need to set dimensions to avoid an incomplete FBO if we're doing a pure ROV draw. + glNamedFramebufferParameteri(m_fbo, GL_FRAMEBUFFER_DEFAULT_WIDTH, size.x); + glNamedFramebufferParameteri(m_fbo, GL_FRAMEBUFFER_DEFAULT_HEIGHT, size.y); + } } void GSDeviceOGL::SetViewport(const GSVector2i& viewport) @@ -2706,6 +2811,11 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) const GSVector2i rtsize = (config.rt ? config.rt : config.ds)->GetSize(); GSTexture* colclip_rt = g_gs_device->GetColorClipTexture(); + GSTexture* draw_rt = config.ps.HasColorROV() ? nullptr : config.rt; + GSTexture* draw_ds = config.ps.HasDepthROV() ? nullptr : config.ds; + GSTexture* draw_ds_as_rt = config.ps.HasDepthROV() ? nullptr : m_ds_as_rt; + GSTexture* draw_rt_rov = config.ps.HasColorROV() ? config.rt : nullptr; + GSTexture* draw_ds_rov = config.ps.HasDepthROV() ? config.ds : nullptr; GSTexture* draw_rt_clone = nullptr; GSTexture* draw_ds_clone = nullptr; GSTexture* primid_texture = nullptr; @@ -2762,6 +2872,8 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) const GSVector4 sRect = dRect / GSVector4(rtsize.x, rtsize.y).xyxy(); StretchRect(config.rt, sRect, colclip_rt, dRect, ShaderConvert::COLCLIP_INIT, Nearest); } + + draw_rt = colclip_rt ? colclip_rt : config.rt; } // Destination Alpha Setup @@ -2820,13 +2932,13 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) IASetPrimitiveTopology(topology); if (config.tex && (m_features.texture_barrier || config.tex_hazard == GSHWDrawConfig::TEX_HAZARD_NONE)) - PSSetShaderResource(0, config.tex); + PSSetShaderResource(TEXTURE_TEXTURE, config.tex); if (config.pal) - PSSetShaderResource(1, config.pal); + PSSetShaderResource(TEXTURE_PALETTE, config.pal); if (m_features.texture_barrier && (config.require_one_barrier || config.require_full_barrier)) - PSSetShaderResource(2, colclip_rt ? colclip_rt : config.rt); + PSSetShaderResource(TEXTURE_RT, colclip_rt ? colclip_rt : config.rt); if (m_features.texture_barrier && (config.require_one_barrier || config.require_full_barrier) && config.ps.IsFeedbackLoopDepth()) - PSSetShaderResource(4, m_features.depth_feedback ? config.ds : m_ds_as_rt); + PSSetShaderResource(TEXTURE_DEPTH, m_features.depth_feedback ? config.ds : m_ds_as_rt); SetupSampler(config.sampler); @@ -2896,10 +3008,10 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) psel.ps.date = 3; config.alpha_second_pass.ps.date = 3; SetupPipeline(psel); - PSSetShaderResource(3, primid_texture); + PSSetShaderResource(TEXTURE_PRIMID, primid_texture); } - if (m_ds_as_rt) + if (draw_ds_as_rt) { // We must clear the blend equation of any dual source blending factors or // it may interact badly with MRTs, even if blending is disabled. @@ -2918,25 +3030,21 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) OMSetBlendState(); } - GSTexture* draw_rt = colclip_rt ? colclip_rt : config.rt; - GSTexture* draw_ds_as_rt = m_ds_as_rt; - GSTexture* draw_ds = config.ds; - // Clear texture binding when it's bound to RT or DS. if (!config.tex && ((draw_rt && static_cast(draw_rt)->GetID() == GLState::tex_unit[0]) || (draw_ds && static_cast(draw_ds)->GetID() == GLState::tex_unit[0]))) - PSSetShaderResource(0, nullptr); + PSSetShaderResource(TEXTURE_TEXTURE, nullptr); // Avoid changing framebuffer just to switch from rt+depth to rt and vice versa. bool fb_optimization_needs_barrier = false; - if (!draw_rt && GLState::rt && GLState::ds == draw_ds && config.tex != GLState::rt && - GLState::rt->GetSize() == draw_ds->GetSize() && !draw_ds_as_rt) + if (!(draw_rt || draw_rt_rov) && GLState::rt && GLState::ds == draw_ds && config.tex != GLState::rt && + draw_ds && GLState::rt->GetSize() == draw_ds->GetSize() && !draw_ds_as_rt) { draw_rt = GLState::rt; fb_optimization_needs_barrier = !GLState::rt_written; } - else if (!draw_ds && GLState::ds && GLState::rt == draw_rt && config.tex != GLState::ds && - GLState::ds->GetSize() == draw_rt->GetSize() && !draw_ds_as_rt) + else if (!(draw_ds || draw_ds_rov) && GLState::ds && GLState::rt == draw_rt && config.tex != GLState::ds && + draw_rt && GLState::ds->GetSize() == draw_rt->GetSize() && !draw_ds_as_rt) { draw_ds = GLState::ds; fb_optimization_needs_barrier = !GLState::ds_written; @@ -2953,7 +3061,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) const bool rt_feedbackloop_pass1 = config.IsFeedbackLoopRT(config.ps); const bool rt_feedbackloop_pass2 = config.IsFeedbackLoopRT(config.alpha_second_pass.ps); - if (draw_rt && !m_features.texture_barrier && (((config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) && + if (draw_rt && !draw_rt_rov && !m_features.texture_barrier && (((config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) && (rt_feedbackloop_pass1 || rt_feedbackloop_pass2)))) { // Requires a copy of the RT. @@ -2965,7 +3073,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) const bool ds_feedbackloop_pass1 = config.ps.IsFeedbackLoopDepth(); const bool ds_feedbackloop_pass2 = config.alpha_second_pass.ps.IsFeedbackLoopDepth(); - if (draw_ds && !m_features.texture_barrier && m_features.depth_feedback && + if (draw_ds && !draw_ds_rov && !m_features.texture_barrier && m_features.depth_feedback && (config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) && (ds_feedbackloop_pass1 || ds_feedbackloop_pass2)) { // Requires a copy of the DS. @@ -2975,7 +3083,9 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) Console.Warning("GL: Failed to allocate temp texture for DS copy."); } - OMSetRenderTargets(draw_rt, draw_ds_as_rt, draw_ds, &config.scissor); + PSSetUnorderedAccess(draw_rt_rov, draw_ds_rov); + + OMSetRenderTargets(draw_rt, draw_ds_as_rt, draw_ds, &config.scissor, &rtsize); OMSetColorMaskState(config.colormask); SetupOM(config.depth); @@ -2993,7 +3103,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) } SendHWDraw(config, rt_feedbackloop_pass1 ? draw_rt_clone : nullptr, draw_rt, ds_feedbackloop_pass1 ? draw_ds_clone : nullptr, draw_ds, - config.require_one_barrier, config.require_full_barrier); + config.require_one_barrier, config.require_full_barrier, rtsize); if (config.blend_multi_pass.enable) { @@ -3041,7 +3151,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) const bool one_barrier = config.alpha_second_pass.require_one_barrier && m_features.feedback_loops(); SetupOM(config.alpha_second_pass.depth); SendHWDraw(config, rt_feedbackloop_pass2 ? draw_rt_clone : nullptr, draw_rt, ds_feedbackloop_pass2 ? draw_ds_clone : nullptr, draw_ds, - one_barrier, config.alpha_second_pass.require_full_barrier); + one_barrier, config.alpha_second_pass.require_full_barrier, rtsize); } if (colclip_rt) @@ -3114,7 +3224,7 @@ void GSDeviceOGL::FeedbackCopyAndBind(const GSHWDrawConfig& config, void GSDeviceOGL::SendHWDraw(const GSHWDrawConfig& config, GSTexture* draw_rt_clone, GSTexture* draw_rt, GSTexture* draw_ds_clone, GSTexture* draw_ds, - const bool one_barrier, const bool full_barrier) + const bool one_barrier, const bool full_barrier, const GSVector2i& rtsize) { #ifdef PCSX2_DEVBUILD if ((one_barrier || full_barrier) && !(config.IsFeedbackLoopRT(config.ps) || config.IsFeedbackLoopDepth(config.ps))) [[unlikely]] @@ -3171,6 +3281,9 @@ void GSDeviceOGL::SendHWDraw(const GSHWDrawConfig& config, } Draw(config); + + if (config.ps.HasColorROV() || config.ps.HasDepthROV()) + g_perfmon.Put(GSPerfMon::DrawCallsROV, 1); } // Note: used as a callback of DebugMessageCallback. Don't change the signature diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h index bd69c57d9d..9b91ba7fcd 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h @@ -115,6 +115,21 @@ public: using OMDepthStencilSelector = GSHWDrawConfig::DepthStencilSelector; using OMColorMaskSelector = GSHWDrawConfig::ColorMaskSelector; + enum TextureUnit : u32 + { + TEXTURE_TEXTURE, + TEXTURE_PALETTE, + TEXTURE_RT, + TEXTURE_PRIMID, + TEXTURE_DEPTH, + }; + + enum ImageUnit : u32 + { + IMAGE_RT_ROV, + IMAGE_DEPTH_ROV, + }; + struct alignas(16) ProgramSelector { PSSelector ps; @@ -124,7 +139,7 @@ public: __fi bool operator==(const ProgramSelector& p) const { return BitEqual(*this, p); } __fi bool operator!=(const ProgramSelector& p) const { return !BitEqual(*this, p); } }; - static_assert(sizeof(ProgramSelector) == 16, "Program selector is 16 bytes"); + static_assert(sizeof(ProgramSelector) == 32, "Program selector is 32 bytes"); struct ProgramSelectorHash { @@ -284,6 +299,8 @@ private: void OMAttachDs(GSTexture* ds = nullptr); void OMSetFBO(GLuint fbo); + void PSBindImage(GLuint image_unit, GSTexture* img); + void DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds); void SetIndexBuffer(std::unique_ptr& buffer, const void* index, size_t count); @@ -369,7 +386,7 @@ public: const GSVector4i& copyarea, const GSVector4i& samplearea); void SendHWDraw(const GSHWDrawConfig& config, GSTexture* draw_rt_clone, GSTexture* draw_rt, GSTexture* draw_ds_clone, GSTexture* draw_ds, - const bool one_barrier, const bool full_barrier); + const bool one_barrier, const bool full_barrier, const GSVector2i& rtsize); void SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GSVector4i& bbox); void VSSetUniformBuffer(GSHWDrawConfig::VSConstantBuffer& cb); @@ -382,6 +399,7 @@ public: void IASetIndexBuffer(const void* index, size_t count); void VSSetIndexBuffer(const void* index, size_t count); + void PSSetUnorderedAccess(GSTexture* rt, GSTexture* ds); void PSSetShaderResource(int i, GSTexture* sr); void PSSetSamplerState(GLuint ss); void ClearSamplerCache() override; @@ -389,7 +407,8 @@ public: void OMSetDepthStencilState(GSDepthStencilOGL* dss); void OMSetBlendState(bool enable = false, GLenum src_factor = GL_ONE, GLenum dst_factor = GL_ZERO, GLenum op = GL_FUNC_ADD, GLenum src_factor_alpha = GL_ONE, GLenum dst_factor_alpha = GL_ZERO, bool is_constant = false, u8 constant = 0); - void OMSetRenderTargets(GSTexture* rt, GSTexture* ds_as_rt, GSTexture* ds, const GSVector4i* scissor = nullptr); + void OMSetRenderTargets(GSTexture* rt, GSTexture* ds_as_rt, GSTexture* ds, const GSVector4i* scissor = nullptr, + const GSVector2i* viewport = nullptr); void OMSetColorMaskState(OMColorMaskSelector sel = OMColorMaskSelector()); void OMUnbindTexture(GSTextureOGL* tex); diff --git a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp index 4b83c1df6f..2d08504c3f 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp @@ -30,34 +30,33 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format m_type = type; m_texture_id = 0; m_mipmap_levels = 1; - int gl_fmt = 0; // Bunch of constant parameter switch (m_format) { // 1 Channel integer case Format::PrimID: - gl_fmt = GL_R32F; + m_gl_format = GL_R32F; m_int_format = GL_RED; m_int_type = GL_INT; m_int_shift = 2; break; case Format::UInt32: - gl_fmt = GL_R32UI; + m_gl_format = GL_R32UI; m_int_format = GL_RED_INTEGER; m_int_type = GL_UNSIGNED_INT; m_int_shift = 2; break; case Format::UInt16: - gl_fmt = GL_R16UI; + m_gl_format = GL_R16UI; m_int_format = GL_RED_INTEGER; m_int_type = GL_UNSIGNED_SHORT; m_int_shift = 1; break; - // 1 Channel normalized + // 1 Channel normalized case Format::UNorm8: - gl_fmt = GL_R8; + m_gl_format = GL_R8; m_int_format = GL_RED; m_int_type = GL_UNSIGNED_BYTE; m_int_shift = 0; @@ -65,7 +64,7 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format // 1 channel float case Format::DepthColor: - gl_fmt = GL_R32F; + m_gl_format = GL_R32F; m_int_format = GL_RED; m_int_type = GL_FLOAT; m_int_shift = 2; @@ -75,7 +74,7 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format case Format::Color: case Format::ColorHQ: case Format::ColorHDR: - gl_fmt = GL_RGBA8; + m_gl_format = GL_RGBA8; m_int_format = GL_RGBA; m_int_type = GL_UNSIGNED_BYTE; m_int_shift = 2; @@ -83,7 +82,7 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format // 4 channel float case Format::ColorClip: - gl_fmt = GL_RGBA16; + m_gl_format = GL_RGBA16; m_int_format = GL_RGBA; m_int_type = GL_UNSIGNED_SHORT; m_int_shift = 3; @@ -94,14 +93,14 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format { if (!g_gs_device->Features().framebuffer_fetch) { - gl_fmt = GL_DEPTH32F_STENCIL8; + m_gl_format = GL_DEPTH32F_STENCIL8; m_int_format = GL_DEPTH_STENCIL; m_int_type = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; m_int_shift = 3; // 4 bytes for depth + 4 bytes for stencil by texels } else { - gl_fmt = GL_DEPTH_COMPONENT32F; + m_gl_format = GL_DEPTH_COMPONENT32F; m_int_format = GL_DEPTH_COMPONENT; m_int_type = GL_FLOAT; m_int_shift = 2; @@ -110,28 +109,28 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format break; case Format::BC1: - gl_fmt = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + m_gl_format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; m_int_format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; m_int_type = GL_UNSIGNED_BYTE; m_int_shift = 1; break; case Format::BC2: - gl_fmt = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + m_gl_format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; m_int_format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; m_int_type = GL_UNSIGNED_BYTE; m_int_shift = 1; break; case Format::BC3: - gl_fmt = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + m_gl_format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; m_int_format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; m_int_type = GL_UNSIGNED_BYTE; m_int_shift = 1; break; case Format::BC7: - gl_fmt = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + m_gl_format = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; m_int_format = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; m_int_type = GL_UNSIGNED_BYTE; m_int_shift = 1; @@ -157,7 +156,7 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format glTextureParameteri(m_texture_id, GL_TEXTURE_SWIZZLE_A, GL_RED); } - glTextureStorage2D(m_texture_id, m_mipmap_levels, gl_fmt, m_size.x, m_size.y); + glTextureStorage2D(m_texture_id, m_mipmap_levels, m_gl_format, m_size.x, m_size.y); } GSTextureOGL::~GSTextureOGL() @@ -335,6 +334,8 @@ void GSTextureOGL::SetDebugName(std::string_view name) if (glObjectLabel) glObjectLabel(GL_TEXTURE, m_texture_id, static_cast(name.length()), static_cast(name.data())); + + m_debug_name = name; } #endif diff --git a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.h b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.h index 0fa79d0166..41a72399ba 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.h +++ b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.h @@ -22,6 +22,7 @@ private: u32 m_map_offset = 0; // internal opengl format/type/alignment + GLenum m_gl_format = 0; GLenum m_int_format = 0; GLenum m_int_type = 0; u32 m_int_shift = 0; @@ -33,6 +34,7 @@ public: __fi GLenum GetIntFormat() const { return m_int_format; } __fi GLenum GetIntType() const { return m_int_type; } __fi u32 GetIntShift() const { return m_int_shift; } + __fi u32 GetGLFormat() const { return m_gl_format; } void* GetNativeHandle() const override; @@ -55,6 +57,7 @@ public: } __fi u32 GetID() { return m_texture_id; } + __fi u32 GetGLFormat() { return m_gl_format; } }; class GSDownloadTextureOGL final : public GSDownloadTexture diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index f8b50de6f6..02330de1eb 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -444,6 +444,8 @@ bool GSDeviceVK::SelectDeviceExtensions(ExtensionList* extension_list, bool enab enable_surface && SupportsExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME, false); #endif + m_optional_extensions.vk_ext_fragment_shader_interlock = SupportsExtension(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME, false); + return true; } @@ -459,6 +461,7 @@ bool GSDeviceVK::SelectDeviceFeatures() m_device_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics; m_device_features.textureCompressionBC = available_features.textureCompressionBC; m_device_features.geometryShader = available_features.geometryShader; + m_device_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics; return true; } @@ -628,6 +631,8 @@ bool GSDeviceVK::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer // VK_EXT_swapchain_maintenance1 types/enums are aliases of VK_KHR_swapchain_maintenance1 types/enums. VkPhysicalDeviceSwapchainMaintenance1FeaturesKHR swapchain_maintenance1_feature = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_KHR}; + VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT fragment_shader_interlock_ext_feature = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT}; if (m_optional_extensions.vk_ext_provoking_vertex) { @@ -654,6 +659,11 @@ bool GSDeviceVK::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer swapchain_maintenance1_feature.swapchainMaintenance1 = VK_TRUE; Vulkan::AddPointerToChain(&device_info, &swapchain_maintenance1_feature); } + if (m_optional_extensions.vk_ext_fragment_shader_interlock) + { + fragment_shader_interlock_ext_feature.fragmentShaderPixelInterlock = VK_TRUE; + Vulkan::AddPointerToChain(&device_info, &fragment_shader_interlock_ext_feature); + } VkResult res = vkCreateDevice(m_physical_device, &device_info, nullptr, &m_device); if (res != VK_SUCCESS) @@ -724,6 +734,8 @@ bool GSDeviceVK::ProcessDeviceExtensions() VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_KHR, nullptr, VK_TRUE}; VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT attachment_feedback_loop_feature = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT}; + VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT fragment_shader_interlock_ext_feature = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT }; // add in optional feature structs if (m_optional_extensions.vk_ext_provoking_vertex) @@ -736,6 +748,8 @@ bool GSDeviceVK::ProcessDeviceExtensions() Vulkan::AddPointerToChain(&features2, &attachment_feedback_loop_feature); if (m_optional_extensions.vk_swapchain_maintenance1) Vulkan::AddPointerToChain(&features2, &swapchain_maintenance1_feature); + if (m_optional_extensions.vk_ext_fragment_shader_interlock) + Vulkan::AddPointerToChain(&features2, &fragment_shader_interlock_ext_feature); // query vkGetPhysicalDeviceFeatures2(m_physical_device, &features2); @@ -813,6 +827,9 @@ bool GSDeviceVK::ProcessDeviceExtensions() m_optional_extensions.vk_swapchain_maintenance1 &= (swapchain_maintenance1_feature.swapchainMaintenance1 == VK_TRUE); + m_optional_extensions.vk_ext_fragment_shader_interlock &= + (fragment_shader_interlock_ext_feature.fragmentShaderPixelInterlock == VK_TRUE); + Console.WriteLn( "VK_EXT_provoking_vertex is %s", m_optional_extensions.vk_ext_provoking_vertex ? "supported" : "NOT supported"); Console.WriteLn( @@ -830,6 +847,8 @@ bool GSDeviceVK::ProcessDeviceExtensions() m_optional_extensions.vk_khr_driver_properties ? "supported" : "NOT supported"); Console.WriteLn("VK_EXT_attachment_feedback_loop_layout is %s", m_optional_extensions.vk_ext_attachment_feedback_loop_layout ? "supported" : "NOT supported"); + Console.WriteLn("VK_EXT_fragment_shader_interlock is %s", + m_optional_extensions.vk_ext_fragment_shader_interlock ? "supported" : "NOT supported"); return true; } @@ -2100,6 +2119,12 @@ bool GSDeviceVK::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) return false; } + if ((m_null_framebuffer = GSTextureVK::CreateNullFramebuffer()) == VK_NULL_HANDLE) + { + Host::ReportErrorAsync("GS", "Failed to create dummy framebuffer"); + return false; + } + { std::optional shader = ReadShaderSource("shaders/vulkan/tfx.glsl"); if (!shader.has_value()) @@ -2743,6 +2768,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; + return true; } @@ -2997,7 +3025,7 @@ void GSDeviceVK::DoMultiStretchRects( const MultiStretchRect* rects, u32 num_rects, GSTextureVK* dTex, ShaderConvertSelector shader) { g_perfmon.Put(GSPerfMon::TextureCopies, 1); - + // Set up vertices first. const u32 vertex_reserve_size = num_rects * 4 * sizeof(GSVertexPT1); const u32 index_reserve_size = num_rects * 6 * sizeof(u16); @@ -3487,14 +3515,15 @@ void GSDeviceVK::VSSetIndexBuffer(const void* index, size_t count) } void GSDeviceVK::OMSetRenderTargets( - GSTexture* rt, GSTexture* ds, const GSVector4i& scissor, FeedbackLoopFlag feedback_loop) + GSTexture* rt, GSTexture* ds, const GSVector4i& scissor, FeedbackLoopFlag feedback_loop, + const GSVector2i& viewport_size) { GSTextureVK* vkRt = static_cast(rt); GSTextureVK* vkDs = static_cast(ds); - pxAssert(vkRt || vkDs); if (m_current_render_target != vkRt || m_current_depth_target != vkDs || - m_current_framebuffer_feedback_loop != feedback_loop) + m_current_framebuffer_feedback_loop != feedback_loop || + m_current_framebuffer == VK_NULL_HANDLE) { // framebuffer change or feedback loop enabled/disabled EndRenderPass(); @@ -3506,12 +3535,16 @@ void GSDeviceVK::OMSetRenderTargets( (feedback_loop & FeedbackLoopFlag_ReadAndWriteRT) != 0, (feedback_loop & (FeedbackLoopFlag_ReadAndWriteDepth | FeedbackLoopFlag_ReadDepth)) != 0); } - else + else if (vkDs) { pxAssert(!(feedback_loop & FeedbackLoopFlag_ReadAndWriteRT)); m_current_framebuffer = vkDs->GetLinkedFramebuffer( nullptr, false, (feedback_loop & (FeedbackLoopFlag_ReadAndWriteDepth | FeedbackLoopFlag_ReadDepth)) != 0); } + else + { + m_current_framebuffer = m_null_framebuffer; + } } else if (InRenderPass()) { @@ -3656,7 +3689,7 @@ void GSDeviceVK::OMSetRenderTargets( } // This is used to set/initialize the framebuffer for tfx rendering. - const GSVector2i size = vkRt ? vkRt->GetSize() : vkDs->GetSize(); + const GSVector2i size = (vkRt ? vkRt->GetSize() : (vkDs ? vkDs->GetSize() : viewport_size)); const VkViewport vp{0.0f, 0.0f, static_cast(size.x), static_cast(size.y), 0.0f, 1.0f}; SetViewport(vp); @@ -3732,6 +3765,11 @@ static void AddShaderHeader(std::stringstream& ss) ss << "#define DISABLE_TEXTURE_BARRIER 1\n"; if (features.texture_barrier && dev->UseFeedbackLoopLayout()) ss << "#define HAS_FEEDBACK_LOOP_LAYOUT 1\n"; + if (features.rov) + { + ss << "#extension GL_ARB_fragment_shader_interlock : require\n"; + ss << "#extension GL_ARB_shader_image_load_store : require\n"; + } } static void AddShaderStageMacro(std::stringstream& ss, bool vs, bool gs, bool fs) @@ -3905,6 +3943,8 @@ bool GSDeviceVK::CreatePipelineLayouts() (m_features.texture_barrier && !UseFeedbackLoopLayout()) ? VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT : VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, VK_SHADER_STAGE_FRAGMENT_BIT); + dslb.AddBinding(TFX_TEXTURE_RT_ROV, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_FRAGMENT_BIT); + dslb.AddBinding(TFX_TEXTURE_DEPTH_ROV, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_FRAGMENT_BIT); if ((m_tfx_texture_ds_layout = dslb.Create(dev)) == VK_NULL_HANDLE) return false; Vulkan::SetObjectName(dev, m_tfx_texture_ds_layout, "TFX texture descriptor layout"); @@ -4739,6 +4779,11 @@ void GSDeviceVK::DestroyResources() if (m_utility_ds_layout != VK_NULL_HANDLE) vkDestroyDescriptorSetLayout(m_device, m_utility_ds_layout, nullptr); + if (m_null_framebuffer != VK_NULL_HANDLE) + { + vkDestroyFramebuffer(m_device, m_null_framebuffer, nullptr); + } + if (m_null_texture) { m_null_texture->Destroy(false); @@ -4870,7 +4915,8 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector AddMacro(ss, "PS_AA1", static_cast(sel.aa1)); AddMacro(ss, "PS_ABE", sel.abe); AddMacro(ss, "PS_ANISOTROPIC_FILTERING", sel.sw_aniso); - + AddMacro(ss, "PS_ROV_COLOR", sel.rov_color); + AddMacro(ss, "PS_ROV_DEPTH", static_cast(sel.rov_depth)); ss << m_tfx_source; VkShaderModule mod = g_vulkan_shader_cache->GetFragmentShader(ss.str()); @@ -5223,7 +5269,95 @@ void GSDeviceVK::SetLineWidth(float width) m_dirty_flags |= DIRTY_FLAG_LINE_WIDTH; } -void GSDeviceVK::PSSetShaderResource(int i, GSTexture* sr, bool check_state) +void GSDeviceVK::PSSetUnorderedAccess(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds) +{ + GSTextureVK* vkRt = static_cast(rt); + GSTextureVK* vkDs = static_cast(ds); + GSTextureVK* oldVkRt = m_tfx_textures[TFX_TEXTURE_RT_ROV] != m_null_texture.get() ? + m_tfx_textures[TFX_TEXTURE_RT_ROV] : nullptr; + GSTextureVK* oldVkDs = m_tfx_textures[TFX_TEXTURE_DEPTH_ROV] != m_null_texture.get() ? + m_tfx_textures[TFX_TEXTURE_DEPTH_ROV] : nullptr; + + if (!(vkRt || vkDs || oldVkRt || oldVkDs)) + return; + + pxAssert(!(vkRt || vkDs) || m_features.rov); + + if (vkRt) + { + PSSetShaderResource(TFX_TEXTURE_RT_ROV, vkRt, true, ResourceType::UAV); + + // Unbind conflicting RT texture + PSSetShaderResource(TFX_TEXTURE_RT, nullptr, false); + + // Unbind conflicting source texture + if (m_tfx_textures[TFX_TEXTURE_TEXTURE] == vkRt) + { + PSSetShaderResource(TFX_TEXTURE_TEXTURE, nullptr, false); + } + + if (write_rt) + { + vkRt->SetState(GSTexture::State::Dirty); + } + } + else + { + // Unbind to avoid conflicts with OM targets. + PSSetShaderResource(TFX_TEXTURE_RT_ROV, nullptr, false); + } + + if (vkDs) + { + PSSetShaderResource(TFX_TEXTURE_DEPTH_ROV, vkDs, true, ResourceType::UAV); + + // Unbind conflicting depth texture + PSSetShaderResource(TFX_TEXTURE_DEPTH, nullptr, false); + + // Unbind conflicting source texture + if (m_tfx_textures[TFX_TEXTURE_TEXTURE] == vkDs) + { + PSSetShaderResource(TFX_TEXTURE_TEXTURE, nullptr, false); + } + + if (write_ds) + { + vkDs->SetState(GSTexture::State::Dirty); + } + } + else + { + // Unbind to avoid conflicts with OM targets. + PSSetShaderResource(TFX_TEXTURE_DEPTH_ROV, nullptr, false); + } + + if (GSConfig.HWROVBarriersVK) + { + // This is to fix issues with some systems that seem to require barriers even with FSI. + // Adds a barriers to the UAVs before every draw. Can result in a large performance hit. + if ((vkRt || vkDs) && InRenderPass()) + { + GL_INS("Ending render pass due to UAV barrier"); + EndRenderPass(); + } + for (GSTextureVK* tex : std::array{ vkRt, vkDs }) + { + if (tex) + { + if (InRenderPass()) + { + GL_INS("Ending render pass due to UAV barrier"); + EndRenderPass(); + } + // The subresources version does the barrier even when the before/after layout + // are the same since we want a memory barrier here, not just a layout change. + tex->TransitionSubresourcesToLayout(GetCurrentCommandBuffer(), 0, 1, tex->GetLayout(), tex->GetLayout()); + } + } + } +} + +void GSDeviceVK::PSSetShaderResource(int i, GSTexture* sr, bool check_state, ResourceType type) { GSTextureVK* vkTex = static_cast(sr); bool update_layout = false; @@ -5231,15 +5365,20 @@ void GSDeviceVK::PSSetShaderResource(int i, GSTexture* sr, bool check_state) { if (check_state) { - if (vkTex->GetLayout() != GSTextureVK::Layout::ShaderReadOnly && InRenderPass()) - { - GL_INS("Ending render pass due to resource transition"); - EndRenderPass(); - update_layout = true; - } + const GSTextureVK::Layout layout = GetResourceLayout(type); vkTex->CommitClear(); - vkTex->TransitionToLayout(GSTextureVK::Layout::ShaderReadOnly); + + if (vkTex->GetLayout() != layout) + { + update_layout = true; + if (InRenderPass()) + { + GL_INS("Ending render pass due to resource transition"); + EndRenderPass(); + } + vkTex->TransitionToLayout(layout); + } } vkTex->SetUseFenceCounter(GetCurrentFenceCounter()); } @@ -5567,6 +5706,16 @@ bool GSDeviceVK::ApplyTFXState(bool already_execed) m_tfx_textures[TFX_TEXTURE_DEPTH]->GetVkLayout()); } } + if (flags & DIRTY_FLAG_TFX_TEXTURE_RT_ROV) + { + dsub.AddImageDescriptorWrite(VK_NULL_HANDLE, TFX_TEXTURE_RT_ROV, m_tfx_textures[TFX_TEXTURE_RT_ROV]->GetView(), + m_tfx_textures[TFX_TEXTURE_RT_ROV]->GetVkLayout(), true); + } + if (flags & DIRTY_FLAG_TFX_TEXTURE_DEPTH_ROV) + { + dsub.AddImageDescriptorWrite(VK_NULL_HANDLE, TFX_TEXTURE_DEPTH_ROV, m_tfx_textures[TFX_TEXTURE_DEPTH_ROV]->GetView(), + m_tfx_textures[TFX_TEXTURE_DEPTH_ROV]->GetVkLayout(), true); + } dsub.PushUpdate(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_tfx_pipeline_layout, TFX_DESCRIPTOR_SET_TEXTURES); } @@ -5744,8 +5893,10 @@ GSTextureVK* GSDeviceVK::SetupPrimitiveTrackingDATE(GSHWDrawConfig& config) void GSDeviceVK::RenderHW(GSHWDrawConfig& config) { const GSVector2i rtsize(config.rt ? config.rt->GetSize() : config.ds->GetSize()); - GSTextureVK* draw_rt = static_cast(config.rt); - GSTextureVK* draw_ds = static_cast(config.ds); + GSTextureVK* draw_rt = config.ps.HasColorROV() ? nullptr : static_cast(config.rt); + GSTextureVK* draw_ds = config.ps.HasDepthROV() ? nullptr : static_cast(config.ds); + GSTextureVK* draw_rt_rov = config.ps.HasColorROV() ? static_cast(config.rt) : nullptr; + GSTextureVK* draw_ds_rov = config.ps.HasDepthROV() ? static_cast(config.ds) : nullptr; GSTextureVK* draw_rt_clone = nullptr; GSTextureVK* colclip_rt = static_cast(g_gs_device->GetColorClipTexture()); @@ -5756,11 +5907,11 @@ void GSDeviceVK::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(TFX_TEXTURE_TEXTURE, config.tex, config.tex != config.rt && config.tex != config.ds); PSSetSampler(config.sampler); } if (config.pal) - PSSetShaderResource(1, config.pal, true); + PSSetShaderResource(TFX_TEXTURE_PALETTE, config.pal, true); if (config.blend.constant_enable) SetBlendConstants(config.blend.constant); @@ -5839,6 +5990,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) g_gs_device->SetColorClipTexture(nullptr); colclip_rt = nullptr; + draw_rt = config.ps.HasColorROV() ? nullptr : static_cast(config.rt); } else { @@ -5927,25 +6079,31 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) // colclip hw requires blitting. EndRenderPass(); } - else if (InRenderPass() && (m_current_render_target == draw_rt || m_current_depth_target == draw_ds)) + else if (InRenderPass() && + ((draw_rt && m_current_render_target == draw_rt) || + (draw_ds && m_current_depth_target == draw_ds))) { // avoid restarting the render pass just to switch from rt+depth to rt and vice versa - // keep the depth even if doing colclip hw draws, because the next draw will probably re-enable depth - if (!draw_rt && m_current_render_target && config.tex != m_current_render_target && - m_current_render_target->GetSize() == draw_ds->GetSize()) + // keep the depth even if doing colclip hw draws, because the next draw will probably re-enable depth. + if (!(draw_rt || draw_rt_rov) && m_current_render_target && config.tex != m_current_render_target && + draw_ds && m_current_render_target->GetSize() == draw_ds->GetSize()) { draw_rt = m_current_render_target; m_pipeline_selector.rt = true; } - else if (!draw_ds && m_current_depth_target && config.tex != m_current_depth_target && - m_current_depth_target->GetSize() == draw_rt->GetSize()) + else if (!(draw_ds || draw_ds_rov) && m_current_depth_target && config.tex != m_current_depth_target && + draw_rt && m_current_depth_target->GetSize() == draw_rt->GetSize()) { draw_ds = m_current_depth_target; m_pipeline_selector.ds = true; } // Prefer keeping feedback loop enabled, that way we're not constantly restarting render passes - pipe.feedback_loop_flags |= m_current_framebuffer_feedback_loop; + if (draw_rt) + pipe.feedback_loop_flags |= m_current_framebuffer_feedback_loop & FeedbackLoopFlag_ReadAndWriteRT; + if (draw_ds) + pipe.feedback_loop_flags |= (m_current_framebuffer_feedback_loop & + (FeedbackLoopFlag_ReadAndWriteDepth | FeedbackLoopFlag_ReadDepth)); } if (draw_rt && ((config.require_one_barrier && (config.IsFeedbackLoopRT(config.ps) || config.IsFeedbackLoopRT(config.alpha_second_pass.ps)))) && !m_features.texture_barrier) @@ -5992,25 +6150,46 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) Console.Warning("VK: Failed to allocate temp texture for RT copy."); } - OMSetRenderTargets(draw_rt, draw_ds, config.scissor, static_cast(pipe.feedback_loop_flags)); - if (pipe.IsRTFeedbackLoop()) + if (pipe.IsRTFeedbackLoop() && draw_rt) { pxAssertMsg(m_features.texture_barrier, "Texture barriers enabled"); PSSetShaderResource(TFX_TEXTURE_RT, draw_rt, false); + + if (m_tfx_textures[TFX_TEXTURE_RT_ROV] == draw_rt) + { + // Unbind to avoid conflicts. + PSSetShaderResource(TFX_TEXTURE_RT_ROV, nullptr, false); + m_dirty_flags |= static_cast(DIRTY_FLAG_TFX_TEXTURE_RT_ROV); + } } else if (!draw_rt_clone) { - PSSetShaderResource(TFX_TEXTURE_RT, nullptr, false); // Unbind to avoid layout conflicts + // Unbind to avoid conflicts with framebuffer + PSSetShaderResource(TFX_TEXTURE_RT, nullptr, false); } - if (pipe.IsDepthFeedbackLoop()) + + if (pipe.IsDepthFeedbackLoop() && draw_ds) { pxAssertMsg(m_features.texture_barrier, "Texture barriers enabled"); PSSetShaderResource(TFX_TEXTURE_DEPTH, draw_ds, false); + + if (m_tfx_textures[TFX_TEXTURE_DEPTH_ROV] == draw_ds) + { + // Unbind to avoid conflicts. + PSSetShaderResource(TFX_TEXTURE_DEPTH_ROV, nullptr, false); + m_dirty_flags |= static_cast(DIRTY_FLAG_TFX_TEXTURE_DEPTH_ROV); + } } else { - PSSetShaderResource(TFX_TEXTURE_DEPTH, nullptr, false); // Unbind to avoid layout conflicts + // Unbind to avoid conflicts with framebuffer + PSSetShaderResource(TFX_TEXTURE_DEPTH, nullptr, false); } + + PSSetUnorderedAccess(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); + // Begin render pass if new target or out of the area. if (!InRenderPass()) { @@ -6188,13 +6367,13 @@ void GSDeviceVK::UpdateHWPipelineSelector(GSHWDrawConfig& config, PipelineSelect pipe.vs.key = config.vs.key; pipe.ps.key_hi = config.ps.key_hi; pipe.ps.key_lo = config.ps.key_lo; - pipe.dss.key = config.depth.key; - pipe.bs.key = config.blend.key; + pipe.dss.key = config.ps.HasDepthROV() ? GSHWDrawConfig::DepthStencilSelector::NoDepth().key : config.depth.key; + pipe.bs.key = config.ps.HasColorROV() ? GSHWDrawConfig::BlendState().key : config.blend.key; pipe.bs.constant = 0; // don't dupe states with different alpha values - pipe.cms.key = config.colormask.key; + pipe.cms.key = config.ps.HasColorROV() ? GSHWDrawConfig::ColorMaskSelector().key : config.colormask.key; pipe.topology = static_cast(config.topology); - pipe.rt = config.rt != nullptr; - pipe.ds = config.ds != nullptr; + pipe.rt = config.rt != nullptr && !config.ps.HasColorROV(); + pipe.ds = config.ds != nullptr && !config.ps.HasDepthROV(); pipe.line_width = config.line_expand; pipe.feedback_loop_flags = FeedbackLoopFlag_None; if (m_features.texture_barrier && (config.require_one_barrier || config.require_full_barrier)) @@ -6205,7 +6384,7 @@ void GSDeviceVK::UpdateHWPipelineSelector(GSHWDrawConfig& config, PipelineSelect if (config.IsFeedbackLoopDepth(config.ps)) pipe.feedback_loop_flags |= FeedbackLoopFlag_ReadAndWriteDepth; } - if (!(pipe.feedback_loop_flags & FeedbackLoopFlag_ReadAndWriteDepth)) + if (pipe.ds && !(pipe.feedback_loop_flags & FeedbackLoopFlag_ReadAndWriteDepth)) { pipe.feedback_loop_flags |= (config.tex && config.tex == config.ds) ? FeedbackLoopFlag_ReadDepth : FeedbackLoopFlag_None; } @@ -6339,4 +6518,7 @@ void GSDeviceVK::SendHWDraw(const GSHWDrawConfig& config, GSTextureVK* draw_rt, } Draw(config); + + if (config.ps.HasColorROV() || config.ps.HasDepthROV()) + g_perfmon.Put(GSPerfMon::DrawCallsROV, 1); } diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h index 0478d992df..647527f2e0 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h @@ -46,6 +46,7 @@ public: bool vk_khr_driver_properties : 1; bool vk_khr_shader_non_semantic_info : 1; bool vk_ext_attachment_feedback_loop_layout : 1; + bool vk_ext_fragment_shader_interlock : 1; }; // Global state accessors @@ -61,8 +62,8 @@ public: // The interaction between raster order attachment access and fbfetch is unclear. __fi bool UseFeedbackLoopLayout() const { - return (m_optional_extensions.vk_ext_attachment_feedback_loop_layout && - !m_optional_extensions.vk_ext_rasterization_order_attachment_access); + return m_optional_extensions.vk_ext_attachment_feedback_loop_layout && + !m_optional_extensions.vk_ext_rasterization_order_attachment_access; } // Helpers for getting constants @@ -301,6 +302,25 @@ public: FeedbackLoopFlag_ReadAndWriteDepth = 4, }; + enum class ResourceType + { + SRV, // Shader resource view (read only) + UAV, // Unordered access (read/write) + }; + + static constexpr GSTextureVK::Layout GetResourceLayout(ResourceType type) + { + switch (type) + { + default: + pxFailRel("Impossible."); + case ResourceType::SRV: + return GSTextureVK::Layout::ShaderReadOnly; + case ResourceType::UAV: + return GSTextureVK::Layout::ReadWriteImage; + } + } + struct alignas(8) PipelineSelector { GSHWDrawConfig::PSSelector ps; @@ -334,7 +354,7 @@ public: __fi bool IsDepthFeedbackLoop() const { return ((feedback_loop_flags & FeedbackLoopFlag_ReadAndWriteDepth) != 0); } __fi bool IsTestingAndSamplingDepth() const { return ((feedback_loop_flags & (FeedbackLoopFlag_ReadDepth | FeedbackLoopFlag_ReadAndWriteDepth)) != 0); } }; - static_assert(sizeof(PipelineSelector) == 24, "Pipeline selector is 24 bytes"); + static_assert(sizeof(PipelineSelector) == 32, "Pipeline selector is 32 bytes"); struct PipelineSelectorHash { @@ -368,6 +388,8 @@ public: TFX_TEXTURE_RT, TFX_TEXTURE_PRIMID, TFX_TEXTURE_DEPTH, + TFX_TEXTURE_RT_ROV, + TFX_TEXTURE_DEPTH_ROV, NUM_TFX_TEXTURES }; @@ -586,11 +608,12 @@ public: void IASetIndexBuffer(const void* index, size_t count); void VSSetIndexBuffer(const void* index, size_t count); - void PSSetShaderResource(int i, GSTexture* sr, bool check_state); + void PSSetUnorderedAccess(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); void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i& scissor, - FeedbackLoopFlag feedback_loop = FeedbackLoopFlag_None); + FeedbackLoopFlag feedback_loop = FeedbackLoopFlag_None, const GSVector2i& viewport_size = {}); void SetVSConstantBuffer(const GSHWDrawConfig::VSConstantBuffer& cb); void SetPSConstantBuffer(const GSHWDrawConfig::PSConstantBuffer& cb); @@ -654,28 +677,31 @@ public: private: enum DIRTY_FLAG : u32 { - DIRTY_FLAG_TFX_TEXTURE_0 = (1 << 0), // 0, 1, 2, 3, 4 - DIRTY_FLAG_TFX_UBO = (1 << 5), - DIRTY_FLAG_UTILITY_TEXTURE = (1 << 6), - DIRTY_FLAG_BLEND_CONSTANTS = (1 << 7), - DIRTY_FLAG_LINE_WIDTH = (1 << 8), - DIRTY_FLAG_INDEX_BUFFER = (1 << 9), - DIRTY_FLAG_VIEWPORT = (1 << 10), - DIRTY_FLAG_SCISSOR = (1 << 11), - DIRTY_FLAG_PIPELINE = (1 << 12), - DIRTY_FLAG_VS_CONSTANT_BUFFER = (1 << 13), - DIRTY_FLAG_PS_CONSTANT_BUFFER = (1 << 14), - DIRTY_FLAG_VS_PUSH_CONSTANTS = (1 << 15), + DIRTY_FLAG_TFX_TEXTURE_0 = (1 << 0), // 0, 1, 2, 3, 4, 5, 6 + DIRTY_FLAG_TFX_UBO = (1 << 7), + DIRTY_FLAG_UTILITY_TEXTURE = (1 << 8), + DIRTY_FLAG_BLEND_CONSTANTS = (1 << 9), + DIRTY_FLAG_LINE_WIDTH = (1 << 10), + DIRTY_FLAG_INDEX_BUFFER = (1 << 11), + DIRTY_FLAG_VIEWPORT = (1 << 12), + DIRTY_FLAG_SCISSOR = (1 << 13), + DIRTY_FLAG_PIPELINE = (1 << 14), + DIRTY_FLAG_VS_CONSTANT_BUFFER = (1 << 15), + DIRTY_FLAG_PS_CONSTANT_BUFFER = (1 << 16), + DIRTY_FLAG_VS_PUSH_CONSTANTS = (1 << 17), DIRTY_FLAG_TFX_TEXTURE_TEX = (DIRTY_FLAG_TFX_TEXTURE_0 << 0), DIRTY_FLAG_TFX_TEXTURE_PALETTE = (DIRTY_FLAG_TFX_TEXTURE_0 << 1), DIRTY_FLAG_TFX_TEXTURE_RT = (DIRTY_FLAG_TFX_TEXTURE_0 << 2), DIRTY_FLAG_TFX_TEXTURE_PRIMID = (DIRTY_FLAG_TFX_TEXTURE_0 << 3), DIRTY_FLAG_TFX_TEXTURE_DEPTH = (DIRTY_FLAG_TFX_TEXTURE_0 << 4), + DIRTY_FLAG_TFX_TEXTURE_RT_ROV = (DIRTY_FLAG_TFX_TEXTURE_0 << 5), + DIRTY_FLAG_TFX_TEXTURE_DEPTH_ROV = (DIRTY_FLAG_TFX_TEXTURE_0 << 6), DIRTY_FLAG_TFX_TEXTURES = DIRTY_FLAG_TFX_TEXTURE_TEX | DIRTY_FLAG_TFX_TEXTURE_PALETTE | DIRTY_FLAG_TFX_TEXTURE_RT | DIRTY_FLAG_TFX_TEXTURE_PRIMID | - DIRTY_FLAG_TFX_TEXTURE_DEPTH, + DIRTY_FLAG_TFX_TEXTURE_DEPTH | DIRTY_FLAG_TFX_TEXTURE_RT_ROV | + DIRTY_FLAG_TFX_TEXTURE_DEPTH_ROV, DIRTY_BASE_STATE = DIRTY_FLAG_INDEX_BUFFER | DIRTY_FLAG_PIPELINE | DIRTY_FLAG_VIEWPORT | DIRTY_FLAG_SCISSOR | DIRTY_FLAG_BLEND_CONSTANTS | DIRTY_FLAG_LINE_WIDTH, @@ -716,7 +742,7 @@ private: float m_current_line_width = 1.0f; u8 m_blend_constant_color = 0; - std::array m_tfx_textures{}; + std::array m_tfx_textures{}; VkSampler m_tfx_sampler = VK_NULL_HANDLE; u32 m_tfx_sampler_sel = 0; VkDescriptorSet m_tfx_ubo_descriptor_set = VK_NULL_HANDLE; @@ -732,6 +758,7 @@ private: VkPipeline m_current_pipeline = VK_NULL_HANDLE; std::unique_ptr m_null_texture; + VkFramebuffer m_null_framebuffer; // current pipeline selector - we save this in the struct to avoid re-zeroing it every draw PipelineSelector m_pipeline_selector = {}; diff --git a/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp index fcad57fbf9..23c2e7d3b0 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp @@ -11,6 +11,24 @@ #include "common/Console.h" #include "common/BitUtils.h" +VkFramebuffer GSTextureVK::CreateNullFramebuffer() +{ + const VkRenderPass rp = GSDeviceVK::GetInstance()->GetRenderPass( + VK_FORMAT_UNDEFINED, + VK_FORMAT_UNDEFINED, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_ATTACHMENT_LOAD_OP_DONT_CARE, + VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, false, false); + + if (!rp) + return VK_NULL_HANDLE; + + Vulkan::FramebufferBuilder fbb; + fbb.SetSize(16384, 16384, 1); + fbb.SetRenderPass(rp); + + return fbb.Create(GSDeviceVK::GetInstance()->GetDevice()); +} + static constexpr const VkComponentMapping s_identity_swizzle{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; @@ -101,7 +119,7 @@ std::unique_ptr GSTextureVK::Create(Type type, Format format, int w pxAssert(levels == 1); ici.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | (GSDeviceVK::GetInstance()->UseFeedbackLoopLayout() ? VK_IMAGE_USAGE_ATTACHMENT_FEEDBACK_LOOP_BIT_EXT : VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT); } @@ -239,6 +257,10 @@ void GSTextureVK::Destroy(bool defer) m_image = VK_NULL_HANDLE; m_allocation = VK_NULL_HANDLE; } + +#ifdef PCSX2_DEVBUILD + m_debug_name.clear(); +#endif } VkImageLayout GSTextureVK::GetVkLayout() const @@ -523,6 +545,8 @@ void GSTextureVK::SetDebugName(std::string_view name) Vulkan::SetObjectName(GSDeviceVK::GetInstance()->GetDevice(), m_image, "%.*s", static_cast(name.size()), name.data()); Vulkan::SetObjectName(GSDeviceVK::GetInstance()->GetDevice(), m_view, "%.*s", static_cast(name.size()), name.data()); + + m_debug_name = name; } #endif @@ -547,13 +571,17 @@ void GSTextureVK::CommitClear(VkCommandBuffer cmdbuf) const VkImageSubresourceRange srr = {VK_IMAGE_ASPECT_DEPTH_BIT, 0u, 1u, 0u, 1u}; vkCmdClearDepthStencilImage(cmdbuf, m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &cv, 1, &srr); } - else + else if (IsRenderTarget()) { alignas(16) VkClearColorValue cv; GSVector4::store(cv.float32, GetClearForFormat()); const VkImageSubresourceRange srr = {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u}; vkCmdClearColorImage(cmdbuf, m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &cv, 1, &srr); } + else + { + pxFailRel("Illegal texture type for clear."); + } SetState(GSTexture::State::Dirty); } @@ -582,7 +610,7 @@ void GSTextureVK::TransitionSubresourcesToLayout( VkCommandBuffer command_buffer, int start_level, int num_levels, Layout old_layout, Layout new_layout) { VkImageAspectFlags aspect; - if (m_type == Type::DepthStencil) + if (IsDepthStencil()) { aspect = g_gs_device->Features().stencil_buffer ? (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) : VK_IMAGE_ASPECT_DEPTH_BIT; @@ -754,6 +782,14 @@ void GSTextureVK::TransitionSubresourcesToLayout( break; } vkCmdPipelineBarrier(command_buffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier); + + // Count as a UAV barrier if we transition to/from UAV. + if (IsRenderTargetOrDepthStencil() && + (old_layout == Layout::ReadWriteImage || new_layout == Layout::ReadWriteImage)) + { + g_perfmon.Put(GSPerfMon::Barriers, 1); + g_perfmon.Put(GSPerfMon::BarriersROV, 1); + } } VkFramebuffer GSTextureVK::GetFramebuffer(bool feedback_loop) diff --git a/pcsx2/GS/Renderers/Vulkan/GSTextureVK.h b/pcsx2/GS/Renderers/Vulkan/GSTextureVK.h index 1f7223bdaa..4c9783e87d 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSTextureVK.h +++ b/pcsx2/GS/Renderers/Vulkan/GSTextureVK.h @@ -42,6 +42,8 @@ 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; } + __fi VkFormat GetVkFormat() const { return m_vk_format; } VkImageLayout GetVkLayout() const; @@ -70,6 +72,8 @@ public: void TransitionSubresourcesToLayout( VkCommandBuffer command_buffer, int start_level, int num_levels, Layout old_layout, Layout new_layout); + static VkFramebuffer CreateNullFramebuffer(); + /// Framebuffers are lazily allocated. VkFramebuffer GetFramebuffer(bool feedback_loop); diff --git a/pcsx2/GS/Renderers/Vulkan/VKBuilders.cpp b/pcsx2/GS/Renderers/Vulkan/VKBuilders.cpp index 5d22390217..bbec5beeed 100644 --- a/pcsx2/GS/Renderers/Vulkan/VKBuilders.cpp +++ b/pcsx2/GS/Renderers/Vulkan/VKBuilders.cpp @@ -733,7 +733,7 @@ void Vulkan::DescriptorSetUpdateBuilder::PushUpdate( } void Vulkan::DescriptorSetUpdateBuilder::AddImageDescriptorWrite(VkDescriptorSet set, u32 binding, VkImageView view, - VkImageLayout layout /*= VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL*/) + VkImageLayout layout /*= VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL*/, bool storage_image /*= false*/) { pxAssert(m_num_writes < MAX_WRITES && m_num_image_infos < MAX_IMAGE_INFOS); @@ -747,7 +747,7 @@ void Vulkan::DescriptorSetUpdateBuilder::AddImageDescriptorWrite(VkDescriptorSet dw.dstSet = set; dw.dstBinding = binding; dw.descriptorCount = 1; - dw.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + dw.descriptorType = storage_image ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE : VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; dw.pImageInfo = ⅈ } diff --git a/pcsx2/GS/Renderers/Vulkan/VKBuilders.h b/pcsx2/GS/Renderers/Vulkan/VKBuilders.h index 124812298a..39806ffd9f 100644 --- a/pcsx2/GS/Renderers/Vulkan/VKBuilders.h +++ b/pcsx2/GS/Renderers/Vulkan/VKBuilders.h @@ -241,7 +241,7 @@ namespace Vulkan bool clear = true); void AddImageDescriptorWrite(VkDescriptorSet set, u32 binding, VkImageView view, - VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, bool storage_image = false); void AddSamplerDescriptorWrite(VkDescriptorSet set, u32 binding, VkSampler sampler); void AddSamplerDescriptorWrites(VkDescriptorSet set, u32 binding, const VkSampler* samplers, u32 num_samplers); void AddCombinedImageSamplerDescriptorWrite(VkDescriptorSet set, u32 binding, VkImageView view, diff --git a/pcsx2/ImGui/ImGuiOverlays.cpp b/pcsx2/ImGui/ImGuiOverlays.cpp index ddd882f0e5..5da665660a 100644 --- a/pcsx2/ImGui/ImGuiOverlays.cpp +++ b/pcsx2/ImGui/ImGuiOverlays.cpp @@ -916,6 +916,12 @@ __ri void ImGuiManager::DrawSettingsOverlay(float scale, float margin, float spa if (GSConfig.HWAA1) APPEND("AA1 "); + if (GSConfig.HWROV) + APPEND("ROV "); + + if (GSConfig.HWROVBarriersVK) + APPEND("RBVK "); + // deliberately test global and print local here for auto values if (EmuConfig.GS.TextureFiltering != BiFiltering::PS2) APPEND("BF={} ", static_cast(GSConfig.TextureFiltering)); diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index d147fcf4eb..df57ca9341 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -755,6 +755,9 @@ Pcsx2Config::GSOptions::GSOptions() HWAccurateAlphaTest = false; HWAA1 = false; UseDebugBlend = false; + HWROV = false; + HWROVLogging = false; + HWROVBarriersVK = false; ManualUserHacks = false; UserHacks_AlignSpriteX = false; @@ -1043,6 +1046,9 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapBitBool(HWAccurateAlphaTest); SettingsWrapBitBool(HWAA1); SettingsWrapBitBool(UseDebugBlend); + SettingsWrapBitBool(HWROV); + SettingsWrapBitBool(HWROVLogging); + SettingsWrapBitBool(HWROVBarriersVK); SettingsWrapIntEnumEx(AccurateBlendingUnit, "accurate_blending_unit"); SettingsWrapIntEnumEx(TextureFiltering, "filter"); SettingsWrapIntEnumEx(TexturePreloading, "texture_preloading"); diff --git a/pcsx2/ShaderCacheVersion.h b/pcsx2/ShaderCacheVersion.h index 4a3ef03969..156f55b344 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 = 98; // Last changed in PR 14477 +static constexpr u32 SHADER_CACHE_VERSION = 99; // Last changed in PR 14503