From 4cc584f47d310c95d3686d8f4c9ffb052ee38fcc Mon Sep 17 00:00:00 2001 From: Clippy95 <7094610+Clippy95@users.noreply.github.com> Date: Sat, 15 Aug 2026 10:32:54 +0300 Subject: [PATCH] [D3D12] Fix constant-alpha blending in the RTV path --- .../gpu/d3d12/d3d12_command_processor.cc | 52 ++++++++++++++++++- src/xenia/gpu/d3d12/d3d12_command_processor.h | 10 ++-- src/xenia/gpu/d3d12/pipeline_cache.cc | 30 +++++++---- src/xenia/gpu/d3d12/pipeline_cache.h | 4 +- src/xenia/ui/d3d12/d3d12_provider.cc | 8 +++ src/xenia/ui/d3d12/d3d12_provider.h | 4 ++ 6 files changed, 92 insertions(+), 16 deletions(-) diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.cc b/src/xenia/gpu/d3d12/d3d12_command_processor.cc index 3a4c743ea..228cfb29b 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.cc +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.cc @@ -2831,7 +2831,8 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, #endif // Update viewport, scissor, blend factor and stencil reference. UpdateFixedFunctionState(viewport_info, scissor, primitive_polygonal, - normalized_depth_control); + normalized_depth_control, normalized_color_mask, + bound_depth_and_color_render_target_bits); // Update system constants before uploading them. // TODO(Triang3l): With ROV, pass the disabled render target mask for safety. @@ -3902,7 +3903,9 @@ void D3D12CommandProcessor::ClearCommandAllocatorCache() { void D3D12CommandProcessor::UpdateFixedFunctionState( const draw_util::ViewportInfo& viewport_info, const draw_util::Scissor& scissor, bool primitive_polygonal, - reg::RB_DEPTHCONTROL normalized_depth_control) { + reg::RB_DEPTHCONTROL normalized_depth_control, + uint32_t normalized_color_mask, + uint32_t bound_depth_and_color_render_target_bits) { #if XE_GPU_FINE_GRAINED_DRAW_SCOPES SCOPE_profile_cpu_f("gpu"); #endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES @@ -3936,6 +3939,51 @@ void D3D12CommandProcessor::UpdateFixedFunctionState( regs.Get(XE_GPU_REG_RB_BLEND_BLUE), regs.Get(XE_GPU_REG_RB_BLEND_ALPHA), }; + if (!GetD3D12Provider().IsAlphaBlendFactorSupported()) { + bool color_uses_constant_color = false; + bool color_uses_constant_alpha = false; + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + // Ignore unbound targets and targets that don't write RGB, since their + // color blend factors don't affect the host output merger. + if (!(bound_depth_and_color_render_target_bits & + (uint32_t(1) << (1 + i))) || + !((normalized_color_mask >> (i * 4)) & 0b0111)) { + continue; + } + auto blend_control = regs.Get( + reg::RB_BLENDCONTROL::rt_register_indices[i]); + // Direct3D 12 ignores blend factors for MIN and MAX. + if (blend_control.color_comb_fcn == xenos::BlendOp::kMin || + blend_control.color_comb_fcn == xenos::BlendOp::kMax) { + continue; + } + const xenos::BlendFactor color_blend_factors[] = { + blend_control.color_srcblend, blend_control.color_destblend}; + for (xenos::BlendFactor color_blend_factor : color_blend_factors) { + switch (color_blend_factor) { + case xenos::BlendFactor::kConstantColor: + case xenos::BlendFactor::kOneMinusConstantColor: + color_uses_constant_color = true; + break; + case xenos::BlendFactor::kConstantAlpha: + case xenos::BlendFactor::kOneMinusConstantAlpha: + color_uses_constant_alpha = true; + break; + default: + break; + } + } + } + // Legacy D3D12 has only a four-component constant-color factor. If the + // draw needs only the scalar constant-alpha factor, emulate it by + // replicating A. Mixed constant-color and constant-alpha use can't be + // represented exactly, so preserve the color factor in that case. + if (color_uses_constant_alpha && !color_uses_constant_color) { + blend_factor[0] = blend_factor[3]; + blend_factor[1] = blend_factor[3]; + blend_factor[2] = blend_factor[3]; + } + } // std::memcmp instead of != so in case of NaN, every draw won't be // invalidating it. ff_blend_factor_update_needed_ |= diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.h b/src/xenia/gpu/d3d12/d3d12_command_processor.h index 6c2a2aa8b..1d48d8cde 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.h +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.h @@ -452,10 +452,12 @@ class D3D12CommandProcessor final : public CommandProcessor { D3D12_CPU_DESCRIPTOR_HANDLE& cpu_handle_out, D3D12_GPU_DESCRIPTOR_HANDLE& gpu_handle_out); - void UpdateFixedFunctionState(const draw_util::ViewportInfo& viewport_info, - const draw_util::Scissor& scissor, - bool primitive_polygonal, - reg::RB_DEPTHCONTROL normalized_depth_control); + void UpdateFixedFunctionState( + const draw_util::ViewportInfo& viewport_info, + const draw_util::Scissor& scissor, bool primitive_polygonal, + reg::RB_DEPTHCONTROL normalized_depth_control, + uint32_t normalized_color_mask, + uint32_t bound_depth_and_color_render_target_bits); template XE_NOINLINE void UpdateSystemConstantValues_Impl( diff --git a/src/xenia/gpu/d3d12/pipeline_cache.cc b/src/xenia/gpu/d3d12/pipeline_cache.cc index 224d4c983..b0ec9ae72 100644 --- a/src/xenia/gpu/d3d12/pipeline_cache.cc +++ b/src/xenia/gpu/d3d12/pipeline_cache.cc @@ -1544,9 +1544,9 @@ bool PipelineCache::GetCurrentStateDescription( // ONE_MINUS_CONSTANT_COLOR /* 13 */ PipelineBlendFactor::kInvBlendFactor, // CONSTANT_ALPHA - /* 14 */ PipelineBlendFactor::kBlendFactor, + /* 14 */ PipelineBlendFactor::kAlphaFactor, // ONE_MINUS_CONSTANT_ALPHA - /* 15 */ PipelineBlendFactor::kInvBlendFactor, + /* 15 */ PipelineBlendFactor::kInvAlphaFactor, /* 16 */ PipelineBlendFactor::kSrcAlphaSat, }; // Like kBlendFactorMap, but with color modes changed to alpha. Some @@ -3156,14 +3156,26 @@ ID3D12PipelineState* PipelineCache::CreateD3D12Pipeline( // Render targets and blending. state_desc.BlendState.IndependentBlendEnable = true; - static constexpr D3D12_BLEND kBlendFactorMap[] = { - D3D12_BLEND_ZERO, D3D12_BLEND_ONE, - D3D12_BLEND_SRC_COLOR, D3D12_BLEND_INV_SRC_COLOR, - D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA, - D3D12_BLEND_DEST_COLOR, D3D12_BLEND_INV_DEST_COLOR, - D3D12_BLEND_DEST_ALPHA, D3D12_BLEND_INV_DEST_ALPHA, - D3D12_BLEND_BLEND_FACTOR, D3D12_BLEND_INV_BLEND_FACTOR, + const bool alpha_blend_factor_supported = + command_processor_.GetD3D12Provider().IsAlphaBlendFactorSupported(); + const D3D12_BLEND kBlendFactorMap[] = { + D3D12_BLEND_ZERO, + D3D12_BLEND_ONE, + D3D12_BLEND_SRC_COLOR, + D3D12_BLEND_INV_SRC_COLOR, + D3D12_BLEND_SRC_ALPHA, + D3D12_BLEND_INV_SRC_ALPHA, + D3D12_BLEND_DEST_COLOR, + D3D12_BLEND_INV_DEST_COLOR, + D3D12_BLEND_DEST_ALPHA, + D3D12_BLEND_INV_DEST_ALPHA, + D3D12_BLEND_BLEND_FACTOR, + D3D12_BLEND_INV_BLEND_FACTOR, D3D12_BLEND_SRC_ALPHA_SAT, + alpha_blend_factor_supported ? D3D12_BLEND_ALPHA_FACTOR + : D3D12_BLEND_BLEND_FACTOR, + alpha_blend_factor_supported ? D3D12_BLEND_INV_ALPHA_FACTOR + : D3D12_BLEND_INV_BLEND_FACTOR, }; // 8 entries for safety since 3 bits from the guest are passed directly. static constexpr D3D12_BLEND_OP kBlendOpMap[] = { diff --git a/src/xenia/gpu/d3d12/pipeline_cache.h b/src/xenia/gpu/d3d12/pipeline_cache.h index d93a3d1d3..ab3fb04e5 100644 --- a/src/xenia/gpu/d3d12/pipeline_cache.h +++ b/src/xenia/gpu/d3d12/pipeline_cache.h @@ -180,6 +180,8 @@ class PipelineCache { kBlendFactor, kInvBlendFactor, kSrcAlphaSat, + kAlphaFactor, + kInvAlphaFactor, }; // Update PipelineDescription::kVersion if anything is changed! @@ -237,7 +239,7 @@ class PipelineCache { PipelineRenderTarget render_targets[xenos::kMaxColorRenderTargets]; inline bool operator==(const PipelineDescription& other) const; - static constexpr uint32_t kVersion = 0x20260716; + static constexpr uint32_t kVersion = 0x20260815; }); XEPACKEDSTRUCT(PipelineStoredDescription, { diff --git a/src/xenia/ui/d3d12/d3d12_provider.cc b/src/xenia/ui/d3d12/d3d12_provider.cc index 069ea1aeb..52954dd41 100644 --- a/src/xenia/ui/d3d12/d3d12_provider.cc +++ b/src/xenia/ui/d3d12/d3d12_provider.cc @@ -463,6 +463,12 @@ bool D3D12Provider::Initialize() { unaligned_block_textures_supported_ = bool(options8.UnalignedBlockTexturesSupported); } + alpha_blend_factor_supported_ = false; + D3D12_FEATURE_DATA_D3D12_OPTIONS13 options13 = {}; + if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS13, + &options13, sizeof(options13)))) { + alpha_blend_factor_supported_ = bool(options13.AlphaBlendFactorSupported); + } virtual_address_bits_per_resource_ = 0; D3D12_FEATURE_DATA_GPU_VIRTUAL_ADDRESS_SUPPORT virtual_address_support; if (SUCCEEDED(device->CheckFeatureSupport( @@ -478,6 +484,7 @@ bool D3D12Provider::Initialize() { "* Pixel-shader-specified stencil reference: {}\n" "* Programmable sample positions: tier {}\n" "* Rasterizer-ordered views: {}\n" + "* Scalar alpha blend factor: {}\n" "* Resource binding: tier {}\n" "* Tiled resources: tier {}\n" "* Unaligned block-compressed textures: {}", @@ -487,6 +494,7 @@ bool D3D12Provider::Initialize() { ps_specified_stencil_reference_supported_ ? "yes" : "no", uint32_t(programmable_sample_positions_tier_), rasterizer_ordered_views_supported_ ? "yes" : "no", + alpha_blend_factor_supported_ ? "yes" : "no", uint32_t(resource_binding_tier_), uint32_t(tiled_resources_tier_), unaligned_block_textures_supported_ ? "yes" : "no"); diff --git a/src/xenia/ui/d3d12/d3d12_provider.h b/src/xenia/ui/d3d12/d3d12_provider.h index 32d5606b8..6542341c6 100644 --- a/src/xenia/ui/d3d12/d3d12_provider.h +++ b/src/xenia/ui/d3d12/d3d12_provider.h @@ -114,6 +114,9 @@ class D3D12Provider : public GraphicsProvider { bool AreRasterizerOrderedViewsSupported() const { return rasterizer_ordered_views_supported_; } + bool IsAlphaBlendFactorSupported() const { + return alpha_blend_factor_supported_; + } D3D12_RESOURCE_BINDING_TIER GetResourceBindingTier() const { return resource_binding_tier_; } @@ -210,6 +213,7 @@ class D3D12Provider : public GraphicsProvider { uint32_t virtual_address_bits_per_resource_; bool ps_specified_stencil_reference_supported_; bool rasterizer_ordered_views_supported_; + bool alpha_blend_factor_supported_; bool unaligned_block_textures_supported_; };