renderer_vulkan: Emulate logic op when unsupported
* Similar to GLES this is done to prepare for the android port
This commit is contained in:
@@ -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!");
|
||||||
|
@@ -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;
|
||||||
|
@@ -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,
|
||||||
|
@@ -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;
|
||||||
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() {
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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));
|
||||||
|
Reference in New Issue
Block a user