From 64936747263efe3d0c366ae43638a380882f891e Mon Sep 17 00:00:00 2001 From: nmzik Date: Thu, 17 Sep 2026 23:16:51 +0400 Subject: [PATCH] shader: dual-source blending (Vulkan) --- .../host_gpu/renderer/pipeline/pipelineCache.cpp | 15 +++++++++++++++ src/graphics/presentation/window/vulkanWindow.cpp | 4 +++- .../backend/spirv/spirvEmitterModule.cpp | 11 ++++++++++- src/graphics/shader/shader.cpp | 1 + src/graphics/shader/shader.h | 1 + 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index a9d2eca06..d60a90c88 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -593,6 +593,21 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms( ShaderParams pixel_params; if (pixel_active) { pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info); + const auto& blend = context.GetBlendControl(0); + const auto is_dual_source = [](uint8_t factor) { + return factor >= static_cast(Prospero::BlendFactor::kSrc1Color) && + factor <= static_cast(Prospero::BlendFactor::kOneMinusSrc1Alpha); + }; + pixel_info.dual_source_blending = + blend.enable && !context.GetRenderTarget(0).info.blend_bypass && + (is_dual_source(blend.color_srcblend) || is_dual_source(blend.color_destblend) || + (blend.separate_alpha_blend && + (is_dual_source(blend.alpha_srcblend) || is_dual_source(blend.alpha_destblend)))); + if (pixel_info.dual_source_blending) { + // MRT1 supplies a second blend source for the same render target as MRT0. + pixel_info.target_output_mode[1] = pixel_info.target_output_mode[0]; + pixel_info.target_export_mapping[1] = pixel_info.target_export_mapping[0]; + } } if (context.GetClipControl().clip_disable) { const auto& viewport = context.GetScreenViewport().viewports[0]; diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index 2f2c8a87b..04b765c35 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -610,10 +610,11 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, graphics.mesh_shader_properties.maxMeshOutputPrimitives, graphics.mesh_shader_properties.maxMeshSharedMemorySize); } - // VulkanFindPhysicalDevice already checked the required creation features. These two + // VulkanFindPhysicalDevice already checked the required creation features. These // requirements are specific to this creation path and are not part of device selection. EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE); EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE); + EXIT_NOT_IMPLEMENTED(supported_features2.features.dualSrcBlend != VK_TRUE); vk::PhysicalDeviceFeatures device_features {}; device_features.fragmentStoresAndAtomics = VK_TRUE; device_features.samplerAnisotropy = VK_TRUE; @@ -624,6 +625,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE; device_features.shaderImageGatherExtended = VK_TRUE; device_features.independentBlend = VK_TRUE; + device_features.dualSrcBlend = VK_TRUE; device_features.tessellationShader = VK_TRUE; device_features.sampleRateShading = VK_TRUE; device_features.depthBiasClamp = VK_TRUE; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index 7f8fb1c90..25d0650f3 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -553,8 +553,17 @@ void DefineOutputs(EmitterState& state) { const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4); binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput, binding.debug_name.c_str()); + const bool dual_source = binding.kind == IR::StageOutputKind::Mrt && + state.program.stage == ShaderType::Pixel && + state.input_info.pixel->dual_source_blending; + EXIT_NOT_IMPLEMENTED(dual_source && binding.index > 1); state.builder.AddAnnotation(spv::OpDecorate, binding.variable_id, - spv::DecorationLocation, binding.location); + spv::DecorationLocation, + dual_source ? 0u : binding.location); + if (dual_source) { + state.builder.AddAnnotation(spv::OpDecorate, binding.variable_id, + spv::DecorationIndex, binding.index); + } break; } } diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index 220e6fbee..748ceb459 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -721,6 +721,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector key.push_back(static_cast(info.ps_depth_export_enable)); key.push_back(static_cast(info.ps_sample_mask_export_enable)); key.push_back(static_cast(info.ps_early_z)); + key.push_back(static_cast(info.dual_source_blending)); key.insert(key.end(), std::begin(info.target_output_mode), std::end(info.target_output_mode)); for (uint32_t base = 0; base < info.target_export_mapping.size(); base += 4u) { uint32_t packed = 0; diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index f45dcc8d7..695121756 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -175,6 +175,7 @@ struct ShaderPixelInputInfo { bool ps_depth_export_enable = false; bool ps_sample_mask_export_enable = false; bool ps_sample_shading = false; + bool dual_source_blending = false; bool ps_early_z = false; bool ps_execute_on_noop = false; ShaderStageRuntime stage;