citra-emu
/
citra
Archived
1
0
Fork 0

video_core: Perform quaternion correction and interpolation in fragment shader using barycentric extension. (#7126)

This commit is contained in:
Steveice10 2023-11-09 15:23:56 -08:00 committed by GitHub
parent fcc0fd671a
commit 84f9e9a10f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 100 additions and 7 deletions

View File

@ -280,7 +280,15 @@ bool RasterizerOpenGL::SetupGeometryShader() {
return false; return false;
} }
// Enable the quaternion fix-up geometry-shader only if we are actually doing per-fragment
// lighting and care about proper quaternions. Otherwise just use standard vertex+fragment
// shaders
if (regs.lighting.disable) {
shader_manager.UseTrivialGeometryShader();
} else {
shader_manager.UseFixedGeometryShader(regs); shader_manager.UseFixedGeometryShader(regs);
}
return true; return true;
} }

View File

@ -88,7 +88,12 @@ static std::tuple<PicaVSConfig, Pica::Shader::ShaderSetup> BuildVSConfigFromRaw(
Pica::Shader::ShaderSetup setup; Pica::Shader::ShaderSetup setup;
setup.program_code = program_code; setup.program_code = program_code;
setup.swizzle_data = swizzle_data; setup.swizzle_data = swizzle_data;
return {PicaVSConfig{raw.GetRawShaderConfig(), setup, driver.HasClipCullDistance(), true},
// Enable the geometry-shader only if we are actually doing per-fragment lighting
// and care about proper quaternions. Otherwise just use standard vertex+fragment shaders
const bool use_geometry_shader = !raw.GetRawShaderConfig().lighting.disable;
return {PicaVSConfig{raw.GetRawShaderConfig(), setup, driver.HasClipCullDistance(),
use_geometry_shader},
setup}; setup};
} }
@ -265,6 +270,7 @@ public:
.has_geometry_shader = true, .has_geometry_shader = true,
.has_custom_border_color = true, .has_custom_border_color = true,
.has_fragment_shader_interlock = false, .has_fragment_shader_interlock = false,
.has_fragment_shader_barycentric = false,
.has_blend_minmax_factor = driver.HasBlendMinMaxFactor(), .has_blend_minmax_factor = driver.HasBlendMinMaxFactor(),
.has_minus_one_to_one_range = true, .has_minus_one_to_one_range = true,
.has_logic_op = !driver.IsOpenGLES(), .has_logic_op = !driver.IsOpenGLES(),
@ -272,6 +278,8 @@ public:
.has_gl_arm_framebuffer_fetch = driver.HasArmShaderFramebufferFetch(), .has_gl_arm_framebuffer_fetch = driver.HasArmShaderFramebufferFetch(),
.has_gl_nv_fragment_shader_interlock = driver.GetVendor() == Vendor::Nvidia, .has_gl_nv_fragment_shader_interlock = driver.GetVendor() == Vendor::Nvidia,
.has_gl_intel_fragment_shader_interlock = driver.GetVendor() == Vendor::Intel, .has_gl_intel_fragment_shader_interlock = driver.GetVendor() == Vendor::Intel,
// TODO: This extension requires GLSL 450 / OpenGL 4.5 context.
.has_gl_nv_fragment_shader_barycentric = false,
.is_vulkan = false, .is_vulkan = false,
}; };
} }
@ -327,7 +335,11 @@ ShaderProgramManager::~ShaderProgramManager() = default;
bool ShaderProgramManager::UseProgrammableVertexShader(const Pica::Regs& regs, bool ShaderProgramManager::UseProgrammableVertexShader(const Pica::Regs& regs,
Pica::Shader::ShaderSetup& setup) { Pica::Shader::ShaderSetup& setup) {
PicaVSConfig config{regs, setup, driver.HasClipCullDistance(), true}; // Enable the geometry-shader only if we are actually doing per-fragment lighting
// and care about proper quaternions. Otherwise just use standard vertex+fragment shaders
const bool use_geometry_shader = !regs.lighting.disable;
PicaVSConfig config{regs, setup, driver.HasClipCullDistance(), use_geometry_shader};
auto [handle, result] = impl->programmable_vertex_shaders.Get(config, setup); auto [handle, result] = impl->programmable_vertex_shaders.Get(config, setup);
if (handle == 0) if (handle == 0)
return false; return false;

View File

@ -403,7 +403,8 @@ bool Instance::CreateDevice() {
vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR, vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR,
vk::PhysicalDeviceCustomBorderColorFeaturesEXT, vk::PhysicalDeviceIndexTypeUint8FeaturesEXT, vk::PhysicalDeviceCustomBorderColorFeaturesEXT, vk::PhysicalDeviceIndexTypeUint8FeaturesEXT,
vk::PhysicalDeviceFragmentShaderInterlockFeaturesEXT, vk::PhysicalDeviceFragmentShaderInterlockFeaturesEXT,
vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT>(); vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT,
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR>();
const vk::StructureChain properties_chain = const vk::StructureChain properties_chain =
physical_device.getProperties2<vk::PhysicalDeviceProperties2, physical_device.getProperties2<vk::PhysicalDeviceProperties2,
vk::PhysicalDevicePortabilitySubsetPropertiesKHR>(); vk::PhysicalDevicePortabilitySubsetPropertiesKHR>();
@ -436,6 +437,7 @@ bool Instance::CreateDevice() {
}; };
const bool is_nvidia = driver_id == vk::DriverIdKHR::eNvidiaProprietary; const bool is_nvidia = driver_id == vk::DriverIdKHR::eNvidiaProprietary;
const bool is_moltenvk = driver_id == vk::DriverIdKHR::eMoltenvk;
const bool is_arm = driver_id == vk::DriverIdKHR::eArmProprietary; const bool is_arm = driver_id == vk::DriverIdKHR::eArmProprietary;
const bool is_qualcomm = driver_id == vk::DriverIdKHR::eQualcommProprietary; const bool is_qualcomm = driver_id == vk::DriverIdKHR::eQualcommProprietary;
@ -459,6 +461,9 @@ bool Instance::CreateDevice() {
const bool has_pipeline_creation_cache_control = const bool has_pipeline_creation_cache_control =
add_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME, is_nvidia, add_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME, is_nvidia,
"it is broken on Nvidia drivers"); "it is broken on Nvidia drivers");
const bool has_fragment_shader_barycentric =
add_extension(VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME, is_moltenvk,
"the PerVertexKHR attribute is not supported by MoltenVK");
const auto family_properties = physical_device.getQueueFamilyProperties(); const auto family_properties = physical_device.getQueueFamilyProperties();
if (family_properties.empty()) { if (family_properties.empty()) {
@ -514,6 +519,7 @@ bool Instance::CreateDevice() {
vk::PhysicalDeviceIndexTypeUint8FeaturesEXT{}, vk::PhysicalDeviceIndexTypeUint8FeaturesEXT{},
vk::PhysicalDeviceFragmentShaderInterlockFeaturesEXT{}, vk::PhysicalDeviceFragmentShaderInterlockFeaturesEXT{},
vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT{}, vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT{},
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR{},
}; };
#define PROP_GET(structName, prop, property) property = properties_chain.get<structName>().prop; #define PROP_GET(structName, prop, property) property = properties_chain.get<structName>().prop;
@ -581,6 +587,13 @@ bool Instance::CreateDevice() {
device_chain.unlink<vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT>(); device_chain.unlink<vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT>();
} }
if (has_fragment_shader_barycentric) {
FEAT_SET(vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR, fragmentShaderBarycentric,
fragment_shader_barycentric)
} else {
device_chain.unlink<vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR>();
}
#undef PROP_GET #undef PROP_GET
#undef FEAT_SET #undef FEAT_SET

View File

@ -168,6 +168,11 @@ public:
return shader_stencil_export; return shader_stencil_export;
} }
/// Returns true when VK_KHR_fragment_shader_barycentric is supported
bool IsFragmentShaderBarycentricSupported() const {
return fragment_shader_barycentric;
}
/// Returns the vendor ID of the physical device /// Returns the vendor ID of the physical device
u32 GetVendorID() const { u32 GetVendorID() const {
return properties.vendorID; return properties.vendorID;
@ -307,6 +312,7 @@ private:
bool fragment_shader_interlock{}; bool fragment_shader_interlock{};
bool image_format_list{}; bool image_format_list{};
bool pipeline_creation_cache_control{}; bool pipeline_creation_cache_control{};
bool fragment_shader_barycentric{};
bool shader_stencil_export{}; bool shader_stencil_export{};
bool tooling_info{}; bool tooling_info{};
bool debug_utils_supported{}; bool debug_utils_supported{};

View File

@ -96,6 +96,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
.has_geometry_shader = instance.UseGeometryShaders(), .has_geometry_shader = instance.UseGeometryShaders(),
.has_custom_border_color = instance.IsCustomBorderColorSupported(), .has_custom_border_color = instance.IsCustomBorderColorSupported(),
.has_fragment_shader_interlock = instance.IsFragmentShaderInterlockSupported(), .has_fragment_shader_interlock = instance.IsFragmentShaderInterlockSupported(),
.has_fragment_shader_barycentric = instance.IsFragmentShaderBarycentricSupported(),
.has_blend_minmax_factor = false, .has_blend_minmax_factor = false,
.has_minus_one_to_one_range = false, .has_minus_one_to_one_range = false,
.has_logic_op = !instance.NeedsLogicOpEmulation(), .has_logic_op = !instance.NeedsLogicOpEmulation(),
@ -331,8 +332,13 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
bool PipelineCache::UseProgrammableVertexShader(const Pica::Regs& regs, bool PipelineCache::UseProgrammableVertexShader(const Pica::Regs& regs,
Pica::Shader::ShaderSetup& setup, Pica::Shader::ShaderSetup& setup,
const VertexLayout& layout) { const VertexLayout& layout) {
PicaVSConfig config{regs, setup, instance.IsShaderClipDistanceSupported(), // Enable the geometry-shader only if we are actually doing per-fragment lighting
instance.UseGeometryShaders()}; // and care about proper quaternions. Otherwise just use standard vertex+fragment shaders.
// We also don't need the geometry shader if we have the barycentric extension.
const bool use_geometry_shader = instance.UseGeometryShaders() && !regs.lighting.disable &&
!instance.IsFragmentShaderBarycentricSupported();
PicaVSConfig config{regs, setup, instance.IsShaderClipDistanceSupported(), use_geometry_shader};
for (u32 i = 0; i < layout.attribute_count; i++) { for (u32 i = 0; i < layout.attribute_count; i++) {
const VertexAttribute& attr = layout.attributes[i]; const VertexAttribute& attr = layout.attributes[i];

View File

@ -337,6 +337,14 @@ bool RasterizerVulkan::SetupGeometryShader() {
return false; return false;
} }
// Enable the quaternion fix-up geometry-shader only if we are actually doing per-fragment
// lighting and care about proper quaternions. Otherwise just use standard vertex+fragment
// shaders. We also don't need a geometry shader if the barycentric extension is supported.
if (regs.lighting.disable || instance.IsFragmentShaderBarycentricSupported()) {
pipeline_cache.UseTrivialGeometryShader();
return true;
}
return pipeline_cache.UseFixedGeometryShader(regs); return pipeline_cache.UseFixedGeometryShader(regs);
} }

View File

@ -567,6 +567,17 @@ void FragmentModule::WriteLighting() {
} }
} }
// If the barycentric extension is enabled, perform quaternion correction here.
if (use_fragment_shader_barycentric) {
out += "vec4 normquat_0 = normquats[0];\n"
"vec4 normquat_1 = mix(normquats[1], -normquats[1], "
"bvec4(AreQuaternionsOpposite(normquats[0], normquats[1])));\n"
"vec4 normquat_2 = mix(normquats[2], -normquats[2], "
"bvec4(AreQuaternionsOpposite(normquats[0], normquats[2])));\n"
"vec4 normquat = gl_BaryCoord.x * normquat_0 + gl_BaryCoord.y * normquat_1 + "
"gl_BaryCoord.z * normquat_2;\n";
}
// Rotate the surface-local normal by the interpolated normal quaternion to convert it to // Rotate the surface-local normal by the interpolated normal quaternion to convert it to
// eyespace. // eyespace.
out += "vec4 normalized_normquat = normalize(normquat);\n" out += "vec4 normalized_normquat = normalize(normquat);\n"
@ -1231,6 +1242,20 @@ void FragmentModule::DefineExtensions() {
use_fragment_shader_interlock = false; use_fragment_shader_interlock = false;
} }
} }
if (config.lighting.enable) {
use_fragment_shader_barycentric = true;
if (profile.has_fragment_shader_barycentric) {
out += "#extension GL_EXT_fragment_shader_barycentric : enable\n";
out += "#define pervertex pervertexEXT\n";
out += "#define gl_BaryCoord gl_BaryCoordEXT\n";
} else if (profile.has_gl_nv_fragment_shader_barycentric) {
out += "#extension GL_NV_fragment_shader_barycentric : enable\n";
out += "#define pervertex pervertexNV\n";
out += "#define gl_BaryCoord gl_BaryCoordNV\n";
} else {
use_fragment_shader_barycentric = false;
}
}
if (config.EmulateBlend()) { if (config.EmulateBlend()) {
if (profile.has_gl_ext_framebuffer_fetch) { if (profile.has_gl_ext_framebuffer_fetch) {
out += "#extension GL_EXT_shader_framebuffer_fetch : enable\n"; out += "#extension GL_EXT_shader_framebuffer_fetch : enable\n";
@ -1263,7 +1288,11 @@ void FragmentModule::DefineInterface() {
define_input("vec2 texcoord1", Semantic::Texcoord1); define_input("vec2 texcoord1", Semantic::Texcoord1);
define_input("vec2 texcoord2", Semantic::Texcoord2); define_input("vec2 texcoord2", Semantic::Texcoord2);
define_input("float texcoord0_w", Semantic::Texcoord0_W); define_input("float texcoord0_w", Semantic::Texcoord0_W);
if (use_fragment_shader_barycentric) {
define_input("pervertex vec4 normquats[]", Semantic::Normquat);
} else {
define_input("vec4 normquat", Semantic::Normquat); define_input("vec4 normquat", Semantic::Normquat);
}
define_input("vec3 view", Semantic::View); define_input("vec3 view", Semantic::View);
// Output attributes // Output attributes
@ -1360,6 +1389,14 @@ float LookupLightingLUTSigned(int lut_index, float pos) {
return LookupLightingLUT(lut_index, index, delta); return LookupLightingLUT(lut_index, index, delta);
} }
)"; )";
if (use_fragment_shader_barycentric) {
out += R"(
bool AreQuaternionsOpposite(vec4 qa, vec4 qb) {
return (dot(qa, qb) < 0.0);
}
)";
}
} }
void FragmentModule::DefineShadowHelpers() { void FragmentModule::DefineShadowHelpers() {

View File

@ -87,6 +87,7 @@ private:
std::string out; std::string out;
bool use_blend_fallback{}; bool use_blend_fallback{};
bool use_fragment_shader_interlock{}; bool use_fragment_shader_interlock{};
bool use_fragment_shader_barycentric{};
}; };
/** /**

View File

@ -12,6 +12,7 @@ struct Profile {
bool has_geometry_shader{}; bool has_geometry_shader{};
bool has_custom_border_color{}; bool has_custom_border_color{};
bool has_fragment_shader_interlock{}; bool has_fragment_shader_interlock{};
bool has_fragment_shader_barycentric{};
bool has_blend_minmax_factor{}; bool has_blend_minmax_factor{};
bool has_minus_one_to_one_range{}; bool has_minus_one_to_one_range{};
bool has_logic_op{}; bool has_logic_op{};
@ -19,6 +20,7 @@ struct Profile {
bool has_gl_arm_framebuffer_fetch{}; bool has_gl_arm_framebuffer_fetch{};
bool has_gl_nv_fragment_shader_interlock{}; bool has_gl_nv_fragment_shader_interlock{};
bool has_gl_intel_fragment_shader_interlock{}; bool has_gl_intel_fragment_shader_interlock{};
bool has_gl_nv_fragment_shader_barycentric{};
bool is_vulkan{}; bool is_vulkan{};
}; };