diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.cpp b/rpcs3/Emu/RSX/Common/TextureUtils.cpp index 9d6e9eee9bb8..2310721843c0 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.cpp +++ b/rpcs3/Emu/RSX/Common/TextureUtils.cpp @@ -1694,15 +1694,16 @@ namespace rsx std::pair get_compatible_gcm_format(rsx::surface_color_format format) { + // NOTE: HW tests prove that all multibyte formats need to swap bytes switch (format) { case rsx::surface_color_format::r5g6b5: - return{ CELL_GCM_TEXTURE_R5G6B5, false }; + return{ CELL_GCM_TEXTURE_R5G6B5, true }; case rsx::surface_color_format::x8r8g8b8_z8r8g8b8: case rsx::surface_color_format::x8r8g8b8_o8r8g8b8: case rsx::surface_color_format::a8r8g8b8: - return{ CELL_GCM_TEXTURE_A8R8G8B8, true }; //verified + return{ CELL_GCM_TEXTURE_A8R8G8B8, true }; case rsx::surface_color_format::x8b8g8r8_o8b8g8r8: case rsx::surface_color_format::x8b8g8r8_z8b8g8r8: @@ -1717,7 +1718,7 @@ namespace rsx case rsx::surface_color_format::x1r5g5b5_o1r5g5b5: case rsx::surface_color_format::x1r5g5b5_z1r5g5b5: - return{ CELL_GCM_TEXTURE_A1R5G5B5, false }; + return{ CELL_GCM_TEXTURE_A1R5G5B5, true }; case rsx::surface_color_format::b8: return{ CELL_GCM_TEXTURE_B8, false }; @@ -1726,7 +1727,7 @@ namespace rsx return{ CELL_GCM_TEXTURE_G8B8, true }; case rsx::surface_color_format::x32: - return{ CELL_GCM_TEXTURE_X32_FLOAT, true }; //verified + return{ CELL_GCM_TEXTURE_X32_FLOAT, true }; default: fmt::throw_exception("Unhandled surface format 0x%x", static_cast(format)); } diff --git a/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp b/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp index ba4d1a297874..755f4eaca84a 100644 --- a/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp +++ b/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp @@ -690,6 +690,12 @@ namespace rsx rop_control.set_output_remap(remap_index); } + if (fragment_program.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) + { + const auto blend_enable_mask = REGS(m_ctx)->blend_enabled_mask() & REGS(m_ctx)->surface_color_target_mask(); + rop_control.set_blend_target_mask(blend_enable_mask); + } + // Generate wpos coefficients // wpos equation is now as follows (ignoring pixel center offset): // wpos.y = (frag_coord / resolution_scale) * ((window_origin!=top)?-1.: 1.) + ((window_origin!=top)? window_height : 0) diff --git a/rpcs3/Emu/RSX/Core/RSXDriverState.h b/rpcs3/Emu/RSX/Core/RSXDriverState.h index 9e7bc2cd68c6..b95bafb7d26f 100644 --- a/rpcs3/Emu/RSX/Core/RSXDriverState.h +++ b/rpcs3/Emu/RSX/Core/RSXDriverState.h @@ -43,6 +43,8 @@ namespace rsx zeta_address_is_cyclic = (1 << 26), // The currently bound Z buffer is active for R/W in a cyclic manner zeta_address_cyclic_barrier = (1 << 27), // A memory barrier is required to "end" the Z buffer cyclic state + blend_config_dirty = (1 << 28), // Blending configuration has been updated. Only matters when programmable blending is in use. + // TODO - Should signal that we simply need to do a FP compare before the next draw call and invalidate the ucode if the content has changed. // Marking as dirty to invalidate hot cache also works, it's not like there's tons of barriers per frame anyway. fragment_program_needs_rehash = fragment_program_ucode_dirty, diff --git a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp index fcc2860306d3..f6d8a25aa638 100644 --- a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp @@ -169,7 +169,7 @@ void GLFragmentDecompilerThread::insertConstants(std::stringstream & OS) if (m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) { - const auto frag_depth_type = (m_prog.ctrl & RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER) + const auto frag_depth_type = (m_prog.ctrl & RSX_SHADER_CONTROL_ROP_MULTISAMPLED) ? "sampler2DMS" : "sampler2D"; @@ -249,7 +249,7 @@ void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) m_shader_props.require_alpha_kill = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL); m_shader_props.require_color_format_convert = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT); m_shader_props.emulate_depth_compare = !!(m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE); - m_shader_props.depth_buffer_multisampled = !!(m_prog.ctrl & RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER); + m_shader_props.ROP_output_multisampled = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ROP_MULTISAMPLED); glsl::insert_glsl_legacy_function(OS, m_shader_props); } @@ -344,7 +344,7 @@ void GLFragmentDecompilerThread::insertMainEnd(std::stringstream & OS) OS << "void main()\n"; OS << "{\n"; - ::glsl::insert_rop_init(OS); + ::glsl::insert_rop_init(OS, m_prog.mrt_buffers_count); OS << "\n" << " fs_main();\n\n"; diff --git a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp index 25b349467a2e..526b69539b29 100644 --- a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp +++ b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp @@ -16,9 +16,9 @@ color_format rsx::internals::surface_color_format_to_gl(rsx::surface_color_forma case rsx::surface_color_format::a8r8g8b8: return{ ::gl::texture::type::uint_8_8_8_8_rev, ::gl::texture::format::bgra, ::gl::texture::internal_format::bgra8, true }; - //These formats discard their alpha component, forced to 0 or 1 - //All XBGR formats will have remapping before they can be read back in shaders as DRGB8 - //Prefix o = 1, z = 0 + // These formats discard their alpha component, forced to 0 or 1 + // All XBGR formats will have remapping before they can be read back in shaders as DRGB8 + // Prefix o = 1, z = 0 case rsx::surface_color_format::x1r5g5b5_o1r5g5b5: return{ ::gl::texture::type::ushort_5_5_5_1, ::gl::texture::format::rgb, ::gl::texture::internal_format::bgr5a1, true, { ::gl::texture::channel::one, ::gl::texture::channel::r, ::gl::texture::channel::g, ::gl::texture::channel::b } }; @@ -54,7 +54,7 @@ color_format rsx::internals::surface_color_format_to_gl(rsx::surface_color_forma { ::gl::texture::channel::one, ::gl::texture::channel::r, ::gl::texture::channel::r, ::gl::texture::channel::r } }; case rsx::surface_color_format::g8b8: - return{ ::gl::texture::type::ubyte, ::gl::texture::format::rg, ::gl::texture::internal_format::rg8, false, + return{ ::gl::texture::type::ubyte, ::gl::texture::format::rg, ::gl::texture::internal_format::rg8, true, { ::gl::texture::channel::g, ::gl::texture::channel::r, ::gl::texture::channel::g, ::gl::texture::channel::r } }; case rsx::surface_color_format::x32: diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp index daa2ad9fc001..235f6a6856f0 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp @@ -151,8 +151,13 @@ namespace glsl ; } - void insert_rop_init(std::ostream& OS) + void insert_rop_init(std::ostream& OS, u32 mrt_buffer_count) { + program_common::define_glsl_constants(OS, + { + { "_MRT_BUFFERS_COUNT", mrt_buffer_count }, + }); + OS << #include "GLSLSnippets/RSXProg/RSXROPPrologue.glsl" ; @@ -205,6 +210,8 @@ namespace glsl { "FRAG_DEPTH_FLOAT_BIT ", rsx::ROP_control_bits::FRAG_DEPTH_FLOAT_BIT }, { "MRT_CHANNEL_REMAP_OFFSET ", rsx::ROP_control_bits::MRT_CHANNEL_REMAP_OFFSET }, { "MRT_CHANNEL_REMAP_LENGTH ", rsx::ROP_control_bits::MRT_CHANNEL_REMAP_NUM_BITS }, + { "MRT_BLEND_TARGETS_OFFSET ", rsx::ROP_control_bits::MRT_BLEND_TARGETS_OFFSET }, + { "MRT_BLEND_TARGETS_LENGTH ", rsx::ROP_control_bits::MRT_BLEND_TARGETS_NUM_BITS }, { "ROP_CMD_MASK ", rsx::ROP_control_bits::ROP_CMD_MASK } }); @@ -263,9 +270,9 @@ namespace glsl enabled_options.push_back("_ENABLE_DEPTH_COMPARE"); } - if (props.depth_buffer_multisampled) + if (props.ROP_output_multisampled) { - enabled_options.push_back("_ENABLE_DEPTH_BUFFER_MULTISAMPLED"); + enabled_options.push_back("_ENABLE_ROP_OUTPUT_MULTISAMPLED"); } } @@ -346,6 +353,11 @@ namespace glsl enabled_options.push_back("_ENABLE_ROP_CHANNEL_REMAPPING"); } + if (props.ROP_programmable_blend) + { + enabled_options.push_back("_ENABLE_PROGRAMMABLE_BLENDING"); + } + if (props.require_fog_read) { program_common::define_glsl_constants(OS, @@ -493,6 +505,11 @@ namespace glsl } } } + + if (props.ROP_programmable_blend) + { + insert_blend_prologue(OS); + } } std::string getFunctionImpl(FUNCTION f) diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.h b/rpcs3/Emu/RSX/Program/GLSLCommon.h index b4273c4aff84..c3b839912e8f 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.h +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.h @@ -28,11 +28,13 @@ namespace rsx ALPHA_FUNC_OFFSET = 12, MSAA_SAMPLE_CTRL_OFFSET = 15, MRT_CHANNEL_REMAP_OFFSET = 17, + MRT_BLEND_TARGETS_OFFSET = 20, // Data lengths ALPHA_FUNC_NUM_BITS = 3, MSAA_SAMPLE_CTRL_NUM_BITS = 2, MRT_CHANNEL_REMAP_NUM_BITS = 3, + MRT_BLEND_TARGETS_NUM_BITS = 4, // Meta ROP_CMD_MASK = 0xF // Commands are encoded in the lower 4 bits @@ -54,6 +56,7 @@ namespace rsx void set_msaa_control(uint ctrl) { value |= (ctrl << ROP_control_bits::MSAA_SAMPLE_CTRL_OFFSET); } void set_output_remap(uint remap) { value |= (remap << ROP_control_bits::MRT_CHANNEL_REMAP_OFFSET); } + void set_blend_target_mask(uint mask) { value |= ((mask & 0xF) << ROP_control_bits::MRT_BLEND_TARGETS_OFFSET); } }; } @@ -77,7 +80,7 @@ namespace glsl std::string getHalfTypeNameImpl(usz elementCount); std::string compareFunctionImpl(COMPARE f, std::string_view Op0, std::string_view Op1, bool scalar = false); void insert_vertex_input_fetch(std::stringstream& OS, glsl_rules rules, bool glsl4_compliant=true); - void insert_rop_init(std::ostream& OS); + void insert_rop_init(std::ostream& OS, u32 mrt_buffers_count); void insert_rop(std::ostream& OS, const shader_properties& props); void insert_glsl_legacy_function(std::ostream& OS, const shader_properties& props); std::string getFunctionImpl(FUNCTION f); diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentPrologue.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentPrologue.glsl index a75ea84d6e42..3a94052b667c 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentPrologue.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentPrologue.glsl @@ -15,16 +15,24 @@ R"( // Default. Used when we're not utilizing native fp16 vec4 round_to_8bit(const in vec4 v4) { - uvec4 raw = uvec4(max(floor(fma(_fx12_truncate(v4), vec4(255.), vec4(0.5))), vec4(0.))); + const uvec4 raw = uvec4(max(floor(fma(_fx12_truncate(v4), vec4(255.), vec4(0.5))), vec4(0.))); return vec4(raw) / vec4(255.); } #ifndef _32_BIT_OUTPUT f16vec4 round_to_8bit(const in f16vec4 v4) { - uvec4 raw = uvec4(max(floor(fma(_fx12_truncate(vec4(v4)), f16vec4(255.), f16vec4(0.5))), vec4(0.))); + const uvec4 raw = uvec4(max(floor(fma(_fx12_truncate(vec4(v4)), f16vec4(255.), f16vec4(0.5))), vec4(0.))); return f16vec4(raw) / f16vec4(255.); } #endif + +// Scalar variant of 8-bit rounding is only available for f32. +float round_to_8bit(const in float v) +{ + const uint raw = uint(max(floor(fma(_fx12_truncate(v), 255.f, 0.5f)), 0.f)); + return float(raw) / 255.f; +} + #endif #ifdef _DISABLE_EARLY_DISCARD @@ -178,6 +186,24 @@ vec4 remap_ROP_output(const in vec4 col, const in uint remap_index) return vec4(col.rgb, 0.f); } } +vec4 remap_ROP_input(const in vec4 col, const in uint remap_index) +{ + switch (remap_index) + { + default: + case ROP_REMAP_SWIZZLE_RGBA: // RGBA + return col; + case ROP_REMAP_SWIZZLE_BBBB: // B8 + return col.rrrr; + case ROP_REMAP_SWIZZLE_GBGB: // G8B8 + return col.rgrg; + case ROP_REMAP_SWIZZLE_RGB1: // RGB1 + case ROP_REMAP_SWIZZLE_RGB0: // RGB0 + // This seems completely wrong but hwtest shows that blending with DST_ALPHA reads 1 for all XRGB8 formats regardless of the 0/1 values. + // Lucky for us this opens up the optimization opportunity to not need PB for such formats when only DST_A is referenced. SRC_A is another matter altogether. + return vec4(col.rgb, 1.f); + } +} #endif )" diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXProgrammableBlendPrologue.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXProgrammableBlendPrologue.glsl index 0cb33fe4d6c7..94803f419c1e 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXProgrammableBlendPrologue.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXProgrammableBlendPrologue.glsl @@ -4,9 +4,9 @@ R"( * Required register definitions from ROP config struct { vec4 blend_constants; // fp32x4 - uint blend_func; // rgb16, a16 - uint blend_factors_a; // src16, dst16 - uint blend_factors_rgb; // src16, dst16 + uint blend_eqn; // Packed RGB in lower 16 bits, A in upper 16 bits + uint blend_sfactors; // Ditto + uint blend_dfactors; // Ditto } */ @@ -35,6 +35,26 @@ R"( #define BLEND_FUNC_ADD_SIGNED 0x0000F006 #define BLEND_FUNC_REVERSE_ADD_SIGNED 0x0000F007 +#ifdef INT_FRAMEBUFFER_BIT +#define _blend_saturate _saturate +#define _blend_saturate_signed_imp(U8Type, S8Type, FloatType, v) \ + const U8Type u8 = U8Type(clamp(v, 0.f, 1.f) * 255.f + 0.5f); \ + const S8Type s8 = S8Type(u8 << 24) >> 24; \ + return FloatType(s8 / 255.f); + +float _blend_saturate_signed(float v) +{ + _blend_saturate_signed_imp(uint, int, float, v); +} +vec3 _blend_saturate_signed(vec3 v) +{ + _blend_saturate_signed_imp(uvec3, ivec3, vec3, v); +} +#else +#define _blend_saturate(v) v +#define _blend_saturate_signed(v) v +#endif + float get_blend_factor_a(const in uint op, const in vec4 src, const in vec4 dst) { switch (op) @@ -51,9 +71,9 @@ float get_blend_factor_a(const in uint op, const in vec4 src, const in vec4 dst) case BLEND_FACTOR_ONE_MINUS_DST_COLOR: return 1. - dst.a; case BLEND_FACTOR_SRC_ALPHA_SATURATE: return 1; case BLEND_FACTOR_CONSTANT_COLOR: - case BLEND_FACTOR_CONSTANT_ALPHA: return constants.a; + case BLEND_FACTOR_CONSTANT_ALPHA: return blend_constants.a; case BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR: - case BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA: return 1. - constants.a; + case BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA: return 1. - blend_constants.a; } return 0.; } @@ -69,10 +89,10 @@ vec3 get_blend_factor_rgb(const in uint op, const in vec4 src, const in vec4 dst case BLEND_FACTOR_ONE_MINUS_SRC_COLOR: return 1. - src.rgb; case BLEND_FACTOR_ONE_MINUS_SRC_ALPHA: return 1. - src.aaa; case BLEND_FACTOR_DST_COLOR: return dst.rgb; - case BLEND_FACTOR_DST_ALPHA: return dst.a; + case BLEND_FACTOR_DST_ALPHA: return dst.aaa; case BLEND_FACTOR_ONE_MINUS_DST_COLOR: return 1. - dst.rgb; - case BLEND_FACTOR_ONE_MINUS_DST_ALPHA: return 1. - dst.a; - case BLEND_FACTOR_SRC_ALPHA_SATURATE: return src.rgb; + case BLEND_FACTOR_ONE_MINUS_DST_ALPHA: return 1. - dst.aaa; + case BLEND_FACTOR_SRC_ALPHA_SATURATE: return vec3(min(src.a, 1. - dst.a)); case BLEND_FACTOR_CONSTANT_COLOR: return blend_constants.rgb; case BLEND_FACTOR_CONSTANT_ALPHA: return blend_constants.aaa; case BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR: return 1. - blend_constants.rgb; @@ -83,9 +103,18 @@ vec3 get_blend_factor_rgb(const in uint op, const in vec4 src, const in vec4 dst float apply_blend_func_a(const in vec4 src, const in vec4 dst) { - uint blend_factor_a_s = _get_bits(blend_factors_a, 0, 16); - uint blend_factor_a_d = _get_bits(blend_factors_a, 16, 16); - uint func = _get_bits(blend_func, 16, 16); + uint blend_factor_a_s = _get_bits(blend_sfactors, 16, 16); + uint blend_factor_a_d = _get_bits(blend_dfactors, 16, 16); + uint func = _get_bits(blend_eqn, 16, 16); + + switch (func) + { + case BLEND_MIN: return min(_blend_saturate(src.a), dst.a); + case BLEND_MAX: return max(_blend_saturate(src.a), dst.a); + case BLEND_FUNC_REVERSE_SUBTRACT_SIGNED: return dst.a - _blend_saturate_signed(src.a); + case BLEND_FUNC_ADD_SIGNED: return _blend_saturate_signed(src.a) + dst.a; + case BLEND_FUNC_REVERSE_ADD_SIGNED: return _blend_saturate(src.a) + _blend_saturate_signed(dst.a); + } const float src_factor_a = get_blend_factor_a(blend_factor_a_s, src, dst); const float dst_factor_a = get_blend_factor_a(blend_factor_a_d, src, dst); @@ -96,24 +125,28 @@ float apply_blend_func_a(const in vec4 src, const in vec4 dst) switch (func) { - case BLEND_FUNC_ADD: return _saturate(s) + d; - case BLEND_MIN: return min(_saturate(s), d); - case BLEND_MAX: return max(_saturate(s), d); - case BLEND_FUNC_SUBTRACT: return _saturate(s) - d; - case BLEND_FUNC_REVERSE_SUBTRACT: return d - _saturate(s); - case BLEND_FUNC_REVERSE_SUBTRACT_SIGNED: return d - s; - case BLEND_FUNC_ADD_SIGNED: return s + d; - case BLEND_FUNC_REVERSE_ADD_SIGNED: return s + d; + case BLEND_FUNC_ADD: return _blend_saturate(s) + d; + case BLEND_FUNC_SUBTRACT: return _blend_saturate(s) - d; + case BLEND_FUNC_REVERSE_SUBTRACT: return d - _blend_saturate(s); } - return vec3(0.); + return 0.f; } vec3 apply_blend_func_rgb(const in vec4 src, const in vec4 dst) { - uint blend_factor_rgb_s = _get_bits(blend_factors_rgb, 0, 16); - uint blend_factor_rgb_d = _get_bits(blend_factors_rgb, 16, 16); - uint func = _get_bits(blend_func, 0, 16); + uint blend_factor_rgb_s = _get_bits(blend_sfactors, 0, 16); + uint blend_factor_rgb_d = _get_bits(blend_dfactors, 0, 16); + uint func = _get_bits(blend_eqn, 0, 16); + + switch (func) + { + case BLEND_MIN: return min(_blend_saturate(src.rgb), dst.rgb); + case BLEND_MAX: return max(_blend_saturate(src.rgb), dst.rgb); + case BLEND_FUNC_REVERSE_SUBTRACT_SIGNED: return dst.rgb - _blend_saturate_signed(src.rgb); + case BLEND_FUNC_ADD_SIGNED: return _blend_saturate_signed(src.rgb) + dst.rgb; + case BLEND_FUNC_REVERSE_ADD_SIGNED: return _blend_saturate(src.rgb) + _blend_saturate_signed(dst.rgb); + } const vec3 src_factor_rgb = get_blend_factor_rgb(blend_factor_rgb_s, src, dst); const vec3 dst_factor_rgb = get_blend_factor_rgb(blend_factor_rgb_d, src, dst); @@ -124,14 +157,9 @@ vec3 apply_blend_func_rgb(const in vec4 src, const in vec4 dst) switch (func) { - case BLEND_FUNC_ADD: return _saturate(s) + d; - case BLEND_MIN: return min(_saturate(s), d); - case BLEND_MAX: return max(_saturate(s), d); - case BLEND_FUNC_SUBTRACT: return _saturate(s) - d; - case BLEND_FUNC_REVERSE_SUBTRACT: return d - _saturate(s); - case BLEND_FUNC_REVERSE_SUBTRACT_SIGNED: return d - s; - case BLEND_FUNC_ADD_SIGNED: return s + d; - case BLEND_FUNC_REVERSE_ADD_SIGNED: return s + d; + case BLEND_FUNC_ADD: return _blend_saturate(s) + d; + case BLEND_FUNC_SUBTRACT: return _blend_saturate(s) - d; + case BLEND_FUNC_REVERSE_SUBTRACT: return d - _blend_saturate(s); } return vec3(0.); @@ -145,8 +173,12 @@ vec4 do_blend(const in vec4 src, const in vec4 dst) apply_blend_func_a(src, dst) ); +#ifdef _ENABLE_ROP_OUTPUT_ROUNDING // Accurate int conversion with wrapping return round_to_8bit(result); +#else + return result; +#endif } )" diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl index a9229fc6f23d..a46c420edeb3 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl @@ -7,29 +7,36 @@ R"( } #endif +// This definitely happens before output quantization. #ifdef _ENABLE_FRAMEBUFFER_SRGB - col0.rgb = _mrt_color_t(linear_to_srgb(col0)).rgb; - col1.rgb = _mrt_color_t(linear_to_srgb(col1)).rgb; - col2.rgb = _mrt_color_t(linear_to_srgb(col2)).rgb; - col3.rgb = _mrt_color_t(linear_to_srgb(col3)).rgb; + MRT0_DO(col0.rgb = _mrt_color_t(linear_to_srgb(col0)).rgb;) + MRT1_DO(col1.rgb = _mrt_color_t(linear_to_srgb(col1)).rgb;) + MRT2_DO(col2.rgb = _mrt_color_t(linear_to_srgb(col2)).rgb;) + MRT3_DO(col3.rgb = _mrt_color_t(linear_to_srgb(col3)).rgb;) #endif -#ifdef _ENABLE_ROP_OUTPUT_ROUNDING - col0 = round_to_8bit(col0); - col1 = round_to_8bit(col1); - col2 = round_to_8bit(col2); - col3 = round_to_8bit(col3); +#if defined(_ENABLE_ROP_OUTPUT_ROUNDING) + MRT0_DO(col0 = round_to_8bit(col0);) + MRT1_DO(col1 = round_to_8bit(col1);) + MRT2_DO(col2 = round_to_8bit(col2);) + MRT3_DO(col3 = round_to_8bit(col3);) #endif - // Post-output stages - // Alpha Testing +// Alpha Testing. This stage always runs even without a color output. #ifdef _ENABLE_ALPHA_TEST + // TODO: Verify that alpha test actually runs on quantized output! + // Behavior was inferred from game observations but real tests will be needed here. +#if _MRT_BUFFERS_COUNT >= 1 + #define _alpha_quantize(v) v +#else + #define _alpha_quantize(v) round_to_8bit(v) +#endif // _MRT_BUFFERS_COUNT const uint alpha_func = _get_bits(rop_control, ALPHA_TEST_FUNC_OFFSET, ALPHA_TEST_FUNC_LENGTH); - if (!comparison_passes(col0.a, alpha_ref, alpha_func)) + if (!comparison_passes(_alpha_quantize(col0.a), alpha_ref, alpha_func)) { discard; } -#endif +#endif // _ENABLE_ALPHA_TEST #ifdef _ENABLE_ALPHA_TO_COVERAGE_TEST if (!coverage_test_passes(col0)) @@ -38,12 +45,41 @@ R"( } #endif +// ================= Post-output color stages ================= +#if _MRT_BUFFERS_COUNT >= 1 + +// NOTE: Blending happens before output remapping as per hardware tests. +// Sources = raw shader output, Dest = Remapped output from previous draw +#ifdef _ENABLE_PROGRAMMABLE_BLENDING + MRT0_BLEND_DO(col0 = _mrt_color_t(do_blend(col0, mrt_color0));) + MRT1_BLEND_DO(col1 = _mrt_color_t(do_blend(col1, mrt_color1));) + MRT2_BLEND_DO(col2 = _mrt_color_t(do_blend(col2, mrt_color2));) + MRT3_BLEND_DO(col3 = _mrt_color_t(do_blend(col3, mrt_color3));) +#endif + +#ifdef _ENABLE_ROP_CHANNEL_REMAPPING + MRT0_DO(col0 = _mrt_color_t(remap_ROP_output(col0, ROP_remap));) + MRT1_DO(col1 = _mrt_color_t(remap_ROP_output(col1, ROP_remap));) + MRT2_DO(col2 = _mrt_color_t(remap_ROP_output(col2, ROP_remap));) + MRT3_DO(col3 = _mrt_color_t(remap_ROP_output(col3, ROP_remap));) +#endif // _ENABLE_ROP_CHANNEL_REMAPPING + + // Commit COLOR aspect + MRT0_DO(ocol0 = col0;) + MRT1_DO(ocol1 = col1;) + MRT2_DO(ocol2 = col2;) + MRT3_DO(ocol3 = col3;) + +#endif // _MRT_BUFFERS_COUNT >= 1 + +//// ====================== Depth Export =========================== + #ifdef _ENABLE_DEPTH_COMPARE -#ifdef _ENABLE_DEPTH_BUFFER_MULTISAMPLED +#ifdef _ENABLE_ROP_OUTPUT_MULTISAMPLED float dstDepth = texelFetch(frag_depth, ivec2(gl_FragCoord.xy), gl_SampleID).r; #else float dstDepth = texelFetch(frag_depth, ivec2(gl_FragCoord.xy), 0).r; -#endif +#endif // _ENABLE_ROP_OUTPUT_MULTISAMPLED float srcDepth = gl_FragCoord.z; float scale = _test_bit(rop_control, FRAG_DEPTH_24_BIT) ? float(0xffffffu) : float(0xffffu); @@ -56,37 +92,5 @@ R"( { gl_FragDepth = srcDepth; } -#endif - -#ifdef _ENABLE_ROP_CHANNEL_REMAPPING - const uint ROP_remap = get_ROP_channel_remap(); - col0 = _mrt_color_t(remap_ROP_output(col0, ROP_remap)); - col1 = _mrt_color_t(remap_ROP_output(col1, ROP_remap)); - col2 = _mrt_color_t(remap_ROP_output(col2, ROP_remap)); - col3 = _mrt_color_t(remap_ROP_output(col3, ROP_remap)); -#endif - -#ifdef _ENABLE_PROGRAMMABLE_BLENDING - switch (framebufferCount) - { - case 4: - col3 = do_blend(col3, mrt_color[3]); - // Fallthrough - case 3: - col2 = do_blend(col2, mrt_color[2]); - // Fallthrough - case 2: - col1 = do_blend(col1, mrt_color[1]); - // Fallthrough - default: - col0 = do_blend(col0, mrt_color[0]); - break; - } -#endif - - // Commit - ocol0 = col0; - ocol1 = col1; - ocol2 = col2; - ocol3 = col3; +#endif // _ENABLE_DEPTH_COMPARE )" diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPPrologue.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPPrologue.glsl index f24a1ca20165..b95169c380eb 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPPrologue.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPPrologue.glsl @@ -19,11 +19,65 @@ R"( } #endif +#if _MRT_BUFFERS_COUNT >= 1 + #define MRT0_DO(expr) expr +#else + #define MRT0_DO(expr) +#endif +#if _MRT_BUFFERS_COUNT >= 2 + #define MRT1_DO(expr) expr +#else + #define MRT1_DO(expr) +#endif +#if _MRT_BUFFERS_COUNT >= 3 + #define MRT2_DO(expr) expr +#else + #define MRT2_DO(expr) +#endif +#if _MRT_BUFFERS_COUNT == 4 + #define MRT3_DO(expr) expr +#else + #define MRT3_DO(expr) +#endif + +#if _MRT_BUFFERS_COUNT >= 1 + +#ifdef _ENABLE_ROP_CHANNEL_REMAPPING +const uint ROP_remap = get_ROP_channel_remap(); +#endif + #ifdef _ENABLE_PROGRAMMABLE_BLENDING - vec4 mrt_color[4]; - for (int n = 0; n < framebufferCount; ++n) - { - mrt_color[n] = subPassLoad(mrtAttachments[n]); - } +#define IS_MRT_BLEND_ENABLED(n) _test_bit(rop_control, (n) + MRT_BLEND_TARGETS_OFFSET) + +#ifdef _ENABLE_ROP_OUTPUT_MULTISAMPLED +#define FRAG_LOAD(n) mrt_color##n = subpassLoad(frag_src_##n, gl_SampleID) +#else +#define FRAG_LOAD(n) mrt_color##n = subpassLoad(frag_src_##n) #endif + +#define MRT0_BLEND_DO(expr) MRT0_DO(if (IS_MRT_BLEND_ENABLED(0)) expr) +#define MRT1_BLEND_DO(expr) MRT1_DO(if (IS_MRT_BLEND_ENABLED(1)) expr) +#define MRT2_BLEND_DO(expr) MRT2_DO(if (IS_MRT_BLEND_ENABLED(2)) expr) +#define MRT3_BLEND_DO(expr) MRT3_DO(if (IS_MRT_BLEND_ENABLED(3)) expr) + +MRT0_DO(vec4 mrt_color0;) +MRT1_DO(vec4 mrt_color1;) +MRT2_DO(vec4 mrt_color2;) +MRT3_DO(vec4 mrt_color3;) + +MRT0_BLEND_DO(FRAG_LOAD(0);) +MRT1_BLEND_DO(FRAG_LOAD(1);) +MRT2_BLEND_DO(FRAG_LOAD(2);) +MRT3_BLEND_DO(FRAG_LOAD(3);) + +#ifdef _ENABLE_ROP_CHANNEL_REMAPPING + MRT0_BLEND_DO(mrt_color0 = _mrt_color_t(remap_ROP_input(mrt_color0, ROP_remap));) + MRT1_BLEND_DO(mrt_color1 = _mrt_color_t(remap_ROP_input(mrt_color1, ROP_remap));) + MRT2_BLEND_DO(mrt_color2 = _mrt_color_t(remap_ROP_input(mrt_color2, ROP_remap));) + MRT3_BLEND_DO(mrt_color3 = _mrt_color_t(remap_ROP_input(mrt_color3, ROP_remap));) +#endif // _ENABLE_ROP_CHANNEL_REMAPPING + +#undef FRAG_LOAD +#endif // _ENABLE_PROGRAMMABLE_BLENDING +#endif // _MRT_BUFFERS_COUNT >= 1 )" diff --git a/rpcs3/Emu/RSX/Program/GLSLTypes.h b/rpcs3/Emu/RSX/Program/GLSLTypes.h index 7b8f10d13f3e..cade46757d66 100644 --- a/rpcs3/Emu/RSX/Program/GLSLTypes.h +++ b/rpcs3/Emu/RSX/Program/GLSLTypes.h @@ -38,12 +38,12 @@ namespace glsl bool require_fog_read : 1; bool emulate_shadow_compare : 1; bool emulate_depth_compare : 1; - bool depth_buffer_multisampled : 1; bool low_precision_tests : 1; bool disable_early_discard : 1; bool supports_native_fp16 : 1; // ROP control flags + bool ROP_output_multisampled : 1; bool ROP_output_rounding : 1; bool ROP_sRGB_packing : 1; bool ROP_alpha_test : 1; @@ -51,6 +51,7 @@ namespace glsl bool ROP_polygon_stipple_test : 1; bool ROP_discard : 1; bool ROP_channel_remap : 1; + bool ROP_programmable_blend : 1; // Texturing spec bool require_texture_ops : 1; // Global switch to enable/disable all texture code diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index bcf26d86ad59..cfa735fe8a55 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -8,6 +8,7 @@ #include "Host/MM.h" #include "Host/RSXDMAWriter.h" #include "NV47/HW/context.h" +#include "NV47/HW/context_accessors.define.h" #include "Program/GLSLCommon.h" #include "rsx_methods.h" @@ -123,6 +124,11 @@ namespace rsx // TODO: Proper context manager static rsx::context s_ctx{ .rsxthr = nullptr, .register_state = &method_registers }; + constexpr u32 fs_export_config_mask = + RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE | + RSX_SHADER_CONTROL_ROP_MULTISAMPLED | + RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING; + rsx_iomap_table::rsx_iomap_table() noexcept : ea(fill_array(-1)) , io(fill_array(-1)) @@ -289,6 +295,131 @@ namespace rsx } } + static bool evaluate_programmable_blending_state(rsx::context* ctx, u32& fragment_ctrl) + { + const bool is_blend_config_dirty = RSX(ctx)->m_graphics_state.test(rsx::blend_config_dirty); + RSX(ctx)->m_graphics_state.clear(rsx::blend_config_dirty); + + const auto blend_enable_mask = REGS(ctx)->blend_enabled_mask() & REGS(ctx)->surface_color_target_mask(); + const bool programmable_blend_active = !!(fragment_ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING); + const bool is_blending_active = !!blend_enable_mask && !REGS(ctx)->logic_op_enabled(); + + if (!is_blending_active && !programmable_blend_active) + { + return false; + } + + if (!is_blending_active) + { + fragment_ctrl &= ~RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING; + return true; + } + + if (g_cfg.video.disable_hardware_blending) + { + fragment_ctrl |= RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING; + return !programmable_blend_active; + } + + // We actually need to handle this, we're still blending + const auto is_signed_equation = [](rsx::blend_equation equation) + { + switch (equation) + { + case rsx::blend_equation::add_signed: + case rsx::blend_equation::reverse_add_signed: + case rsx::blend_equation::reverse_subtract_signed: + return true; + default: + return false; + } + }; + + const auto factor_references_alpha = [](rsx::blend_factor factor) + { + switch (factor) + { + case rsx::blend_factor::src_alpha: + case rsx::blend_factor::one_minus_src_alpha: + case rsx::blend_factor::dst_alpha: + case rsx::blend_factor::one_minus_dst_alpha: + case rsx::blend_factor::src_alpha_saturate: + return true; + default: + return false; + } + }; + + const auto shader_writes_alpha = [&]() + { + for (u32 i = 0, mask = blend_enable_mask; !!mask; mask >>= 1, i++) + { + if (REGS(ctx)->color_mask_a(i)) + { + return true; + } + } + return false; + }; + + bool need_programmable_blending = false; + const auto surface_format = REGS(ctx)->surface_color(); + + switch (surface_format) + { + case surface_color_format::x1r5g5b5_z1r5g5b5: + case surface_color_format::x1r5g5b5_o1r5g5b5: + case surface_color_format::x8r8g8b8_z8r8g8b8: + case surface_color_format::x8r8g8b8_o8r8g8b8: + case surface_color_format::x8b8g8r8_z8b8g8r8: + case surface_color_format::x8b8g8r8_o8b8g8r8: + // Force PB if alpha is read in the RGB factors or A is written without passthrough. + if (factor_references_alpha(REGS(ctx)->blend_func_sfactor_rgb()) || + factor_references_alpha(REGS(ctx)->blend_func_dfactor_rgb())) + { + need_programmable_blending = true; + } + else + { + need_programmable_blending = shader_writes_alpha() && ( + REGS(ctx)->blend_equation_a() == rsx::blend_equation::reverse_subtract || + REGS(ctx)->blend_equation_a() == rsx::blend_equation::min || + REGS(ctx)->blend_equation_a() == rsx::blend_equation::max || + REGS(ctx)->blend_func_sfactor_a() != rsx::blend_factor::one || + REGS(ctx)->blend_func_dfactor_a() != rsx::blend_factor::zero); + } + break; + case surface_color_format::b8: + case surface_color_format::g8b8: + // These 2 don't accept custom factors anyway + [[ fallthrough ]]; + default: + need_programmable_blending = need_programmable_blending || + is_signed_equation(REGS(ctx)->blend_equation_rgb()) || + is_signed_equation(REGS(ctx)->blend_equation_a()); + break; + } + + if (need_programmable_blending && is_blend_config_dirty) + { + RSX(ctx)->m_graphics_state.set(rsx::fragment_state_dirty); + } + + if (need_programmable_blending == programmable_blend_active) + { + return false; + } + + if (!need_programmable_blending) + { + fragment_ctrl &= ~RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING; + return true; + } + + fragment_ctrl |= RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING; + return true; + } + std::pair interleaved_range_info::calculate_required_range(u32 first, u32 count) { if (vertex_range.second) @@ -1779,7 +1910,7 @@ namespace rsx for (uint i = 0; i < mrt_buffers.size(); ++i) { - if (m_ctx->register_state->color_write_enabled(i)) + if (REGS(m_ctx)->color_write_enabled(i)) { const auto real_index = mrt_buffers[i]; m_framebuffer_layout.color_write_enabled[real_index] = true; @@ -1893,7 +2024,7 @@ namespace rsx return; } - const auto target = m_ctx->register_state->surface_color_target(); + const auto target = REGS(m_ctx)->surface_color_target(); if (rsx::utility::get_mrt_buffers_count(target) == current_fragment_program.mrt_buffers_count) { return; @@ -1969,29 +2100,51 @@ namespace rsx rsx::flags32_t thread::get_fragment_program_export_config() { - if (!g_cfg.video.emulate_depth_compare) [[ likely ]] + u32 expected_ctrl = 0; + + // Programmable blending + if (backend_config.supports_programmable_blending) { - return 0; + expected_ctrl = current_fragment_program.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING; + + if (m_graphics_state.test(rsx::pipeline_config_dirty)) + { + evaluate_programmable_blending_state(m_ctx, expected_ctrl); + } } - if (m_ctx->register_state->current_draw_clause.classify_mode() != primitive_class::polygon) + // Resolve MSAA here if needed + if (!!(expected_ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) && + REGS(m_ctx)->surface_antialias() != rsx::surface_antialiasing::center_1_sample && + backend_config.supports_hw_msaa) { - return 0; + expected_ctrl |= RSX_SHADER_CONTROL_ROP_MULTISAMPLED; } - u32 expected_ctrl = 0; + // Depth compare + if (!g_cfg.video.emulate_depth_compare) [[ likely ]] + { + return expected_ctrl; + } + + if (REGS(m_ctx)->current_draw_clause.classify_mode() != primitive_class::polygon) + { + return expected_ctrl; + } if (m_framebuffer_layout.zeta_address && - m_ctx->register_state->depth_test_enabled() && - m_ctx->register_state->depth_func() == rsx::comparison_function::equal) + REGS(m_ctx)->depth_test_enabled() && + REGS(m_ctx)->depth_func() == rsx::comparison_function::equal) { expected_ctrl |= RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE; + } - if (backend_config.supports_hw_msaa && - m_ctx->register_state->surface_antialias() != rsx::surface_antialiasing::center_1_sample) - { - expected_ctrl |= RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER; - } + // Resolve MSAA here if needed + if ((expected_ctrl & (RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE | RSX_SHADER_CONTROL_ROP_MULTISAMPLED)) == RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE && + REGS(m_ctx)->surface_antialias() != rsx::surface_antialiasing::center_1_sample && + backend_config.supports_hw_msaa) + { + expected_ctrl |= RSX_SHADER_CONTROL_ROP_MULTISAMPLED; } return expected_ctrl; @@ -2091,7 +2244,6 @@ namespace rsx { m_program_cache_hint.invalidate(m_graphics_state.load()); - constexpr u32 fs_export_config_mask = (RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE | RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER); if (u32 export_ctrl = get_fragment_program_export_config(); (current_fragment_program.ctrl & fs_export_config_mask) != export_ctrl) { @@ -2172,46 +2324,45 @@ namespace rsx m_graphics_state.clear(rsx::pipeline_state::fragment_program_dirty); - current_fragment_program.ctrl = m_ctx->register_state->shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT | RSX_SHADER_CONTROL_USES_KIL); - current_fragment_program.texcoord_control_mask = m_ctx->register_state->texcoord_control_mask(); - current_fragment_program.two_sided_lighting = m_ctx->register_state->two_side_light_en(); - current_fragment_program.mrt_buffers_count = rsx::utility::get_mrt_buffers_count(m_ctx->register_state->surface_color_target()); + current_fragment_program.ctrl &= fs_export_config_mask; + current_fragment_program.ctrl |= REGS(m_ctx)->shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT | RSX_SHADER_CONTROL_USES_KIL); + current_fragment_program.texcoord_control_mask = REGS(m_ctx)->texcoord_control_mask(); + current_fragment_program.two_sided_lighting = REGS(m_ctx)->two_side_light_en(); + current_fragment_program.mrt_buffers_count = rsx::utility::get_mrt_buffers_count(REGS(m_ctx)->surface_color_target()); - if (m_ctx->register_state->shade_mode() == rsx::shading_mode::flat && + if (REGS(m_ctx)->shade_mode() == rsx::shading_mode::flat && backend_config.supports_last_provoking_vertex) { current_fragment_program.ctrl |= RSX_SHADER_CONTROL_FLAT_SHADING; } - if (m_ctx->register_state->current_draw_clause.classify_mode() == primitive_class::polygon) + if (REGS(m_ctx)->current_draw_clause.classify_mode() == primitive_class::polygon) { if (!backend_config.supports_normalized_barycentrics) { current_fragment_program.ctrl |= RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION; } - if (m_ctx->register_state->polygon_stipple_enabled()) + if (REGS(m_ctx)->polygon_stipple_enabled()) { current_fragment_program.ctrl |= RSX_SHADER_CONTROL_POLYGON_STIPPLE; } - - current_fragment_program.ctrl |= get_fragment_program_export_config(); } - else if (m_ctx->register_state->point_sprite_enabled() && - m_ctx->register_state->current_draw_clause.primitive == primitive_type::points) + else if (REGS(m_ctx)->point_sprite_enabled() && + REGS(m_ctx)->current_draw_clause.primitive == primitive_type::points) { // Set high word of the control mask to store point sprite control - current_fragment_program.texcoord_control_mask |= u32(m_ctx->register_state->point_sprite_control_mask()) << 16; + current_fragment_program.texcoord_control_mask |= u32(REGS(m_ctx)->point_sprite_control_mask()) << 16; } - if (m_ctx->register_state->alpha_test_enabled()) + if (REGS(m_ctx)->alpha_test_enabled()) { current_fragment_program.ctrl |= RSX_SHADER_CONTROL_ALPHA_TEST; } - if (m_ctx->register_state->msaa_alpha_to_coverage_enabled()) + if (REGS(m_ctx)->msaa_alpha_to_coverage_enabled()) { - const bool is_multiple_samples = m_ctx->register_state->surface_antialias() != rsx::surface_antialiasing::center_1_sample; + const bool is_multiple_samples = REGS(m_ctx)->surface_antialias() != rsx::surface_antialiasing::center_1_sample; if (!backend_config.supports_hw_a2c || (!is_multiple_samples && !backend_config.supports_hw_a2c_1spp)) { // Emulation required @@ -2220,7 +2371,7 @@ namespace rsx } // Check if framebuffer is actually an XRGB format and not a WZYX format - switch (m_ctx->register_state->surface_color()) + switch (REGS(m_ctx)->surface_color()) { case rsx::surface_color_format::w16z16y16x16: case rsx::surface_color_format::w32z32y32x32: @@ -2231,11 +2382,11 @@ namespace rsx // Integer framebuffer formats. These can support sRGB output as well as some special rules for output quantization. current_fragment_program.ctrl |= RSX_SHADER_CONTROL_8BIT_FRAMEBUFFER; if (!(current_fragment_program.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) && // Cannot output sRGB from 32-bit registers - m_ctx->register_state->framebuffer_srgb_enabled()) + REGS(m_ctx)->framebuffer_srgb_enabled()) { current_fragment_program.ctrl |= RSX_SHADER_CONTROL_SRGB_FRAMEBUFFER; } - if (m_ctx->register_state->surface_is_swizzle_remapped()) + if (REGS(m_ctx)->surface_is_swizzle_remapped()) { current_fragment_program.ctrl |= RSX_SHADER_CONTROL_ROP_OUTPUT_REMAP; } @@ -2249,7 +2400,7 @@ namespace rsx { if (!(textures_ref & 1)) continue; - auto &tex = m_ctx->register_state->fragment_textures[i]; + auto &tex = REGS(m_ctx)->fragment_textures[i]; current_fp_texture_state.clear(i); if (!tex.enabled() || sampler_descriptors[i]->format_class == RSX_FORMAT_CLASS_UNDEFINED) @@ -2444,7 +2595,7 @@ namespace rsx if (current_fragment_program.ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) { //Check that the depth stage is not disabled - if (!m_ctx->register_state->depth_test_enabled()) + if (!REGS(m_ctx)->depth_test_enabled()) { rsx_log.trace("FS exports depth component but depth test is disabled (INVALID_OPERATION)"); } diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 598226085b68..701741d8d2ac 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -92,6 +92,7 @@ namespace rsx bool supports_host_gpu_labels; // Advanced host synchronization bool supports_normalized_barycentrics; // Basically all GPUs except NVIDIA have properly normalized barycentrics bool supports_last_provoking_vertex; // Flat shading using RSX's last-vertex convention + bool supports_programmable_blending; // Can handle programmable blending requests }; struct desync_fifo_cmd_info diff --git a/rpcs3/Emu/RSX/VK/VKDraw.cpp b/rpcs3/Emu/RSX/VK/VKDraw.cpp index a63deca25f3e..77927e27b56c 100644 --- a/rpcs3/Emu/RSX/VK/VKDraw.cpp +++ b/rpcs3/Emu/RSX/VK/VKDraw.cpp @@ -147,7 +147,14 @@ VkRenderPass VKGSRender::get_render_pass() void VKGSRender::invalidate_render_pass() { // Regenerate renderpass key for the next draw call - if (const auto key = vk::get_renderpass_key(m_fbo_images, m_current_renderpass_key); + std::vector input_attachments{}; + if (current_fragment_program.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) + { + input_attachments.resize(m_draw_buffers.size()); + std::iota(input_attachments.begin(), input_attachments.end(), 0); + } + + if (const auto key = vk::get_renderpass_key(m_fbo_images, m_current_renderpass_key, input_attachments); key != m_current_renderpass_key) { m_current_renderpass_key = key; @@ -168,7 +175,7 @@ void VKGSRender::update_draw_state() vkCmdSetLineWidth(*m_current_command_buffer, actual_line_width); } - if (rsx::method_registers.blend_enabled()) + if (rsx::method_registers.blend_enabled_mask()) { // Update blend constants auto blend_colors = rsx::get_constant_blend_colors(); @@ -781,6 +788,19 @@ bool VKGSRender::bind_texture_env() m_program->bind_uniform({ *view, vk::null_sampler() }, vk::glsl::binding_set_index_fragment, m_fs_binding_table->frag_depth_input_location); } + if (current_fragment_program.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) + { + ensure(current_fragment_program.mrt_buffers_count == m_draw_buffers.size()); + const auto remap = rsx::default_remap_vector.with_encoding(vk::VK_REMAP_IDENTITY); + + for (u32 i = 0; i < current_fragment_program.mrt_buffers_count; ++i) + { + auto viewable = static_cast(m_fbo_images[i]); + const auto view = viewable->get_view(remap); + m_program->bind_uniform(*view, vk::glsl::binding_set_index_fragment, m_fs_binding_table->frag_src_location[i]); + } + } + return out_of_memory; } @@ -1088,6 +1108,27 @@ void VKGSRender::emit_geometry(u32 sub_index) reload_state = true; }); + if (current_fragment_program.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) + { + // Subpass inter-draw dependency for input attachment reads. Preserves open renderpasses. + for (u32 i = 0; i < current_fragment_program.mrt_buffers_count; ++i) + { + vk::insert_image_memory_barrier( + *m_current_command_buffer, + m_fbo_images[i]->value, + m_fbo_images[i]->current_layout, + m_fbo_images[i]->current_layout, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, + { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }, + true, + VK_DEPENDENCY_BY_REGION_BIT + ); + } + } + // Bind both pipe and descriptors in one go // FIXME: We only need to rebind the pipeline when reload state is set. Flags? m_program->bind(*m_current_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS); diff --git a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp index ffd4842fe796..af6a25a4425e 100644 --- a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp @@ -96,6 +96,15 @@ void VKFragmentDecompilerThread::prepareBindingTable() { vk_prog->binding_table.frag_depth_input_location = location++; } + + std::memset(vk_prog->binding_table.frag_src_location, 0xff, sizeof(vk_prog->binding_table.frag_src_location)); + if (m_prog.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) + { + for (u32 i = 0; i < m_prog.mrt_buffers_count; ++i) + { + vk_prog->binding_table.frag_src_location[i] = location++; + } + } } void VKFragmentDecompilerThread::insertHeader(std::stringstream & OS) @@ -247,7 +256,7 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) if (m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) { - const auto frag_depth_type = (m_prog.ctrl & RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER) + const auto frag_depth_type = (m_prog.ctrl & RSX_SHADER_CONTROL_ROP_MULTISAMPLED) ? "sampler2DMS" : "sampler2D"; @@ -262,6 +271,26 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) )); } + if (m_prog.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) + { + const std::string_view att_type = (m_prog.ctrl & RSX_SHADER_CONTROL_ROP_MULTISAMPLED) + ? "subpassInputMS"sv + : "subpassInput"sv; + + for (u32 i = 0; i < m_prog.mrt_buffers_count; ++i) + { + OS << "layout(input_attachment_index= " << i << ", set=" << vk::glsl::binding_set_index_fragment << ", binding=" << vk_prog->binding_table.frag_src_location[i] << ") uniform " << att_type << " frag_src_" << i << ";\n"; + + inputs.push_back(vk::glsl::program_input::make( + glsl::glsl_fragment_program, + fmt::format("frag_src_%u", i), + vk::glsl::input_type_attachment, + vk::glsl::binding_set_index_fragment, + vk_prog->binding_table.frag_src_location[i] + )); + } + } + // Draw params are always provided by vertex program. Instead of pointer chasing, they're provided as varyings. if (!(m_prog.ctrl & RSX_SHADER_CONTROL_INTERPRETER_MODEL)) { @@ -300,6 +329,29 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) OS << " uvec4 stipple_pattern[];\n"; OS << "};\n\n"; + if (m_prog.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) + { + OS << + "layout(push_constant) uniform push_constants_block\n" + "{\n" + " layout(offset = 4) uint blend_eqn;\n" + " uint blend_sfactors;\n" + " uint blend_dfactors;\n" + " vec4 blend_constants;\n" + "};\n\n"; + + vk::glsl::program_input push_constants + { + .domain = glsl::glsl_fragment_program, + .type = vk::glsl::input_type_push_constant, + .bound_data = vk::glsl::push_constant_ref{ .offset = 4, .size = 28 }, + .set = vk::glsl::binding_set_index_fragment, + .location = umax, + .name = "push_constants_block" + }; + inputs.push_back(std::move(push_constants)); + } + vk::glsl::program_input in { .domain = glsl::glsl_fragment_program, @@ -357,6 +409,7 @@ void VKFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) m_shader_props.ROP_polygon_stipple_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_POLYGON_STIPPLE); m_shader_props.ROP_discard = !!(m_prog.ctrl & RSX_SHADER_CONTROL_USES_KIL); m_shader_props.ROP_channel_remap = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ROP_OUTPUT_REMAP); + m_shader_props.ROP_programmable_blend = !!(m_prog.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING); m_shader_props.require_tex1D_ops = properties.has_tex1D; m_shader_props.require_tex2D_ops = properties.has_tex2D; @@ -365,7 +418,7 @@ void VKFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) m_shader_props.require_alpha_kill = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL); m_shader_props.require_color_format_convert = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT); m_shader_props.emulate_depth_compare = !!(m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE); - m_shader_props.depth_buffer_multisampled = !!(m_prog.ctrl & RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER); + m_shader_props.ROP_output_multisampled = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ROP_MULTISAMPLED); // Declare global constants if (m_shader_props.require_fog_read) @@ -490,16 +543,25 @@ void VKFragmentDecompilerThread::insertMainEnd(std::stringstream & OS) OS << "void main()\n"; OS << "{\n"; - if ((m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TEST) || - (m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) || - (m_prog.ctrl & RSX_SHADER_CONTROL_ROP_OUTPUT_REMAP)) + constexpr u32 ROP_control_access_options = + RSX_SHADER_CONTROL_ALPHA_TEST | + RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE | + RSX_SHADER_CONTROL_ROP_OUTPUT_REMAP | + RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING; + + if (m_prog.ctrl & ROP_control_access_options) { - OS << - " const uint rop_control = fs_contexts[_fs_context_offset].rop_control;\n" - " const float alpha_ref = fs_contexts[_fs_context_offset].alpha_ref;\n\n"; + OS << " const uint rop_control = fs_contexts[_fs_context_offset].rop_control;\n"; + + if (m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TEST) + { + OS << " const float alpha_ref = fs_contexts[_fs_context_offset].alpha_ref;\n"; + } + + OS << "\n"; } - ::glsl::insert_rop_init(OS); + ::glsl::insert_rop_init(OS, m_prog.mrt_buffers_count); OS << "\n" << " fs_main();\n\n"; diff --git a/rpcs3/Emu/RSX/VK/VKFragmentProgram.h b/rpcs3/Emu/RSX/VK/VKFragmentProgram.h index b17a67f1c5b6..ea9144b3f1e7 100644 --- a/rpcs3/Emu/RSX/VK/VKFragmentProgram.h +++ b/rpcs3/Emu/RSX/VK/VKFragmentProgram.h @@ -76,6 +76,7 @@ class VKFragmentProgram u32 ftex_location[16]; // Texture locations array u32 ftex_stencil_location[16]; // Texture stencil mirror array u32 frag_depth_input_location = umax; // Fragment depth compare + u32 frag_src_location[4]; // Fragment input attachment locations } binding_table; diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index a5b446407278..be741d1ab660 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -159,13 +159,15 @@ namespace vk switch (op) { case rsx::blend_equation::add_signed: - rsx_log.trace("blend equation add_signed used. Emulating using FUNC_ADD"); + rsx_log.error("blend equation add_signed used. Emulating using FUNC_ADD"); [[fallthrough]]; - case rsx::blend_equation::add: + case rsx::blend_equation::add: return VK_BLEND_OP_ADD; + case rsx::blend_equation::reverse_add_signed: + rsx_log.error("blend equation reverse_add_signed used. Emulating using FUNC_ADD"); return VK_BLEND_OP_ADD; case rsx::blend_equation::subtract: return VK_BLEND_OP_SUBTRACT; case rsx::blend_equation::reverse_subtract_signed: - rsx_log.trace("blend equation reverse_subtract_signed used. Emulating using FUNC_REVERSE_SUBTRACT"); + rsx_log.error("blend equation reverse_subtract_signed used. Emulating using FUNC_REVERSE_SUBTRACT"); [[fallthrough]]; case rsx::blend_equation::reverse_subtract: return VK_BLEND_OP_REVERSE_SUBTRACT; case rsx::blend_equation::min: return VK_BLEND_OP_MIN; @@ -242,12 +244,14 @@ namespace vk // TODO: This should be deprecated soon (kd) vk::pipeline_props decode_rsx_state( + const rsx::context* ctx, const vertex_input_assembly_state& vertex_input, vk::render_target* ds, const rsx::backend_configuration& backend_config, u8 num_draw_buffers, u8 num_rasterization_samples, - bool depth_bounds_support) + bool depth_bounds_support, + bool force_disable_blending) { vk::pipeline_props properties{}; @@ -257,32 +261,32 @@ namespace vk // Rasterizer state properties.state.set_attachment_count(num_draw_buffers); - properties.state.set_front_face(vk::get_front_face(rsx::method_registers.front_face_mode())); - properties.state.enable_depth_clamp(rsx::method_registers.depth_clamp_enabled() || !rsx::method_registers.depth_clip_enabled()); + properties.state.set_front_face(vk::get_front_face(REGS(ctx)->front_face_mode())); + properties.state.enable_depth_clamp(REGS(ctx)->depth_clamp_enabled() || !REGS(ctx)->depth_clip_enabled()); properties.state.enable_depth_bias(true); properties.state.enable_depth_bounds_test(depth_bounds_support); - if (rsx::method_registers.depth_test_enabled()) + if (REGS(ctx)->depth_test_enabled()) { //NOTE: Like stencil, depth write is meaningless without depth test - properties.state.set_depth_mask(rsx::method_registers.depth_write_enabled()); - properties.state.enable_depth_test(vk::get_compare_func(rsx::method_registers.depth_func())); + properties.state.set_depth_mask(REGS(ctx)->depth_write_enabled()); + properties.state.enable_depth_test(vk::get_compare_func(REGS(ctx)->depth_func())); } - if (rsx::method_registers.cull_face_enabled()) + if (REGS(ctx)->cull_face_enabled()) { - properties.state.enable_cull_face(vk::get_cull_face(rsx::method_registers.cull_face_mode())); + properties.state.enable_cull_face(vk::get_cull_face(REGS(ctx)->cull_face_mode())); } - const auto host_write_mask = rsx::get_write_output_mask(rsx::method_registers.surface_color()); + const auto host_write_mask = rsx::get_write_output_mask(REGS(ctx)->surface_color()); for (uint index = 0; index < num_draw_buffers; ++index) { - bool color_mask_b = rsx::method_registers.color_mask_b(index); - bool color_mask_g = rsx::method_registers.color_mask_g(index); - bool color_mask_r = rsx::method_registers.color_mask_r(index); - bool color_mask_a = rsx::method_registers.color_mask_a(index); + bool color_mask_b = REGS(ctx)->color_mask_b(index); + bool color_mask_g = REGS(ctx)->color_mask_g(index); + bool color_mask_r = REGS(ctx)->color_mask_r(index); + bool color_mask_a = REGS(ctx)->color_mask_a(index); - switch (rsx::method_registers.surface_color()) + switch (REGS(ctx)->surface_color()) { case rsx::surface_color_format::b8: rsx::get_b8_colormask(color_mask_r, color_mask_g, color_mask_b, color_mask_a); @@ -303,35 +307,27 @@ namespace vk } // LogicOp and Blend are mutually exclusive. If both are enabled, LogicOp takes precedence. - if (rsx::method_registers.logic_op_enabled()) + if (REGS(ctx)->logic_op_enabled()) { - properties.state.enable_logic_op(vk::get_logic_op(rsx::method_registers.logic_operation())); + properties.state.enable_logic_op(vk::get_logic_op(REGS(ctx)->logic_operation())); } - else + else if (!force_disable_blending) { - bool mrt_blend_enabled[] = - { - rsx::method_registers.blend_enabled(), - rsx::method_registers.blend_enabled_surface_1(), - rsx::method_registers.blend_enabled_surface_2(), - rsx::method_registers.blend_enabled_surface_3() - }; - VkBlendFactor sfactor_rgb, sfactor_a, dfactor_rgb, dfactor_a; VkBlendOp equation_rgb, equation_a; - if (mrt_blend_enabled[0] || mrt_blend_enabled[1] || mrt_blend_enabled[2] || mrt_blend_enabled[3]) + if (const auto blend_enabled = REGS(ctx)->blend_enabled_mask()) { - sfactor_rgb = vk::get_blend_factor(rsx::method_registers.blend_func_sfactor_rgb()); - sfactor_a = vk::get_blend_factor(rsx::method_registers.blend_func_sfactor_a()); - dfactor_rgb = vk::get_blend_factor(rsx::method_registers.blend_func_dfactor_rgb()); - dfactor_a = vk::get_blend_factor(rsx::method_registers.blend_func_dfactor_a()); - equation_rgb = vk::get_blend_op(rsx::method_registers.blend_equation_rgb()); - equation_a = vk::get_blend_op(rsx::method_registers.blend_equation_a()); + sfactor_rgb = vk::get_blend_factor(REGS(ctx)->blend_func_sfactor_rgb()); + sfactor_a = vk::get_blend_factor(REGS(ctx)->blend_func_sfactor_a()); + dfactor_rgb = vk::get_blend_factor(REGS(ctx)->blend_func_dfactor_rgb()); + dfactor_a = vk::get_blend_factor(REGS(ctx)->blend_func_dfactor_a()); + equation_rgb = vk::get_blend_op(REGS(ctx)->blend_equation_rgb()); + equation_a = vk::get_blend_op(REGS(ctx)->blend_equation_a()); for (u8 idx = 0; idx < num_draw_buffers; ++idx) { - if (mrt_blend_enabled[idx]) + if (blend_enabled & (1u << idx)) { properties.state.enable_blend(idx, sfactor_rgb, sfactor_a, dfactor_rgb, dfactor_a, equation_rgb, equation_a); } @@ -339,31 +335,31 @@ namespace vk } } - if (rsx::method_registers.stencil_test_enabled()) + if (REGS(ctx)->stencil_test_enabled()) { - if (!rsx::method_registers.two_sided_stencil_test_enabled()) + if (!REGS(ctx)->two_sided_stencil_test_enabled()) { properties.state.enable_stencil_test( - vk::get_stencil_op(rsx::method_registers.stencil_op_fail()), - vk::get_stencil_op(rsx::method_registers.stencil_op_zfail()), - vk::get_stencil_op(rsx::method_registers.stencil_op_zpass()), - vk::get_compare_func(rsx::method_registers.stencil_func()), + vk::get_stencil_op(REGS(ctx)->stencil_op_fail()), + vk::get_stencil_op(REGS(ctx)->stencil_op_zfail()), + vk::get_stencil_op(REGS(ctx)->stencil_op_zpass()), + vk::get_compare_func(REGS(ctx)->stencil_func()), 0xFF, 0xFF); //write mask, func_mask, ref are dynamic } else { properties.state.enable_stencil_test_separate(0, - vk::get_stencil_op(rsx::method_registers.stencil_op_fail()), - vk::get_stencil_op(rsx::method_registers.stencil_op_zfail()), - vk::get_stencil_op(rsx::method_registers.stencil_op_zpass()), - vk::get_compare_func(rsx::method_registers.stencil_func()), + vk::get_stencil_op(REGS(ctx)->stencil_op_fail()), + vk::get_stencil_op(REGS(ctx)->stencil_op_zfail()), + vk::get_stencil_op(REGS(ctx)->stencil_op_zpass()), + vk::get_compare_func(REGS(ctx)->stencil_func()), 0xFF, 0xFF); //write mask, func_mask, ref are dynamic properties.state.enable_stencil_test_separate(1, - vk::get_stencil_op(rsx::method_registers.back_stencil_op_fail()), - vk::get_stencil_op(rsx::method_registers.back_stencil_op_zfail()), - vk::get_stencil_op(rsx::method_registers.back_stencil_op_zpass()), - vk::get_compare_func(rsx::method_registers.back_stencil_func()), + vk::get_stencil_op(REGS(ctx)->back_stencil_op_fail()), + vk::get_stencil_op(REGS(ctx)->back_stencil_op_zfail()), + vk::get_stencil_op(REGS(ctx)->back_stencil_op_zpass()), + vk::get_compare_func(REGS(ctx)->back_stencil_func()), 0xFF, 0xFF); //write mask, func_mask, ref are dynamic } @@ -384,13 +380,13 @@ namespace vk if (backend_config.supports_hw_a2c || num_rasterization_samples > 1) { - const bool alpha_to_one_enable = rsx::method_registers.msaa_alpha_to_one_enabled() && backend_config.supports_hw_a2one; + const bool alpha_to_one_enable = REGS(ctx)->msaa_alpha_to_one_enabled() && backend_config.supports_hw_a2one; properties.state.set_multisample_state( num_rasterization_samples, - rsx::method_registers.msaa_sample_mask(), - rsx::method_registers.msaa_enabled(), - rsx::method_registers.msaa_alpha_to_coverage_enabled(), + REGS(ctx)->msaa_sample_mask(), + REGS(ctx)->msaa_enabled(), + REGS(ctx)->msaa_alpha_to_coverage_enabled(), alpha_to_one_enable); // A problem observed on multiple GPUs is that interior geometry edges can resolve 0 samples unless we force shading rate of 1. @@ -642,6 +638,7 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) backend_config.supports_multidraw = true; backend_config.supports_hw_instanced_rendering = true; + backend_config.supports_programmable_blending = true; backend_config.supports_last_provoking_vertex = m_device->get_provoking_vertex_last_support(); if (!backend_config.supports_last_provoking_vertex) @@ -1830,18 +1827,26 @@ bool VKGSRender::load_program() } } + if (!!(current_fragment_program.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) != + vk::renderpass_has_input_attachments(m_current_renderpass_key)) + { + invalidate_render_pass(); + } + auto &vertex_program = current_vertex_program; auto &fragment_program = current_fragment_program; if (m_graphics_state & rsx::pipeline_state::pipeline_config_dirty) { vk::pipeline_props properties = vk::decode_rsx_state( + m_ctx, vertex_state, m_rtts.m_bound_depth_stencil.second, backend_config, static_cast(m_draw_buffers.size()), u8((m_current_renderpass_key >> 16) & 0xF), - m_device->get_depth_bounds_support() + m_device->get_depth_bounds_support(), + !!(current_fragment_program.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) ); properties.renderpass_key = m_current_renderpass_key; @@ -2290,6 +2295,29 @@ void VKGSRender::update_vertex_env(u32 id, const vk::vertex_upload_info& vertex_ 4, &push_val); + if (current_fragment_program.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) + { + // TODO: This should be cached aggressively. + u32 blend_config[7]; + blend_config[0] = REGS(m_ctx)->registers[NV4097_SET_BLEND_EQUATION]; + blend_config[1] = REGS(m_ctx)->registers[NV4097_SET_BLEND_FUNC_SFACTOR]; + blend_config[2] = REGS(m_ctx)->registers[NV4097_SET_BLEND_FUNC_DFACTOR]; + + const auto blend_colors = rsx::get_constant_blend_colors(); + blend_config[3] = std::bit_cast(blend_colors[0]); + blend_config[4] = std::bit_cast(blend_colors[1]); + blend_config[5] = std::bit_cast(blend_colors[2]); + blend_config[6] = std::bit_cast(blend_colors[3]); + + vkCmdPushConstants( + *m_current_command_buffer, + m_program->layout(), + VK_SHADER_STAGE_FRAGMENT_BIT, + 4, + 28, + blend_config); + } + // Now actually fill in the data m_draw_processor.fill_vertex_layout_state( m_vertex_layout, @@ -2642,7 +2670,15 @@ void VKGSRender::prepare_rtts(rsx::framebuffer_creation_context context) } } - m_current_renderpass_key = vk::get_renderpass_key(m_fbo_images); + std::vector input_attachments{}; + if ((current_fragment_program.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING) && + !m_graphics_state.test(rsx::pipeline_state::fragment_program_state_dirty)) + { + input_attachments.resize(m_draw_buffers.size()); + std::iota(input_attachments.begin(), input_attachments.end(), 0); + } + + m_current_renderpass_key = vk::get_renderpass_key(m_fbo_images, input_attachments); m_cached_renderpass = vk::get_renderpass(*m_device, m_current_renderpass_key); // Search old framebuffers for this same configuration @@ -2654,7 +2690,7 @@ void VKGSRender::prepare_rtts(rsx::framebuffer_creation_context context) m_draw_fbo->release(); } - m_draw_fbo = vk::get_framebuffer(*m_device, fbo_width, fbo_height, VK_FALSE, m_cached_renderpass, m_fbo_images); + m_draw_fbo = vk::get_framebuffer(*m_device, fbo_width, fbo_height, vk::to_bool32(!input_attachments.empty()), m_cached_renderpass, m_fbo_images); m_draw_fbo->add_ref(); set_viewport(); diff --git a/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp b/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp index 7ae53c4c5ee3..803dbf99e53a 100644 --- a/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp +++ b/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp @@ -60,6 +60,8 @@ namespace vk return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; case input_type_storage_texture: return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + case input_type_attachment: + return VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; default: fmt::throw_exception("Unexpected program input type %d", static_cast(type)); } diff --git a/rpcs3/Emu/RSX/VK/VKProgramPipeline.h b/rpcs3/Emu/RSX/VK/VKProgramPipeline.h index 2beb2b90eb5c..79b83717b35e 100644 --- a/rpcs3/Emu/RSX/VK/VKProgramPipeline.h +++ b/rpcs3/Emu/RSX/VK/VKProgramPipeline.h @@ -22,6 +22,7 @@ namespace vk input_type_storage_buffer, input_type_storage_texture, input_type_push_constant, + input_type_attachment, // Meta input_type_max_enum, diff --git a/rpcs3/Emu/RSX/VK/VKRenderPass.cpp b/rpcs3/Emu/RSX/VK/VKRenderPass.cpp index 9dc578ef6689..0f3957a2dd16 100644 --- a/rpcs3/Emu/RSX/VK/VKRenderPass.cpp +++ b/rpcs3/Emu/RSX/VK/VKRenderPass.cpp @@ -189,17 +189,23 @@ namespace vk return key.encoded; } - u64 get_renderpass_key(const std::vector& images, u64 previous_key) + u64 get_renderpass_key(const std::vector& images, u64 previous_key, const std::vector& input_attachment_ids) { // Partial update; assumes compatible renderpass keys renderpass_key_blob key(previous_key); key.layout_blob = 0; + key.input_attachments_mask = 0; for (u32 i = 0; i < ::size32(images); ++i) { key.set_layout(i, images[i]->current_layout); } + for (const auto& ref_id : input_attachment_ids) + { + key.set_input_attachment(ref_id); + } + return key.encoded; } @@ -327,11 +333,22 @@ namespace vk subpass.pColorAttachments = attachment_count? attachment_references.data() : nullptr; subpass.pDepthStencilAttachment = depth_format? &attachment_references.back() : nullptr; + rsx::simple_array subpass_dependencies; const auto input_attachments = key.get_input_attachments(); if (!input_attachments.empty()) { subpass.inputAttachmentCount = ::size32(input_attachments); subpass.pInputAttachments = input_attachments.data(); + + subpass_dependencies.push_back({ + .srcSubpass = 0, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, + .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT + }); } VkRenderPassCreateInfo rp_info = {}; @@ -340,6 +357,8 @@ namespace vk rp_info.pAttachments = attachments.data(); rp_info.subpassCount = 1; rp_info.pSubpasses = &subpass; + rp_info.dependencyCount = subpass_dependencies.size(); + rp_info.pDependencies = subpass_dependencies.data(); VkRenderPass result; CHECK_RESULT(vkCreateRenderPass(dev, &rp_info, NULL, &result)); @@ -348,6 +367,12 @@ namespace vk return result; } + bool renderpass_has_input_attachments(u64 renderpass_key) + { + renderpass_key_blob key(renderpass_key); + return key.input_attachments_mask != 0u; + } + void clear_renderpass_cache(VkDevice dev) { // Wipe current status diff --git a/rpcs3/Emu/RSX/VK/VKRenderPass.h b/rpcs3/Emu/RSX/VK/VKRenderPass.h index 5c23e749dfab..7b9494c93456 100644 --- a/rpcs3/Emu/RSX/VK/VKRenderPass.h +++ b/rpcs3/Emu/RSX/VK/VKRenderPass.h @@ -9,11 +9,13 @@ namespace vk class command_buffer; u64 get_renderpass_key(const std::vector& images, const std::vector& input_attachment_ids = {}); - u64 get_renderpass_key(const std::vector& images, u64 previous_key); + u64 get_renderpass_key(const std::vector& images, u64 previous_key, const std::vector& input_attachment_ids = {}); u64 get_renderpass_key(VkFormat surface_format, u8 sample_count = 1); u64 get_renderpass_key(VkFormat color_format, VkFormat depth_format, u8 sample_count = 1); VkRenderPass get_renderpass(VkDevice dev, u64 renderpass_key); + bool renderpass_has_input_attachments(u64 renderpass_key); + void clear_renderpass_cache(VkDevice dev); // Renderpass scope management helpers. diff --git a/rpcs3/Emu/RSX/VK/VKRenderTargets.h b/rpcs3/Emu/RSX/VK/VKRenderTargets.h index 10df539b8618..3ee5010d8ccb 100644 --- a/rpcs3/Emu/RSX/VK/VKRenderTargets.h +++ b/rpcs3/Emu/RSX/VK/VKRenderTargets.h @@ -166,17 +166,24 @@ namespace vk using download_buffer_object = void*; using barrier_descriptor_t = rsx::deferred_clipped_region; - static std::pair get_attachment_create_flags(VkFormat format, [[maybe_unused]] u8 samples) + static std::pair get_attachment_create_flags(VkFormat format, [[maybe_unused]] u8 samples, bool depth) { + VkImageUsageFlags usage_flags = 0; + if (!depth) + { + // For programmable blending + usage_flags = VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; + } + if (g_cfg.video.strict_rendering_mode) { - return {}; + return { usage_flags, 0 }; } // If we have driver support for FBO loops, set the usage flag for it. if (vk::get_current_renderer()->get_framebuffer_loops_support()) { - return { VK_IMAGE_USAGE_ATTACHMENT_FEEDBACK_LOOP_BIT_EXT, VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT }; + return { usage_flags | VK_IMAGE_USAGE_ATTACHMENT_FEEDBACK_LOOP_BIT_EXT, VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT }; } // Workarounds to force transition to GENERAL to decompress. @@ -188,7 +195,7 @@ namespace vk format_features.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) { // Only set if supported by hw - return { VK_IMAGE_USAGE_STORAGE_BIT, 0 }; + return { usage_flags | VK_IMAGE_USAGE_STORAGE_BIT, 0 }; } break; case driver_vendor::AMD: @@ -196,7 +203,7 @@ namespace vk if (vk::get_chip_family() >= chip_class::AMD_navi1x) { // Only needed for GFX10+ - return { 0, VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT }; + return { usage_flags, VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT }; } break; default: @@ -214,7 +221,7 @@ namespace vk break; } - return {}; + return { usage_flags, 0 }; } static std::unique_ptr create_new_surface( @@ -241,7 +248,7 @@ namespace vk sample_layout = rsx::surface_sample_layout::null; } - auto [usage_flags, create_flags] = get_attachment_create_flags(requested_format, samples); + auto [usage_flags, create_flags] = get_attachment_create_flags(requested_format, samples, false); usage_flags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; if (samples == 1) [[likely]] @@ -314,7 +321,7 @@ namespace vk sample_layout = rsx::surface_sample_layout::null; } - auto [usage_flags, create_flags] = get_attachment_create_flags(requested_format, samples); + auto [usage_flags, create_flags] = get_attachment_create_flags(requested_format, samples, true); usage_flags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; if (samples == 1) [[likely]] diff --git a/rpcs3/Emu/RSX/VK/VulkanAPI.h b/rpcs3/Emu/RSX/VK/VulkanAPI.h index ad4063465922..57e366c2a94e 100644 --- a/rpcs3/Emu/RSX/VK/VulkanAPI.h +++ b/rpcs3/Emu/RSX/VK/VulkanAPI.h @@ -59,4 +59,6 @@ constexpr VkStructureType VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNIFORM_BUFFE namespace vk { void init(); + + inline VkBool32 to_bool32(bool x) { return x ? VK_TRUE : VK_FALSE; } } diff --git a/rpcs3/Emu/RSX/VK/vkutils/barriers.cpp b/rpcs3/Emu/RSX/VK/vkutils/barriers.cpp index 6a7e51874944..385b112c4786 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/barriers.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/barriers.cpp @@ -13,7 +13,8 @@ namespace vk VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage, VkAccessFlags src_mask, VkAccessFlags dst_mask, const VkImageSubresourceRange& range, - bool preserve_renderpass) + bool preserve_renderpass, + VkFlags flags) { if (!preserve_renderpass && vk::is_renderpass_open(cmd)) { @@ -31,7 +32,7 @@ namespace vk barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.subresourceRange = range; - vkCmdPipelineBarrier(cmd, src_stage, dst_stage, 0, 0, nullptr, 0, nullptr, 1, &barrier); + vkCmdPipelineBarrier(cmd, src_stage, dst_stage, flags, 0, nullptr, 0, nullptr, 1, &barrier); } void insert_buffer_memory_barrier( diff --git a/rpcs3/Emu/RSX/VK/vkutils/barriers.h b/rpcs3/Emu/RSX/VK/vkutils/barriers.h index aeda0d2ba08d..0c2b7db9b87e 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/barriers.h +++ b/rpcs3/Emu/RSX/VK/vkutils/barriers.h @@ -17,7 +17,7 @@ namespace vk void insert_image_memory_barrier(const vk::command_buffer& cmd, VkImage image, VkImageLayout current_layout, VkImageLayout new_layout, VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage, VkAccessFlags src_mask, VkAccessFlags dst_mask, - const VkImageSubresourceRange& range, bool preserve_renderpass = false); + const VkImageSubresourceRange& range, bool preserve_renderpass = false, VkFlags flags = 0); void insert_global_memory_barrier(const vk::command_buffer& cmd, VkPipelineStageFlags src_stage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, diff --git a/rpcs3/Emu/RSX/gcm_enums.h b/rpcs3/Emu/RSX/gcm_enums.h index dd5e54fbd8a2..fe5cf89eb36f 100644 --- a/rpcs3/Emu/RSX/gcm_enums.h +++ b/rpcs3/Emu/RSX/gcm_enums.h @@ -450,8 +450,8 @@ namespace gcm RSX_SHADER_CONTROL_USED_REGS_MASK = 0x0000000f, RSX_SHADER_CONTROL_USED_TEMP_REGS_MASK = 0xff000000, - RSX_SHADER_CONTROL_USES_KIL = 0x00000080, // program uses KIL op - RSX_SHADER_CONTROL_UNKNOWN0 = 0x00000400, // seemingly always set + RSX_SHADER_CONTROL_USES_KIL = 0x00000080, // program uses KIL op + RSX_SHADER_CONTROL_UNKNOWN0 = 0x00000400, // seemingly always set RSX_SHADER_CONTROL_UNKNOWN1 = 0x00008000, // seemingly set when srgb packer is used?? // Custom @@ -472,12 +472,14 @@ namespace gcm RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT = 0x04000000, // Allow format conversions (BX2, SNORM, SRGB, RENORM) RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE = 0x08000000, // Emulate depth comparisons - RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER = 0x10000000, // Z buffer is multisampled. Only affects depth comparison behavior at this time. + RSX_SHADER_CONTROL_ROP_MULTISAMPLED = 0x10000000, // ROP outputs are multisampled RSX_SHADER_CONTROL_ROP_OUTPUT_REMAP = 0x20000000, // ROP outputs need channel swizzles. + RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING = 0x40000000, // Enable programmable blending. // Meta - RSX_SHADER_CONTROL_META_USES_DISCARD = (RSX_SHADER_CONTROL_USES_KIL | RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL | RSX_SHADER_CONTROL_ALPHA_TEST | RSX_SHADER_CONTROL_POLYGON_STIPPLE | RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE) + RSX_SHADER_CONTROL_META_USES_DISCARD = (RSX_SHADER_CONTROL_USES_KIL | RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL | RSX_SHADER_CONTROL_ALPHA_TEST | RSX_SHADER_CONTROL_POLYGON_STIPPLE | RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE), + RSX_SHADER_CONTROL_META_GCM_FLAGS_MASK = 0x000007ff, // Reserved range for GCM flags. We don't care about most GCM-specific flags. }; // GCM Reports diff --git a/rpcs3/Emu/RSX/rsx_methods.cpp b/rpcs3/Emu/RSX/rsx_methods.cpp index a89793d4cb95..8aea1a4932ed 100644 --- a/rpcs3/Emu/RSX/rsx_methods.cpp +++ b/rpcs3/Emu/RSX/rsx_methods.cpp @@ -775,8 +775,8 @@ namespace rsx state_signals[NV4097_SET_ZMIN_MAX_CONTROL] = rsx::pipeline_config_dirty; state_signals[NV4097_SET_LOGIC_OP_ENABLE] = rsx::pipeline_config_dirty; state_signals[NV4097_SET_LOGIC_OP] = rsx::pipeline_config_dirty; - state_signals[NV4097_SET_BLEND_ENABLE] = rsx::pipeline_config_dirty; - state_signals[NV4097_SET_BLEND_ENABLE_MRT] = rsx::pipeline_config_dirty; + state_signals[NV4097_SET_BLEND_ENABLE] = rsx::pipeline_config_dirty | rsx::blend_config_dirty; + state_signals[NV4097_SET_BLEND_ENABLE_MRT] = rsx::pipeline_config_dirty | rsx::blend_config_dirty; state_signals[NV4097_SET_STENCIL_FUNC] = rsx::pipeline_config_dirty; state_signals[NV4097_SET_BACK_STENCIL_FUNC] = rsx::pipeline_config_dirty; state_signals[NV4097_SET_RESTART_INDEX_ENABLE] = rsx::pipeline_config_dirty; diff --git a/rpcs3/Emu/RSX/rsx_methods.h b/rpcs3/Emu/RSX/rsx_methods.h index de12730930cf..7cfb22ddc64a 100644 --- a/rpcs3/Emu/RSX/rsx_methods.h +++ b/rpcs3/Emu/RSX/rsx_methods.h @@ -490,6 +490,12 @@ namespace rsx return decode().blend_surface_d(); } + u32 blend_enabled_mask() const + { + const u32 base = decode().blend_enabled() ? 1u : 0u; + return (base | registers[NV4097_SET_BLEND_ENABLE_MRT]) & 0xf; + } + bool line_smooth_enabled() const { return decode().line_smooth_enabled(); @@ -759,6 +765,24 @@ namespace rsx return decode().target(); } + u32 surface_color_target_mask() const + { + switch (surface_color_target()) + { + case surface_target::surface_a: + case surface_target::surface_b: + return 1u; + case surface_target::surfaces_a_b: + return 3u; + case surface_target::surfaces_a_b_c: + return 7u; + case surface_target::surfaces_a_b_c_d: + return 15u; + default: + return 0u; + } + } + u16 surface_clip_origin_x() const { return decode().origin_x(); diff --git a/rpcs3/Emu/RSX/rsx_utils.cpp b/rpcs3/Emu/RSX/rsx_utils.cpp index 9ce456513957..195f2cecc999 100644 --- a/rpcs3/Emu/RSX/rsx_utils.cpp +++ b/rpcs3/Emu/RSX/rsx_utils.cpp @@ -88,7 +88,7 @@ namespace rsx u16 blend_color_b = rsx::method_registers.blend_color_16b_b(); u16 blend_color_a = rsx::method_registers.blend_color_16b_a(); - return { blend_color_r / 65535.f, blend_color_g / 65535.f, blend_color_b / 65535.f, blend_color_a / 65535.f }; + return { decode_fp16(blend_color_r), decode_fp16(blend_color_g), decode_fp16(blend_color_b), decode_fp16(blend_color_a) }; } else { diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 2ada1a84ee45..4e0a2e6a4f0d 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -178,6 +178,7 @@ struct cfg_root : cfg::node cfg::_bool disable_async_host_memory_manager{ this, "Disable Asynchronous Memory Manager", false, true }; cfg::_enum output_scaling{ this, "Output Scaling Mode", output_scaling_mode::bilinear, true }; cfg::_bool record_with_overlays{ this, "Record With Overlays", true, true }; + cfg::_bool disable_hardware_blending{ this, "Disable Hardware Blending", false, true }; cfg::_bool disable_hardware_texel_remapping{ this, "Disable Hardware ColorSpace Remapping", false, true }; cfg::uint<0, 100> rcas_sharpening_intensity{ this, "FidelityFX CAS Sharpening Intensity", 50, true }; cfg::_bool disable_blit_engine_upscaling{ this, "Disable Blit Engine Upscaling", false, true }; diff --git a/rpcs3/rpcs3qt/emu_settings_type.cpp b/rpcs3/rpcs3qt/emu_settings_type.cpp index 8c5b81d668ed..99b49f732799 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.cpp +++ b/rpcs3/rpcs3qt/emu_settings_type.cpp @@ -111,9 +111,10 @@ const std::map settings_location = { emu_settings_type::ForceHwMSAAResolve, get_cfg_location(local_cfg.video.force_hw_MSAA_resolve) }, { emu_settings_type::DisableAsyncHostMM, get_cfg_location(local_cfg.video.disable_async_host_memory_manager) }, { emu_settings_type::RecordWithOverlays, get_cfg_location(local_cfg.video.record_with_overlays) }, - { emu_settings_type::DisableHWTexelRemapping, get_cfg_location(local_cfg.video.disable_hardware_texel_remapping) }, + { emu_settings_type::DisableHWBlending, get_cfg_location(local_cfg.video.disable_hardware_texel_remapping) }, + { emu_settings_type::DisableHWTexelRemapping, get_cfg_location(local_cfg.video.disable_hardware_blending) }, { emu_settings_type::FsrSharpeningStrength, get_cfg_location(local_cfg.video.rcas_sharpening_intensity) }, - { emu_settings_type::DisableBlitEngineScaling, get_cfg_location(local_cfg.video.disable_blit_engine_upscaling) }, + { emu_settings_type::DisableBlitEngineScaling, get_cfg_location(local_cfg.video.disable_blit_engine_upscaling) }, // Vulkan { emu_settings_type::VulkanAdapter, get_cfg_location(local_cfg.video.vk.adapter) }, diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index d7a7300e40c0..f0b13114783f 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -111,6 +111,7 @@ enum class emu_settings_type DisableAsyncHostMM, UseReBAR, RecordWithOverlays, + DisableHWBlending, DisableHWTexelRemapping, DisableBlitEngineScaling, diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index ad83f2e90fd1..622263866272 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1446,6 +1446,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std EnhanceCheckBox(emu_settings_type::DisableSpinOptimization, ui->disableSpinOptimization, tooltips.settings.disable_spin_optimization); EnhanceCheckBox(emu_settings_type::EnabledSPUEventsBusyLoop, ui->enableSpuEventsBusyLoop, tooltips.settings.enable_spu_events_busy_loop); EnhanceCheckBox(emu_settings_type::DisableHWTexelRemapping, ui->disableHardwareTexelRemapping, tooltips.settings.disable_hw_texel_remapping); + EnhanceCheckBox(emu_settings_type::DisableHWBlending, ui->disableHardwareBlending, tooltips.settings.disable_hw_blending); // Comboboxes diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 48954ea99a3d..358d94f5db2b 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -4464,6 +4464,13 @@ + + + + Disable Hardware Blending + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 11906ed13a86..54cc698a9bf9 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -131,6 +131,7 @@ class Tooltips : public QObject const QString enable_performance_report = tr("Measure certain events and print a chart after the emulator is stopped. Don't enable if not asked to."); const QString num_ppu_threads = tr("Affects maximum amount of PPU threads running concurrently, the value of 1 has very low compatibility with games.\n2 is the default, if unsure do not modify this setting."); const QString disable_hw_texel_remapping = tr("Disables use of hardware-native color-space remapping formats such as _sRGB and _SNORM suffixes.\nDisabling this option increases accuracy compared to PS3 but can also introduce some noise due to how the software emulation works."); + const QString disable_hw_blending = tr("Force use of programmable blending for backends that support the feature.\nPurely a debugging option, you don't stand to gain anything by enabling this."); // emulator