diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index f9d51487b..d01cef531 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -253,6 +253,7 @@ bool Instance::CreateDevice() { // Not having geometry shaders will cause issues with accelerated rendering. const vk::PhysicalDeviceFeatures available = feature_chain.get().features; + device_features = available; if (!available.geometryShader) { LOG_WARNING(Render_Vulkan, "Geometry shaders not availabe! Accelerated rendering not possible!"); diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 4480d2b9b..634b27580 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -85,6 +85,11 @@ public: return present_queue; } + /// Returns true if logic operations need shader emulation + bool NeedsLogicOpEmulation() const { + return !device_features.logicOp; + } + /// Returns true when VK_KHR_timeline_semaphore is supported bool IsTimelineSemaphoreSupported() const { return timeline_semaphores; @@ -145,6 +150,7 @@ private: vk::Instance instance; vk::SurfaceKHR surface; vk::PhysicalDeviceProperties device_properties; + vk::PhysicalDeviceFeatures device_features; VmaAllocator allocator; vk::Queue present_queue; vk::Queue graphics_queue; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 3ac9c6b27..c4f4df3f3 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -233,7 +233,7 @@ void PipelineCache::UseTrivialGeometryShader() { } void PipelineCache::UseFragmentShader(const Pica::Regs& regs) { - const PicaFSConfig config{regs}; + const PicaFSConfig config{regs, instance}; scheduler.Record([this, config](vk::CommandBuffer, vk::CommandBuffer) { auto [handle, result] = fragment_shaders.Get(config, vk::ShaderStageFlagBits::eFragment, @@ -452,7 +452,7 @@ vk::Pipeline PipelineCache::BuildPipeline(const PipelineInfo& info) { .colorWriteMask = static_cast(info.blending.color_write_mask)}; const vk::PipelineColorBlendStateCreateInfo color_blending = { - .logicOpEnable = !info.blending.blend_enable.Value(), + .logicOpEnable = !info.blending.blend_enable.Value() && !instance.NeedsLogicOpEmulation(), .logicOp = PicaToVK::LogicOp(info.blending.logic_op.Value()), .attachmentCount = 1, .pAttachments = &colorblend_attachment, diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 6af4397e9..416e18bfc 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -805,6 +805,9 @@ void RasterizerVulkan::NotifyPicaRegisterChanged(u32 id) { // Blending case PICA_REG_INDEX(framebuffer.output_merger.alphablend_enable): + if (instance.NeedsLogicOpEmulation()) { + shader_dirty = true; + } SyncBlendEnabled(); break; case PICA_REG_INDEX(framebuffer.output_merger.alpha_blending): @@ -925,6 +928,9 @@ void RasterizerVulkan::NotifyPicaRegisterChanged(u32 id) { // Logic op case PICA_REG_INDEX(framebuffer.output_merger.logic_op): + if (instance.NeedsLogicOpEmulation()) { + shader_dirty = true; + } SyncLogicOp(); break; @@ -1594,12 +1600,34 @@ void RasterizerVulkan::SyncBlendColor() { void RasterizerVulkan::SyncLogicOp() { const auto& regs = Pica::g_state.regs; - pipeline_info.blending.logic_op.Assign(regs.framebuffer.output_merger.logic_op); + + const bool is_logic_op_emulated = + instance.NeedsLogicOpEmulation() && !regs.framebuffer.output_merger.alphablend_enable; + const bool is_logic_op_noop = + regs.framebuffer.output_merger.logic_op == Pica::FramebufferRegs::LogicOp::NoOp; + if (is_logic_op_emulated && is_logic_op_noop) { + // Color output is disabled by logic operation. We use color write mask to skip + // color but allow depth write. + pipeline_info.blending.color_write_mask.Assign(0); + } else { + pipeline_info.blending.logic_op.Assign(regs.framebuffer.output_merger.logic_op); + } } void RasterizerVulkan::SyncColorWriteMask() { const auto& regs = Pica::g_state.regs; const u32 color_mask = (regs.framebuffer.output_merger.depth_color_mask >> 8) & 0xF; + + const bool is_logic_op_emulated = + instance.NeedsLogicOpEmulation() && !regs.framebuffer.output_merger.alphablend_enable; + const bool is_logic_op_noop = + regs.framebuffer.output_merger.logic_op == Pica::FramebufferRegs::LogicOp::NoOp; + if (is_logic_op_emulated && is_logic_op_noop) { + // Color output is disabled by logic operation. We use color write mask to skip + // color but allow depth write. Return early to avoid overwriting this. + return; + } + pipeline_info.blending.color_write_mask.Assign(color_mask); } diff --git a/src/video_core/renderer_vulkan/vk_shader_gen.cpp b/src/video_core/renderer_vulkan/vk_shader_gen.cpp index 95a189656..274aac5ce 100644 --- a/src/video_core/renderer_vulkan/vk_shader_gen.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_gen.cpp @@ -11,6 +11,7 @@ #include "video_core/regs_framebuffer.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" #include "video_core/renderer_vulkan/vk_shader_gen.h" +#include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/video_core.h" using Pica::FramebufferRegs; @@ -100,7 +101,7 @@ out gl_PerVertex { return out; } -PicaFSConfig::PicaFSConfig(const Pica::Regs& regs) { +PicaFSConfig::PicaFSConfig(const Pica::Regs& regs, const Instance& instance) { state.scissor_test_mode.Assign(regs.rasterizer.scissor_test.mode); state.depthmap_enable.Assign(regs.rasterizer.depthmap_enable); @@ -113,8 +114,16 @@ PicaFSConfig::PicaFSConfig(const Pica::Regs& regs) { state.texture2_use_coord1.Assign(regs.texturing.main_config.texture2_use_coord1 != 0); - state.alphablend_enable.Assign(0); - state.logic_op.Assign(Pica::FramebufferRegs::LogicOp::Clear); + // Emulate logic op in the shader if not supported. This is mostly for mobile GPUs + const bool emulate_logic_op = instance.NeedsLogicOpEmulation() && + !Pica::g_state.regs.framebuffer.output_merger.alphablend_enable; + + state.emulate_logic_op.Assign(emulate_logic_op); + if (emulate_logic_op) { + state.logic_op.Assign(regs.framebuffer.output_merger.logic_op); + } else { + state.logic_op.Assign(Pica::FramebufferRegs::LogicOp::NoOp); + } // Copy relevant tev stages fields. // We don't sync const_color here because of the high variance, it is a @@ -1534,6 +1543,30 @@ do { out += "color = byteround(last_tex_env_out);\n"; } + if (state.emulate_logic_op) { + switch (state.logic_op) { + case FramebufferRegs::LogicOp::Clear: + out += "color = vec4(0);\n"; + break; + case FramebufferRegs::LogicOp::Set: + out += "color = vec4(1);\n"; + break; + case FramebufferRegs::LogicOp::Copy: + // Take the color output as-is + break; + case FramebufferRegs::LogicOp::CopyInverted: + out += "color = ~color;\n"; + break; + case FramebufferRegs::LogicOp::NoOp: + // We need to discard the color, but not necessarily the depth. This is not possible + // with fragment shader alone, so we emulate this behavior with the color mask. + break; + default: + LOG_CRITICAL(HW_GPU, "Unhandled logic_op {:x}", static_cast(state.logic_op.Value())); + UNIMPLEMENTED(); + } + } + out += '}'; return out; } diff --git a/src/video_core/renderer_vulkan/vk_shader_gen.h b/src/video_core/renderer_vulkan/vk_shader_gen.h index b413ad345..443950f5b 100644 --- a/src/video_core/renderer_vulkan/vk_shader_gen.h +++ b/src/video_core/renderer_vulkan/vk_shader_gen.h @@ -13,6 +13,8 @@ namespace Vulkan { +class Instance; + enum Attributes { ATTRIBUTE_POSITION, ATTRIBUTE_COLOR, @@ -51,7 +53,7 @@ struct PicaFSConfigState { BitField<17, 1, Pica::RasterizerRegs::DepthBuffering> depthmap_enable; BitField<18, 3, Pica::TexturingRegs::FogMode> fog_mode; BitField<21, 1, u32> fog_flip; - BitField<22, 1, u32> alphablend_enable; + BitField<22, 1, u32> emulate_logic_op; BitField<23, 4, Pica::FramebufferRegs::LogicOp> logic_op; BitField<27, 1, u32> shadow_rendering; BitField<28, 1, u32> shadow_texture_orthographic; @@ -132,7 +134,7 @@ struct PicaFSConfigState { * two separate shaders sharing the same key. */ struct PicaFSConfig : Common::HashableStruct { - PicaFSConfig(const Pica::Regs& regs); + PicaFSConfig(const Pica::Regs& regs, const Instance& instance); bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index));