renderer_vulkan: Emulate logic op when unsupported

* Similar to GLES this is done to prepare for the android port
This commit is contained in:
GPUCode
2022-11-01 14:24:45 +02:00
parent 3c79360fd3
commit 6a4ff8fa24
6 changed files with 78 additions and 8 deletions

View File

@@ -253,6 +253,7 @@ bool Instance::CreateDevice() {
// Not having geometry shaders will cause issues with accelerated rendering. // Not having geometry shaders will cause issues with accelerated rendering.
const vk::PhysicalDeviceFeatures available = feature_chain.get().features; const vk::PhysicalDeviceFeatures available = feature_chain.get().features;
device_features = available;
if (!available.geometryShader) { if (!available.geometryShader) {
LOG_WARNING(Render_Vulkan, LOG_WARNING(Render_Vulkan,
"Geometry shaders not availabe! Accelerated rendering not possible!"); "Geometry shaders not availabe! Accelerated rendering not possible!");

View File

@@ -85,6 +85,11 @@ public:
return present_queue; 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 /// Returns true when VK_KHR_timeline_semaphore is supported
bool IsTimelineSemaphoreSupported() const { bool IsTimelineSemaphoreSupported() const {
return timeline_semaphores; return timeline_semaphores;
@@ -145,6 +150,7 @@ private:
vk::Instance instance; vk::Instance instance;
vk::SurfaceKHR surface; vk::SurfaceKHR surface;
vk::PhysicalDeviceProperties device_properties; vk::PhysicalDeviceProperties device_properties;
vk::PhysicalDeviceFeatures device_features;
VmaAllocator allocator; VmaAllocator allocator;
vk::Queue present_queue; vk::Queue present_queue;
vk::Queue graphics_queue; vk::Queue graphics_queue;

View File

@@ -233,7 +233,7 @@ void PipelineCache::UseTrivialGeometryShader() {
} }
void PipelineCache::UseFragmentShader(const Pica::Regs& regs) { void PipelineCache::UseFragmentShader(const Pica::Regs& regs) {
const PicaFSConfig config{regs}; const PicaFSConfig config{regs, instance};
scheduler.Record([this, config](vk::CommandBuffer, vk::CommandBuffer) { scheduler.Record([this, config](vk::CommandBuffer, vk::CommandBuffer) {
auto [handle, result] = fragment_shaders.Get(config, vk::ShaderStageFlagBits::eFragment, auto [handle, result] = fragment_shaders.Get(config, vk::ShaderStageFlagBits::eFragment,
@@ -452,7 +452,7 @@ vk::Pipeline PipelineCache::BuildPipeline(const PipelineInfo& info) {
.colorWriteMask = static_cast<vk::ColorComponentFlags>(info.blending.color_write_mask)}; .colorWriteMask = static_cast<vk::ColorComponentFlags>(info.blending.color_write_mask)};
const vk::PipelineColorBlendStateCreateInfo color_blending = { 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()), .logicOp = PicaToVK::LogicOp(info.blending.logic_op.Value()),
.attachmentCount = 1, .attachmentCount = 1,
.pAttachments = &colorblend_attachment, .pAttachments = &colorblend_attachment,

View File

@@ -805,6 +805,9 @@ void RasterizerVulkan::NotifyPicaRegisterChanged(u32 id) {
// Blending // Blending
case PICA_REG_INDEX(framebuffer.output_merger.alphablend_enable): case PICA_REG_INDEX(framebuffer.output_merger.alphablend_enable):
if (instance.NeedsLogicOpEmulation()) {
shader_dirty = true;
}
SyncBlendEnabled(); SyncBlendEnabled();
break; break;
case PICA_REG_INDEX(framebuffer.output_merger.alpha_blending): case PICA_REG_INDEX(framebuffer.output_merger.alpha_blending):
@@ -925,6 +928,9 @@ void RasterizerVulkan::NotifyPicaRegisterChanged(u32 id) {
// Logic op // Logic op
case PICA_REG_INDEX(framebuffer.output_merger.logic_op): case PICA_REG_INDEX(framebuffer.output_merger.logic_op):
if (instance.NeedsLogicOpEmulation()) {
shader_dirty = true;
}
SyncLogicOp(); SyncLogicOp();
break; break;
@@ -1594,12 +1600,34 @@ void RasterizerVulkan::SyncBlendColor() {
void RasterizerVulkan::SyncLogicOp() { void RasterizerVulkan::SyncLogicOp() {
const auto& regs = Pica::g_state.regs; const auto& regs = Pica::g_state.regs;
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); pipeline_info.blending.logic_op.Assign(regs.framebuffer.output_merger.logic_op);
} }
}
void RasterizerVulkan::SyncColorWriteMask() { void RasterizerVulkan::SyncColorWriteMask() {
const auto& regs = Pica::g_state.regs; const auto& regs = Pica::g_state.regs;
const u32 color_mask = (regs.framebuffer.output_merger.depth_color_mask >> 8) & 0xF; 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); pipeline_info.blending.color_write_mask.Assign(color_mask);
} }

View File

@@ -11,6 +11,7 @@
#include "video_core/regs_framebuffer.h" #include "video_core/regs_framebuffer.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.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_shader_gen.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
using Pica::FramebufferRegs; using Pica::FramebufferRegs;
@@ -100,7 +101,7 @@ out gl_PerVertex {
return out; 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.scissor_test_mode.Assign(regs.rasterizer.scissor_test.mode);
state.depthmap_enable.Assign(regs.rasterizer.depthmap_enable); 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.texture2_use_coord1.Assign(regs.texturing.main_config.texture2_use_coord1 != 0);
state.alphablend_enable.Assign(0); // Emulate logic op in the shader if not supported. This is mostly for mobile GPUs
state.logic_op.Assign(Pica::FramebufferRegs::LogicOp::Clear); 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. // Copy relevant tev stages fields.
// We don't sync const_color here because of the high variance, it is a // 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"; 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<u32>(state.logic_op.Value()));
UNIMPLEMENTED();
}
}
out += '}'; out += '}';
return out; return out;
} }

View File

@@ -13,6 +13,8 @@
namespace Vulkan { namespace Vulkan {
class Instance;
enum Attributes { enum Attributes {
ATTRIBUTE_POSITION, ATTRIBUTE_POSITION,
ATTRIBUTE_COLOR, ATTRIBUTE_COLOR,
@@ -51,7 +53,7 @@ struct PicaFSConfigState {
BitField<17, 1, Pica::RasterizerRegs::DepthBuffering> depthmap_enable; BitField<17, 1, Pica::RasterizerRegs::DepthBuffering> depthmap_enable;
BitField<18, 3, Pica::TexturingRegs::FogMode> fog_mode; BitField<18, 3, Pica::TexturingRegs::FogMode> fog_mode;
BitField<21, 1, u32> fog_flip; 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<23, 4, Pica::FramebufferRegs::LogicOp> logic_op;
BitField<27, 1, u32> shadow_rendering; BitField<27, 1, u32> shadow_rendering;
BitField<28, 1, u32> shadow_texture_orthographic; BitField<28, 1, u32> shadow_texture_orthographic;
@@ -132,7 +134,7 @@ struct PicaFSConfigState {
* two separate shaders sharing the same key. * two separate shaders sharing the same key.
*/ */
struct PicaFSConfig : Common::HashableStruct<PicaFSConfigState> { struct PicaFSConfig : Common::HashableStruct<PicaFSConfigState> {
PicaFSConfig(const Pica::Regs& regs); PicaFSConfig(const Pica::Regs& regs, const Instance& instance);
bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index)); return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index));