From 9d550fbffa963ae11dcb23e08ff566cfb3e0ed10 Mon Sep 17 00:00:00 2001 From: nmzik Date: Tue, 8 Sep 2026 14:34:18 +0400 Subject: [PATCH] graphics: fix last provoking vertex on PS5 --- src/graphics/host_gpu/graphicContext.h | 1 + src/graphics/host_gpu/renderer/debug.cpp | 1 - .../renderer/pipeline/pipelineCache.cpp | 1 + .../renderer/pipeline/pipelineCache.h | 3 +- .../host_gpu/renderer/pipeline/shaders.cpp | 9 +++++ .../presentation/window/vulkanWindow.cpp | 14 +++++++ tests/ShaderRecompilerComputeTests.cpp | 40 +++++++++++++++---- 7 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/graphics/host_gpu/graphicContext.h b/src/graphics/host_gpu/graphicContext.h index 01e661072..ce95fe8a9 100644 --- a/src/graphics/host_gpu/graphicContext.h +++ b/src/graphics/host_gpu/graphicContext.h @@ -31,6 +31,7 @@ struct GraphicContext { bool compute_subgroup_size_control_enabled = false; bool sample_rate_shading_enabled = false; bool attachment_feedback_loop_enabled = false; + bool provoking_vertex_last_enabled = false; bool supports_block_texel_view = false; bool mesh_shader_enabled = false; vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {}; diff --git a/src/graphics/host_gpu/renderer/debug.cpp b/src/graphics/host_gpu/renderer/debug.cpp index 396685a92..6d7ee5390 100644 --- a/src/graphics/host_gpu/renderer/debug.cpp +++ b/src/graphics/host_gpu/renderer/debug.cpp @@ -478,7 +478,6 @@ static void McCheck(const HW::ModeControl& c) { logged = true; } } - EXIT_NOT_IMPLEMENTED(c.provoking_vtx_last != false); EXIT_NOT_IMPLEMENTED(c.persp_corr_dis != false); } diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index 3561821fd..6e0d4b929 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -728,6 +728,7 @@ PipelineCache::Pipeline& PipelineCache::CreateGraphicsPipeline( static_params.cull_back = !rect_list && mc.cull_back; static_params.cull_front = !rect_list && mc.cull_front; static_params.face = mc.face; + static_params.provoking_vtx_last = mc.provoking_vtx_last; static_params.polygon_mode = ResolvePolygonMode(mc, static_params.cull_front, static_params.cull_back); diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h index 38da9af6e..6fa19e74a 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h @@ -49,6 +49,7 @@ struct PipelineStaticParameters { bool cull_front = false; bool cull_back = false; bool face = false; + bool provoking_vtx_last = false; vk::PolygonMode polygon_mode = vk::PolygonMode::eFill; uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {}; uint8_t color_comb_fcn[RENDER_COLOR_ATTACHMENTS_MAX] = {}; @@ -68,7 +69,7 @@ struct PipelineStaticParameters { static_assert(std::is_trivially_copyable_v); static_assert(std::is_standard_layout_v); static_assert(alignof(PipelineStaticParameters) == 1); -static_assert(sizeof(PipelineStaticParameters) == 165); +static_assert(sizeof(PipelineStaticParameters) == 166); struct PipelineRenderingState { std::array color_formats {}; diff --git a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp index f662b1b8d..4ce567aed 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp @@ -595,6 +595,15 @@ void CreatePipelineInternal( #if !defined(__APPLE__) rasterizer.pNext = &clip_ext; #endif + vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex {}; + EXIT_NOT_IMPLEMENTED(static_params.provoking_vtx_last && + !graphics.provoking_vertex_last_enabled); + if (graphics.provoking_vertex_last_enabled) { + provoking_vertex.provokingVertexMode = static_params.provoking_vtx_last + ? vk::ProvokingVertexModeEXT::eLastVertex : vk::ProvokingVertexModeEXT::eFirstVertex; + provoking_vertex.pNext = rasterizer.pNext; + rasterizer.pNext = &provoking_vertex; + } rasterizer.cullMode = cull_mode; rasterizer.frontFace = front_face; rasterizer.polygonMode = static_params.polygon_mode; diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index fac48fd3a..50caec4e6 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -637,7 +637,15 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V feedback_layout.pNext = &feedback_dynamic; supported_features2.pNext = &feedback_layout; } + const bool provoking_extension = + HasExtension(device_extensions, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); + vk::PhysicalDeviceProvokingVertexFeaturesEXT provoking_vertex {}; + if (provoking_extension) { + provoking_vertex.pNext = supported_features2.pNext; + supported_features2.pNext = &provoking_vertex; + } physical_device.getFeatures2(&supported_features2); + graphics.provoking_vertex_last_enabled = provoking_extension && provoking_vertex.provokingVertexLast; graphics.attachment_feedback_loop_enabled = feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout && feedback_dynamic.attachmentFeedbackLoopDynamicState; @@ -753,6 +761,11 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V create_info.pNext = graphics.attachment_feedback_loop_enabled ? static_cast(&feedback_layout) : feedback_dynamic.pNext; + if (graphics.provoking_vertex_last_enabled) { + provoking_vertex.pNext = const_cast(create_info.pNext); + provoking_vertex.transformFeedbackPreservesProvokingVertex = VK_FALSE; + create_info.pNext = &provoking_vertex; + } create_info.flags = {}; create_info.pQueueCreateInfos = &queue_create_info; create_info.queueCreateInfoCount = 1; @@ -1145,6 +1158,7 @@ void WindowContext::CreateVulkan() { graphic_ctx.memory_budget_ext_enabled = true; } for (const auto* extension: {VK_EXT_ROBUSTNESS_2_EXTENSION_NAME, + VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME, VK_EXT_MESH_SHADER_EXTENSION_NAME, VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME}) { if (HasExtension(available_extensions, extension)) { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 49c750699..fdd63e124 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -11534,7 +11534,8 @@ public: test.fragment_code.push_back(EncodeExp0(0x08, 0x1, false)); test.fragment_code.push_back(EncodeExp1(1, 1, 1, 1)); } else { - AppendVMovLiteral(&test.fragment_code, 0, 0x3f800000u); + test.pixel_interpolator_settings = {0x400u}; + test.fragment_code.push_back(EncodeVintrp(0x02, 0, 0, 0, 2)); } test.fragment_code.push_back(EncodeExp0(0x00, 0xf)); test.fragment_code.push_back(EncodeExp1(0, 0, 0, 0)); @@ -11583,8 +11584,8 @@ public: color.desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment; color.image_id = cache.FindImage(color.desc); std::array vertices{ - -0.75f, -0.75f, 1, 1, 1, 1, 0.75f, -0.75f, 1, 1, 1, 1, - 0.0f, 0.75f, 1, 1, 1, 1}; + -0.75f, -0.75f, 1, 1, 1, 1, 0.75f, -0.75f, 0.5f, 1, 1, 1, + 0.0f, 0.75f, 0.25f, 1, 1, 1}; if (depth_feedback) { vertices = {-1, -1, 0, 0, 0, 1, 3, -1, 2, 0, 0, 1, -1, 3, 0, 2, 0, 1}; } @@ -11592,11 +11593,13 @@ public: std::memcpy(vertex_words.data(), vertices.data(), sizeof(vertices)); auto buffer = CreateHostBuffer(name, sizeof(vertices), vk::BufferUsageFlagBits::eVertexBuffer, vertex_words); - const auto pipeline = [&](bool enabled, uint8_t front, uint8_t back) -> PipelineCache::Pipeline & { + const auto pipeline = [&](bool enabled, uint8_t front, uint8_t back, + bool provoking_last = false) -> PipelineCache::Pipeline & { HW::ModeControl mode{}; mode.poly_mode = enabled; mode.polymode_front_ptype = front; mode.polymode_back_ptype = back; + mode.provoking_vtx_last = provoking_last; registers.SetModeControl(mode); return context.GetPipelineCache().CreateGraphicsPipeline( std::span{&color, 1u}, depth, vertex, scheduler.Current(), &pixel, @@ -11686,6 +11689,19 @@ public: line_pixels[interior] == 0 && std::ranges::any_of(line_pixels, [](u32 value) { return value == 0x3f800000u; }), "wireframe did not preserve triangle edges while leaving its interior empty"); + auto &last_vertex = pipeline(true, 2, 2, true); + Require(name, "provoking vertex pipeline cache", + last_vertex.pipeline != filled.pipeline && + pipeline(true, 2, 2, true).pipeline == last_vertex.pipeline && + pipeline(true, 2, 2).pipeline == filled.pipeline, + "first and last provoking vertices did not keep distinct cached pipelines"); + draw(last_vertex); + const auto last_pixels = read_color(); + for (size_t i = 0; i < solid_pixels.size(); i++) { + Require(name, "flat provoking vertex output", + last_pixels[i] == (solid_pixels[i] == 0 ? 0 : 0x3e800000u), + "last-vertex flat shading changed coverage or did not use vertex two"); + } } resources.UnmapMemory(depth_address, allocation_size); scheduler.Finish(); @@ -12997,6 +13013,7 @@ private: m_runtime_context.queue_family = m_queue_family; m_runtime_context.queue = m_queue; m_runtime_context.attachment_feedback_loop_enabled = true; + m_runtime_context.provoking_vertex_last_enabled = true; VmaVulkanFunctions functions{}; functions.vkGetInstanceProcAddr = @@ -13109,9 +13126,11 @@ private: available_feedback_layout.pNext = &available_color_write; vk::PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT available_feedback_dynamic{}; available_feedback_dynamic.pNext = &available_feedback_layout; + vk::PhysicalDeviceProvokingVertexFeaturesEXT available_provoking_vertex{}; + available_provoking_vertex.pNext = &available_feedback_dynamic; vk::PhysicalDeviceFeatures2 available_features2{}; available_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2; - available_features2.pNext = &available_feedback_dynamic; + available_features2.pNext = &available_provoking_vertex; m_physical_device.getFeatures2(&available_features2); Require("VulkanHarness", "dispatch", available_features.shaderStorageImageWriteWithoutFormat == true, @@ -13143,7 +13162,8 @@ private: available_depth_clip.depthClipEnable && available_clip_control.depthClipControl && available_color_write.colorWriteEnable && available_feedback_layout.attachmentFeedbackLoopLayout && - available_feedback_dynamic.attachmentFeedbackLoopDynamicState, + available_feedback_dynamic.attachmentFeedbackLoopDynamicState && + available_provoking_vertex.provokingVertexLast, "production rasterization features are not supported"); float priority = 1.0f; @@ -13191,7 +13211,10 @@ private: vk::PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT feedback_dynamic{}; feedback_dynamic.pNext = &feedback_layout; feedback_dynamic.attachmentFeedbackLoopDynamicState = true; - device_info.pNext = &feedback_dynamic; + vk::PhysicalDeviceProvokingVertexFeaturesEXT provoking_vertex{}; + provoking_vertex.pNext = &feedback_dynamic; + provoking_vertex.provokingVertexLast = available_provoking_vertex.provokingVertexLast; + device_info.pNext = &provoking_vertex; vk::PhysicalDeviceFeatures device_features{}; device_features.shaderStorageImageWriteWithoutFormat = true; device_features.shaderImageGatherExtended = true; @@ -13207,7 +13230,8 @@ private: VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME, VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME, - VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME}; + VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME, + VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME}; device_info.enabledExtensionCount = std::size(device_extensions); device_info.ppEnabledExtensionNames = device_extensions; RequireVk("VulkanHarness", "dispatch",