Add support for the EXT_provoking_vertex Vulkan extension, allowing us to skip software transform for this case.

This commit is contained in:
Henrik Rydgård 2024-07-17 10:36:05 +02:00
parent 2a35a92514
commit 138193a776
13 changed files with 61 additions and 11 deletions

View File

@ -691,6 +691,8 @@ VkResult VulkanContext::CreateDevice(int physical_device) {
extensionsLookup_.KHR_present_wait = EnableDeviceExtension(VK_KHR_PRESENT_WAIT_EXTENSION_NAME, 0);
}
extensionsLookup_.EXT_provoking_vertex = EnableDeviceExtension(VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME, 0);
// Optional features
if (extensionsLookup_.KHR_get_physical_device_properties2 && vkGetPhysicalDeviceFeatures2) {
VkPhysicalDeviceFeatures2 features2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR };
@ -698,17 +700,23 @@ VkResult VulkanContext::CreateDevice(int physical_device) {
VkPhysicalDeviceMultiviewFeatures multiViewFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES };
VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR };
VkPhysicalDevicePresentIdFeaturesKHR presentIdFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR };
VkPhysicalDeviceProvokingVertexFeaturesEXT provokingVertexFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT };
features2.pNext = &multiViewFeatures;
multiViewFeatures.pNext = &presentWaitFeatures;
presentWaitFeatures.pNext = &presentIdFeatures;
presentIdFeatures.pNext = nullptr;
if (extensionsLookup_.EXT_provoking_vertex) {
presentIdFeatures.pNext = &provokingVertexFeatures;
provokingVertexFeatures.pNext = nullptr;
} else {
presentIdFeatures.pNext = nullptr;
}
vkGetPhysicalDeviceFeatures2(physical_devices_[physical_device_], &features2);
deviceFeatures_.available.standard = features2.features;
deviceFeatures_.available.multiview = multiViewFeatures;
deviceFeatures_.available.presentWait = presentWaitFeatures;
deviceFeatures_.available.presentId = presentIdFeatures;
deviceFeatures_.available.provokingVertex = provokingVertexFeatures;
} else {
vkGetPhysicalDeviceFeatures(physical_devices_[physical_device_], &deviceFeatures_.available.standard);
deviceFeatures_.available.multiview = {};
@ -744,6 +752,11 @@ VkResult VulkanContext::CreateDevice(int physical_device) {
if (extensionsLookup_.KHR_present_wait) {
deviceFeatures_.enabled.presentWait.presentWait = deviceFeatures_.available.presentWait.presentWait;
}
deviceFeatures_.enabled.provokingVertex = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT };
if (extensionsLookup_.EXT_provoking_vertex) {
deviceFeatures_.enabled.provokingVertex.provokingVertexLast = true;
}
// deviceFeatures_.enabled.multiview.multiviewGeometryShader = deviceFeatures_.available.multiview.multiviewGeometryShader;
VkPhysicalDeviceFeatures2 features2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 };
@ -762,7 +775,13 @@ VkResult VulkanContext::CreateDevice(int physical_device) {
features2.pNext = &deviceFeatures_.enabled.multiview;
deviceFeatures_.enabled.multiview.pNext = &deviceFeatures_.enabled.presentWait;
deviceFeatures_.enabled.presentWait.pNext = &deviceFeatures_.enabled.presentId;
deviceFeatures_.enabled.presentId.pNext = nullptr;
if (extensionsLookup_.EXT_provoking_vertex) {
// TODO: Write some proper chaining thing.
deviceFeatures_.enabled.presentId.pNext = &deviceFeatures_.enabled.provokingVertex;
deviceFeatures_.enabled.provokingVertex.pNext = nullptr;
} else {
deviceFeatures_.enabled.presentId.pNext = nullptr;
}
} else {
device_info.pEnabledFeatures = &deviceFeatures_.enabled.standard;
}

View File

@ -279,6 +279,7 @@ public:
VkPhysicalDeviceMultiviewFeatures multiview;
VkPhysicalDevicePresentWaitFeaturesKHR presentWait;
VkPhysicalDevicePresentIdFeaturesKHR presentId;
VkPhysicalDeviceProvokingVertexFeaturesEXT provokingVertex;
};
const PhysicalDeviceProps &GetPhysicalDeviceProperties(int i = -1) const {

View File

@ -268,6 +268,7 @@ struct VulkanExtensions {
bool KHR_present_id; // Should probably check the feature flags instead.
bool KHR_present_wait; // Same
bool GOOGLE_display_timing;
bool EXT_provoking_vertex;
// bool EXT_depth_range_unrestricted; // Allows depth outside [0.0, 1.0] in 32-bit float depth buffers.
};

View File

@ -50,6 +50,7 @@ enum class PipelineFlags : u8 {
USES_GEOMETRY_SHADER = (1 << 3),
USES_MULTIVIEW = (1 << 4), // Inherited from the render pass it was created with.
USES_DISCARD = (1 << 5),
USES_FLAT_SHADING = (1 << 6),
};
ENUM_CLASS_BITOPS(PipelineFlags);

View File

@ -90,6 +90,7 @@ public:
VkDynamicState dynamicStates[6]{};
VkPipelineDynamicStateCreateInfo ds{ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
VkPipelineRasterizationProvokingVertexStateCreateInfoEXT rs_provoking{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT };
// Replaced the ShaderStageInfo with promises here so we can wait for compiles to finish.
Promise<VkShaderModule> *vertexShader = nullptr;

View File

@ -1221,8 +1221,6 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
gDesc.dss = depth->info;
raster->ToVulkan(&gDesc.rs);
// Copy bindings from input layout.
gDesc.topology = primToVK[(int)desc.prim];
@ -1244,6 +1242,11 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
raster->ToVulkan(&gDesc.rs);
if (renderManager_.GetVulkanContext()->GetDeviceFeatures().enabled.provokingVertex.provokingVertexLast) {
gDesc.rs.pNext = &gDesc.rs_provoking;
gDesc.rs_provoking.provokingVertexMode = VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT;
}
pipeline->pipeline = renderManager_.CreateGraphicsPipeline(&gDesc, pipelineFlags, 1 << (size_t)RenderPassType::BACKBUFFER, VK_SAMPLE_COUNT_1_BIT, false, tag ? tag : "thin3d");
if (desc.uniformDesc) {
@ -1564,6 +1567,7 @@ std::vector<std::string> VKContext::GetFeatureList() const {
AddFeature(features, "multiviewGeometryShader", vulkan_->GetDeviceFeatures().available.multiview.multiviewGeometryShader, vulkan_->GetDeviceFeatures().enabled.multiview.multiviewGeometryShader);
AddFeature(features, "presentId", vulkan_->GetDeviceFeatures().available.presentId.presentId, vulkan_->GetDeviceFeatures().enabled.presentId.presentId);
AddFeature(features, "presentWait", vulkan_->GetDeviceFeatures().available.presentWait.presentWait, vulkan_->GetDeviceFeatures().enabled.presentWait.presentWait);
AddFeature(features, "provokingVertexLast", vulkan_->GetDeviceFeatures().available.provokingVertex.provokingVertexLast, vulkan_->GetDeviceFeatures().enabled.provokingVertex.provokingVertexLast);
features.emplace_back(std::string("Preferred depth buffer format: ") + VulkanFormatToString(vulkan_->GetDeviceInfo().preferredDepthStencilFormat));

View File

@ -132,8 +132,8 @@ void VR_GetResolution(engine_t* engine, int *pWidth, int *pHeight) {
*pHeight = height;
}
*pWidth *= VR_GetConfigFloat(VR_CONFIG_VIEWPORT_SUPERSAMPLING);
*pHeight *= VR_GetConfigFloat(VR_CONFIG_VIEWPORT_SUPERSAMPLING);
*pWidth = (int)(*pWidth * VR_GetConfigFloat(VR_CONFIG_VIEWPORT_SUPERSAMPLING));
*pHeight = (int)(*pHeight * VR_GetConfigFloat(VR_CONFIG_VIEWPORT_SUPERSAMPLING));
}
void VR_Recenter(engine_t* engine) {

View File

@ -124,6 +124,10 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
bool flatBug = bugs.Has(Draw::Bugs::BROKEN_FLAT_IN_SHADER) && g_Config.bVendorBugChecksEnabled;
bool doFlatShading = id.Bit(FS_BIT_FLATSHADE) && !flatBug;
if (doFlatShading) {
*fragmentShaderFlags |= FragmentShaderFlags::USES_FLAT_SHADING;
}
ShaderDepalMode shaderDepalMode = (ShaderDepalMode)id.Bits(FS_BIT_SHADER_DEPAL_MODE, 2);
if (texture3D) {
shaderDepalMode = ShaderDepalMode::OFF;

View File

@ -47,6 +47,7 @@ struct FShaderID;
// Can technically be deduced from the fragment shader ID, but this is safer.
enum class FragmentShaderFlags : u32 {
USES_DISCARD = 2,
USES_FLAT_SHADING = 4,
};
ENUM_CLASS_BITOPS(FragmentShaderFlags);

View File

@ -229,12 +229,17 @@ void DrawEngineVulkan::DoFlush() {
GEPrimitiveType prim = prevPrim_;
// Always use software for flat shading to fix the provoking index.
bool useHWTransform = CanUseHardwareTransform(prim) && (tess || gstate.getShadeMode() != GE_SHADE_FLAT);
// Always use software for flat shading to fix the provoking index
// if the provoking vertex extension is not available.
bool provokingVertexOk = (tess || gstate.getShadeMode() != GE_SHADE_FLAT);
if (renderManager->GetVulkanContext()->GetDeviceFeatures().enabled.provokingVertex.provokingVertexLast) {
provokingVertexOk = true;
}
bool useHWTransform = CanUseHardwareTransform(prim) && provokingVertexOk;
uint32_t ibOffset;
uint32_t vbOffset;
// The optimization to avoid indexing isn't really worth it on Vulkan since it means creating more pipelines.
// This could be avoided with the new dynamic state extensions, but not available enough on mobile.
const bool forceIndexed = draw_->GetDeviceCaps().verySlowShaderCompiler;
@ -399,6 +404,10 @@ void DrawEngineVulkan::DoFlush() {
params.allowClear = framebufferManager_->UseBufferedRendering();
params.allowSeparateAlphaClear = false;
params.provokeFlatFirst = true;
if (renderManager->GetVulkanContext()->GetDeviceFeatures().enabled.provokingVertex.provokingVertexLast) {
// We can get the OpenGL behavior, no need for workarounds.
params.provokeFlatFirst = false;
}
params.flippedY = true;
params.usesHalfZ = true;

View File

@ -285,6 +285,11 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
rs.polygonMode = VK_POLYGON_MODE_FILL;
rs.depthClampEnable = key.depthClampEnable;
if (renderManager->GetVulkanContext()->GetDeviceFeatures().enabled.provokingVertex.provokingVertexLast) {
rs.pNext = &desc->rs_provoking;
desc->rs_provoking.provokingVertexMode = VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT;
}
desc->fragmentShaderSource = fs->GetShaderString(SHADER_STRING_SOURCE_CODE);
desc->vertexShaderSource = vs->GetShaderString(SHADER_STRING_SOURCE_CODE);
if (gs) {
@ -376,6 +381,9 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager *
if (fs->Flags() & FragmentShaderFlags::USES_DISCARD) {
pipelineFlags |= PipelineFlags::USES_DISCARD;
}
if (fs->Flags() & FragmentShaderFlags::USES_FLAT_SHADING) {
pipelineFlags |= PipelineFlags::USES_FLAT_SHADING;
}
if (vs->Flags() & VertexShaderFlags::MULTI_VIEW) {
pipelineFlags |= PipelineFlags::USES_MULTIVIEW;
}

View File

@ -75,6 +75,7 @@ struct VulkanPipeline {
bool UsesDepthStencil() const { return (pipelineFlags & PipelineFlags::USES_DEPTH_STENCIL) != 0; }
bool UsesGeometryShader() const { return (pipelineFlags & PipelineFlags::USES_GEOMETRY_SHADER) != 0; }
bool UsesDiscard() const { return (pipelineFlags & PipelineFlags::USES_DISCARD) != 0; }
bool UsesFlatShading() const { return (pipelineFlags & PipelineFlags::USES_FLAT_SHADING) != 0; }
u32 GetVariantsBitmask() const;
};

View File

@ -616,7 +616,7 @@ void GPUDriverTestScreen::ShaderTest(UIContext &dc) {
// Draw rectangle that should be flat shaded
dc.BeginPipeline(flatShadingPipeline_, samplerNearest_);
// There is a "provoking vertex" difference here between GL and Vulkan when using flat shading. One gets one color, one gets the other.
// Wherever possible we should reconfigure the GL provoking vertex to match Vulkan, probably.
// However, we now use the VK_EXT_provoking_vertex extension to make it match up (and match with the PSP).
dc.DrawImageVGradient(ImageID("I_ICON"), 0xFFFFFFFF, 0xFF808080, bounds);
dc.Flush();