From 1bdedc17ad2de38c3e775e4153e481d0b36921ba Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 15:12:47 -0400 Subject: [PATCH 01/12] [nce] Added case for access fault handling to manage page edge cases --- src/core/arm/nce/arm_nce.cpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index bbff9f2829..8949312296 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -43,6 +43,7 @@ fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) { using namespace Common::Literals; constexpr u32 StackSize = 128_KiB; +constexpr u64 SplitPageAccessWindow = 64; } // namespace @@ -158,18 +159,33 @@ bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, } bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) { + auto& host_ctx = static_cast(raw_context)->uc_mcontext; + auto* fpctx = GetFloatingPointState(host_ctx); auto* info = static_cast(raw_info); - // Try to handle an invalid access. - // TODO: handle accesses which split a page? - const Common::ProcessAddress addr = - (reinterpret_cast(info->si_addr) & ~Memory::YUZU_PAGEMASK); + const u64 fault_addr = reinterpret_cast(info->si_addr); + const Common::ProcessAddress addr = fault_addr & ~Memory::YUZU_PAGEMASK; + const u64 page_offset = fault_addr & Memory::YUZU_PAGEMASK; auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory(); - if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) { + bool handled = memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE); + + if (page_offset < SplitPageAccessWindow && addr >= Memory::YUZU_PAGESIZE) { + handled |= memory.InvalidateNCE(addr - Memory::YUZU_PAGESIZE, Memory::YUZU_PAGESIZE); + } + if (page_offset + SplitPageAccessWindow > Memory::YUZU_PAGESIZE) { + handled |= memory.InvalidateNCE(addr + Memory::YUZU_PAGESIZE, Memory::YUZU_PAGESIZE); + } + + if (handled) { // We handled the access successfully and are returning to guest code. return true; } + if (auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx); next_pc) { + host_ctx.pc = *next_pc; + return true; + } + // We couldn't handle the access. return HandleFailedGuestFault(guest_ctx, raw_info, raw_context); } From b7087a16bacbf817b5e5f8010268ce64da741bc1 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 15:37:29 -0400 Subject: [PATCH 02/12] [nce] Added dual channel handling for guest access faults --- src/core/arm/nce/arm_nce.cpp | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index 8949312296..807a8ba919 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -45,6 +45,35 @@ using namespace Common::Literals; constexpr u32 StackSize = 128_KiB; constexpr u64 SplitPageAccessWindow = 64; +[[nodiscard]] constexpr u64 AlignDownPage(u64 addr) { + return addr & ~u64{Memory::YUZU_PAGEMASK}; +} + +[[nodiscard]] bool IsNearPageBoundary(u64 addr) { + const u64 page_offset = addr & Memory::YUZU_PAGEMASK; + return page_offset < SplitPageAccessWindow || + page_offset + SplitPageAccessWindow > Memory::YUZU_PAGESIZE; +} + +[[nodiscard]] bool IsNearTlsWindow(u64 tls_base, u64 fault_addr) { + if (tls_base == 0) { + return false; + } + + const u64 tls_first_page = AlignDownPage(tls_base); + const u64 tls_last_byte = tls_base + Kernel::Svc::ThreadLocalRegionSize - 1; + const u64 tls_last_page = AlignDownPage(tls_last_byte); + const u64 fault_page = AlignDownPage(fault_addr); + + return fault_page + Memory::YUZU_PAGESIZE >= tls_first_page && + fault_page <= tls_last_page + Memory::YUZU_PAGESIZE; +} + +[[nodiscard]] bool ShouldUsePreciseAccessChannel(const GuestContext* guest_ctx, u64 fault_addr) { + return IsNearPageBoundary(fault_addr) || IsNearTlsWindow(guest_ctx->tpidrro_el0, fault_addr) || + IsNearTlsWindow(guest_ctx->tpidr_el0, fault_addr); +} + } // namespace void* ArmNce::RestoreGuestContext(void* raw_context) { @@ -167,6 +196,15 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi const Common::ProcessAddress addr = fault_addr & ~Memory::YUZU_PAGEMASK; const u64 page_offset = fault_addr & Memory::YUZU_PAGEMASK; auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory(); + const bool prefer_precise_channel = ShouldUsePreciseAccessChannel(guest_ctx, fault_addr); + + if (prefer_precise_channel) { + if (auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx); next_pc) { + host_ctx.pc = *next_pc; + return true; + } + } + bool handled = memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE); if (page_offset < SplitPageAccessWindow && addr >= Memory::YUZU_PAGESIZE) { From ca42578d4ecd9aae22a26bd3bb6a12631f0da9ab Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 15:55:06 -0400 Subject: [PATCH 03/12] [nce] Added "tainted" page fault handling inside dual channel --- src/core/arm/nce/arm_nce.cpp | 22 ++++++++++++++++++++-- src/core/arm/nce/arm_nce.h | 10 ++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index 807a8ba919..909bdbe046 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -44,6 +44,7 @@ fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) { using namespace Common::Literals; constexpr u32 StackSize = 128_KiB; constexpr u64 SplitPageAccessWindow = 64; +constexpr size_t MaxPreciseAccessPages = 256; [[nodiscard]] constexpr u64 AlignDownPage(u64 addr) { return addr & ~u64{Memory::YUZU_PAGEMASK}; @@ -191,15 +192,18 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi auto& host_ctx = static_cast(raw_context)->uc_mcontext; auto* fpctx = GetFloatingPointState(host_ctx); auto* info = static_cast(raw_info); + auto* parent = guest_ctx->parent; const u64 fault_addr = reinterpret_cast(info->si_addr); const Common::ProcessAddress addr = fault_addr & ~Memory::YUZU_PAGEMASK; const u64 page_offset = fault_addr & Memory::YUZU_PAGEMASK; - auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory(); - const bool prefer_precise_channel = ShouldUsePreciseAccessChannel(guest_ctx, fault_addr); + auto& memory = parent->m_running_thread->GetOwnerProcess()->GetMemory(); + const bool prefer_precise_channel = ShouldUsePreciseAccessChannel(guest_ctx, fault_addr) || + parent->IsPreciseAccessPage(fault_addr); if (prefer_precise_channel) { if (auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx); next_pc) { + parent->MarkPreciseAccessPage(fault_addr); host_ctx.pc = *next_pc; return true; } @@ -220,6 +224,7 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi } if (auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx); next_pc) { + parent->MarkPreciseAccessPage(fault_addr); host_ctx.pc = *next_pc; return true; } @@ -236,6 +241,19 @@ void ArmNce::HandleHostAccessFault(int sig, void* raw_info, void* raw_context) { return g_orig_segv_action.sa_sigaction(sig, static_cast(raw_info), raw_context); } +bool ArmNce::IsPreciseAccessPage(u64 addr) const { + const std::scoped_lock lk{m_precise_pages_guard}; + return m_precise_pages.contains(AlignDownPage(addr)); +} + +void ArmNce::MarkPreciseAccessPage(u64 addr) { + const std::scoped_lock lk{m_precise_pages_guard}; + if (m_precise_pages.size() >= MaxPreciseAccessPages) { + m_precise_pages.clear(); + } + m_precise_pages.insert(AlignDownPage(addr)); +} + void ArmNce::LockThread(Kernel::KThread* thread) { auto* thread_params = &thread->GetNativeExecutionParameters(); LockThreadParameters(thread_params); diff --git a/src/core/arm/nce/arm_nce.h b/src/core/arm/nce/arm_nce.h index be9b304c4c..8be293055c 100644 --- a/src/core/arm/nce/arm_nce.h +++ b/src/core/arm/nce/arm_nce.h @@ -1,9 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include +#include #include "core/arm/arm_interface.h" #include "core/arm/nce/guest_context.h" @@ -77,6 +81,9 @@ private: static void HandleHostAlignmentFault(int sig, void* info, void* raw_context); static void HandleHostAccessFault(int sig, void* info, void* raw_context); + bool IsPreciseAccessPage(u64 addr) const; + void MarkPreciseAccessPage(u64 addr); + public: Core::System& m_system; @@ -88,6 +95,9 @@ public: GuestContext m_guest_ctx{}; Kernel::KThread* m_running_thread{}; + mutable std::mutex m_precise_pages_guard{}; + std::unordered_set m_precise_pages{}; + // Stack for signal processing. std::unique_ptr m_stack{}; }; From 11d5c0f96a619d84fa93653fcb5b7654d2d5a347 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 16:07:05 -0400 Subject: [PATCH 04/12] [nce] Adjusted precise access fault window handling + decay mechanism --- src/core/arm/nce/arm_nce.cpp | 45 ++++++++++++++++++++++++++++++++---- src/core/arm/nce/arm_nce.h | 6 +++-- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index 909bdbe046..7f8326bf56 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -45,6 +45,7 @@ using namespace Common::Literals; constexpr u32 StackSize = 128_KiB; constexpr u64 SplitPageAccessWindow = 64; constexpr size_t MaxPreciseAccessPages = 256; +constexpr u8 MaxPreciseAccessPageWeight = 4; [[nodiscard]] constexpr u64 AlignDownPage(u64 addr) { return addr & ~u64{Memory::YUZU_PAGEMASK}; @@ -203,7 +204,7 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi if (prefer_precise_channel) { if (auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx); next_pc) { - parent->MarkPreciseAccessPage(fault_addr); + parent->MarkPreciseAccessFaultWindow(fault_addr); host_ctx.pc = *next_pc; return true; } @@ -224,7 +225,7 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi } if (auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx); next_pc) { - parent->MarkPreciseAccessPage(fault_addr); + parent->MarkPreciseAccessFaultWindow(fault_addr); host_ctx.pc = *next_pc; return true; } @@ -248,10 +249,44 @@ bool ArmNce::IsPreciseAccessPage(u64 addr) const { void ArmNce::MarkPreciseAccessPage(u64 addr) { const std::scoped_lock lk{m_precise_pages_guard}; - if (m_precise_pages.size() >= MaxPreciseAccessPages) { - m_precise_pages.clear(); + const u64 page = AlignDownPage(addr); + if (auto it = m_precise_pages.find(page); it != m_precise_pages.end()) { + it->second = std::min(MaxPreciseAccessPageWeight, static_cast(it->second + 1)); + return; + } + + while (m_precise_pages.size() >= MaxPreciseAccessPages) { + DecayPreciseAccessPagesLocked(); + } + + m_precise_pages.emplace(page, 1); +} + +void ArmNce::MarkPreciseAccessFaultWindow(u64 addr) { + MarkPreciseAccessPage(addr); + + if (!IsNearPageBoundary(addr)) { + return; + } + + const u64 page_offset = addr & Memory::YUZU_PAGEMASK; + if (page_offset < SplitPageAccessWindow && addr >= Memory::YUZU_PAGESIZE) { + MarkPreciseAccessPage(addr - Memory::YUZU_PAGESIZE); + } + if (page_offset + SplitPageAccessWindow > Memory::YUZU_PAGESIZE) { + MarkPreciseAccessPage(addr + Memory::YUZU_PAGESIZE); + } +} + +void ArmNce::DecayPreciseAccessPagesLocked() { + for (auto it = m_precise_pages.begin(); it != m_precise_pages.end();) { + if (it->second > 1) { + --it->second; + ++it; + } else { + it = m_precise_pages.erase(it); + } } - m_precise_pages.insert(AlignDownPage(addr)); } void ArmNce::LockThread(Kernel::KThread* thread) { diff --git a/src/core/arm/nce/arm_nce.h b/src/core/arm/nce/arm_nce.h index 8be293055c..48c82c8437 100644 --- a/src/core/arm/nce/arm_nce.h +++ b/src/core/arm/nce/arm_nce.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include #include "core/arm/arm_interface.h" #include "core/arm/nce/guest_context.h" @@ -83,6 +83,8 @@ private: bool IsPreciseAccessPage(u64 addr) const; void MarkPreciseAccessPage(u64 addr); + void MarkPreciseAccessFaultWindow(u64 addr); + void DecayPreciseAccessPagesLocked(); public: Core::System& m_system; @@ -96,7 +98,7 @@ public: Kernel::KThread* m_running_thread{}; mutable std::mutex m_precise_pages_guard{}; - std::unordered_set m_precise_pages{}; + std::unordered_map m_precise_pages{}; // Stack for signal processing. std::unique_ptr m_stack{}; From 4343874a69aecd3bedcd5a00e622d771d60cf3bd Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 16:30:03 -0400 Subject: [PATCH 05/12] [vulkan] Removal Primitive Topology --- src/video_core/renderer_vulkan/fixed_pipeline_state.cpp | 4 +--- src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | 1 - src/video_core/renderer_vulkan/vk_rasterizer.cpp | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index fa25d99016..882ddb3c13 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -103,9 +103,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe tessellation_clockwise.Assign(regs.tessellation.params.output_primitives.Value() == Maxwell::Tessellation::OutputPrimitives::Triangles_CW); patch_control_points_minus_one.Assign(regs.patch_vertices - 1); - const bool can_normalize_topology = - features.has_extended_dynamic_state && features.has_extended_dynamic_state_2; - topology.Assign(can_normalize_topology ? NormalizeDynamicTopologyClass(topology_) : topology_); + topology.Assign(topology_); msaa_mode.Assign(regs.anti_alias_samples_mode); raw2 = 0; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index f60fe20b9a..85430f678d 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -910,7 +910,6 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT_EXT, VK_DYNAMIC_STATE_CULL_MODE_EXT, VK_DYNAMIC_STATE_FRONT_FACE_EXT, - VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT, VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT, VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT, diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index e01bf3a11a..c79c74e2d9 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1083,7 +1083,6 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateCullMode(regs); UpdateDepthCompareOp(regs); UpdateFrontFace(regs); - UpdatePrimitiveTopology(regs); UpdateStencilOp(regs); if (state_tracker.TouchStateEnable()) { UpdateDepthBoundsTestEnable(regs); From 1d0a3e83faf8f350a1040b7c271e386b7ce25b62 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 17:23:46 -0400 Subject: [PATCH 06/12] [vulkan] Removed topologies --- .../renderer_vulkan/fixed_pipeline_state.cpp | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 882ddb3c13..c38c07c76b 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -51,37 +51,6 @@ void RefreshXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& state.varyings = regs.stream_out_layout; } -Maxwell::PrimitiveTopology NormalizeDynamicTopologyClass(Maxwell::PrimitiveTopology topology) { - switch (topology) { - case Maxwell::PrimitiveTopology::Points: - return Maxwell::PrimitiveTopology::Points; - - case Maxwell::PrimitiveTopology::Lines: - case Maxwell::PrimitiveTopology::LineStrip: - return Maxwell::PrimitiveTopology::Lines; - - case Maxwell::PrimitiveTopology::Triangles: - case Maxwell::PrimitiveTopology::TriangleStrip: - case Maxwell::PrimitiveTopology::TriangleFan: - case Maxwell::PrimitiveTopology::Quads: - case Maxwell::PrimitiveTopology::QuadStrip: - case Maxwell::PrimitiveTopology::Polygon: - case Maxwell::PrimitiveTopology::LineLoop: - return Maxwell::PrimitiveTopology::Triangles; - - case Maxwell::PrimitiveTopology::LinesAdjacency: - case Maxwell::PrimitiveTopology::LineStripAdjacency: - return Maxwell::PrimitiveTopology::LinesAdjacency; - - case Maxwell::PrimitiveTopology::TrianglesAdjacency: - case Maxwell::PrimitiveTopology::TriangleStripAdjacency: - return Maxwell::PrimitiveTopology::TrianglesAdjacency; - - case Maxwell::PrimitiveTopology::Patches: - return Maxwell::PrimitiveTopology::Patches; - } - return topology; -} } // Anonymous namespace void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFeatures& features) { From e98280bee9074c0dedce210b4f2a1a501385e04f Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 19:13:09 -0400 Subject: [PATCH 07/12] [vulkan] Removal of dynamic viewport/scissor --- .../renderer_vulkan/vk_graphics_pipeline.cpp | 11 +++-- .../renderer_vulkan/vk_rasterizer.cpp | 46 ++++--------------- 2 files changed, 15 insertions(+), 42 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 85430f678d..9923acdac4 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -692,9 +692,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pNext = nullptr, .flags = 0, - .viewportCount = key.state.extended_dynamic_state ? 0u : num_viewports, + .viewportCount = num_viewports, .pViewports = nullptr, - .scissorCount = key.state.extended_dynamic_state ? 0u : num_viewports, + .scissorCount = num_viewports, .pScissors = nullptr, }; if (device.IsNvViewportSwizzleSupported()) { @@ -906,8 +906,6 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { } if (key.state.extended_dynamic_state) { static constexpr std::array extended{ - VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT_EXT, - VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT_EXT, VK_DYNAMIC_STATE_CULL_MODE_EXT, VK_DYNAMIC_STATE_FRONT_FACE_EXT, VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT, @@ -923,6 +921,11 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { dynamic_states.push_back(VK_DYNAMIC_STATE_SCISSOR); } + if (key.state.extended_dynamic_state) { + dynamic_states.push_back(VK_DYNAMIC_STATE_VIEWPORT); + dynamic_states.push_back(VK_DYNAMIC_STATE_SCISSOR); + } + // EDS2 - Core (3 states) if (key.state.extended_dynamic_state_2) { static constexpr std::array extended2{ diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index c79c74e2d9..0d1f2f75bd 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1159,16 +1159,8 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg .minDepth = 0.0f, .maxDepth = 1.0f, }; - GraphicsPipeline* pipeline = pipeline_cache.CurrentGraphicsPipeline(); - const bool use_viewport_with_count = device.IsExtExtendedDynamicStateSupported() && - (!pipeline || pipeline->UsesExtendedDynamicState()); - scheduler.Record([viewport, use_viewport_with_count](vk::CommandBuffer cmdbuf) { - if (use_viewport_with_count) { - std::array viewports{viewport}; - cmdbuf.SetViewportWithCountEXT(viewports); - } else { - cmdbuf.SetViewport(0, viewport); - } + scheduler.Record([viewport](vk::CommandBuffer cmdbuf) { + cmdbuf.SetViewport(0, viewport); }); return; } @@ -1184,17 +1176,10 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg GetViewportState(device, regs, 12, scale), GetViewportState(device, regs, 13, scale), GetViewportState(device, regs, 14, scale), GetViewportState(device, regs, 15, scale), }; - GraphicsPipeline* pipeline = pipeline_cache.CurrentGraphicsPipeline(); - const bool use_viewport_with_count = device.IsExtExtendedDynamicStateSupported() && - (!pipeline || pipeline->UsesExtendedDynamicState()); - scheduler.Record([this, viewport_list, use_viewport_with_count](vk::CommandBuffer cmdbuf) { + scheduler.Record([this, viewport_list](vk::CommandBuffer cmdbuf) { const u32 num_viewports = std::min(device.GetMaxViewports(), Maxwell::NumViewports); const vk::Span viewports(viewport_list.data(), num_viewports); - if (use_viewport_with_count) { - cmdbuf.SetViewportWithCountEXT(viewports); - } else { - cmdbuf.SetViewport(0, viewports); - } + cmdbuf.SetViewport(0, viewports); }); } @@ -1215,16 +1200,8 @@ void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs scissor.offset.y = static_cast(y); scissor.extent.width = width; scissor.extent.height = height; - GraphicsPipeline* pipeline = pipeline_cache.CurrentGraphicsPipeline(); - const bool use_scissor_with_count = device.IsExtExtendedDynamicStateSupported() && - (!pipeline || pipeline->UsesExtendedDynamicState()); - scheduler.Record([scissor, use_scissor_with_count](vk::CommandBuffer cmdbuf) { - if (use_scissor_with_count) { - std::array scissors{scissor}; - cmdbuf.SetScissorWithCountEXT(scissors); - } else { - cmdbuf.SetScissor(0, scissor); - } + scheduler.Record([scissor](vk::CommandBuffer cmdbuf) { + cmdbuf.SetScissor(0, scissor); }); return; } @@ -1252,17 +1229,10 @@ void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs GetScissorState(regs, 14, up_scale, down_shift), GetScissorState(regs, 15, up_scale, down_shift), }; - GraphicsPipeline* pipeline = pipeline_cache.CurrentGraphicsPipeline(); - const bool use_scissor_with_count = device.IsExtExtendedDynamicStateSupported() && - (!pipeline || pipeline->UsesExtendedDynamicState()); - scheduler.Record([this, scissor_list, use_scissor_with_count](vk::CommandBuffer cmdbuf) { + scheduler.Record([this, scissor_list](vk::CommandBuffer cmdbuf) { const u32 num_scissors = std::min(device.GetMaxViewports(), Maxwell::NumViewports); const vk::Span scissors(scissor_list.data(), num_scissors); - if (use_scissor_with_count) { - cmdbuf.SetScissorWithCountEXT(scissors); - } else { - cmdbuf.SetScissor(0, scissors); - } + cmdbuf.SetScissor(0, scissors); }); } From a02e8a5679a953545a206207612e4339580df3d6 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 19:52:20 -0400 Subject: [PATCH 08/12] [vulkan] Removed dynamic culling mode/ front face. --- src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | 9 +-------- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 4 +--- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 9923acdac4..8a96a11a22 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -576,12 +576,7 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, } void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { - FixedPipelineState::DynamicState dynamic{}; - if (!key.state.extended_dynamic_state) { - dynamic = key.state.dynamic_state; - } else { - dynamic.raw1 = key.state.dynamic_state.raw1; - } + const FixedPipelineState::DynamicState dynamic{key.state.dynamic_state}; static_vector vertex_bindings; static_vector vertex_binding_divisors; static_vector vertex_attributes; @@ -906,8 +901,6 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { } if (key.state.extended_dynamic_state) { static constexpr std::array extended{ - VK_DYNAMIC_STATE_CULL_MODE_EXT, - VK_DYNAMIC_STATE_FRONT_FACE_EXT, VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT, VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT, diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 0d1f2f75bd..a8bbaadc33 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1078,11 +1078,9 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateLineWidth(regs); UpdateLineStipple(regs); - // EDS1: CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest + // EDS1: DepthCompare, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest if (device.IsExtExtendedDynamicStateSupported() && pipeline && pipeline->UsesExtendedDynamicState()) { - UpdateCullMode(regs); UpdateDepthCompareOp(regs); - UpdateFrontFace(regs); UpdateStencilOp(regs); if (state_tracker.TouchStateEnable()) { UpdateDepthBoundsTestEnable(regs); From d1466b0e9d30dbef81101cd2eeef3360d645aee6 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 21:13:26 -0400 Subject: [PATCH 09/12] Revert "[debug] Instrumentalization for EDS related pipeline worker failure." --- .../renderer_vulkan/vk_pipeline_cache.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index c6e6e9286f..2aff10e15b 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -751,19 +751,6 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( descriptor_pool, guest_descriptor_queue, thread_worker, statistics, render_pass_cache, key, std::move(modules), infos); -} catch (const vk::Exception& exception) { - const auto hash = key.Hash(); - LOG_ERROR( - Render_Vulkan, - "Failed to create graphics pipeline 0x{:016x}: {} (result={}, eds={}, eds2={}, " - "eds2_logic_op={}, topology={}, provoking_last={}, xfb={}, conservative={})", - hash, exception.what(), static_cast(exception.GetResult()), - key.state.extended_dynamic_state != 0, key.state.extended_dynamic_state_2 != 0, - key.state.extended_dynamic_state_2_logic_op != 0, static_cast(key.state.topology.Value()), - key.state.provoking_vertex_last != 0, key.state.xfb_enabled != 0, - key.state.conservative_raster_enable != 0); - return nullptr; - } catch (const Shader::Exception& exception) { auto hash = key.Hash(); size_t env_index{0}; From 6985aaf61452363fb87af7f15902f7f11975747e Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 22:16:08 -0400 Subject: [PATCH 10/12] [vulkan] Adjusted dynamic state handling + update state tracker for extended dynamic states --- .../renderer_vulkan/fixed_pipeline_state.cpp | 8 +-- .../renderer_vulkan/fixed_pipeline_state.h | 8 +-- .../renderer_vulkan/vk_graphics_pipeline.cpp | 21 +++--- .../renderer_vulkan/vk_rasterizer.cpp | 67 ++++++++++++++----- .../renderer_vulkan/vk_scheduler.cpp | 11 +-- .../renderer_vulkan/vk_state_tracker.h | 4 ++ 6 files changed, 74 insertions(+), 45 deletions(-) diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index c38c07c76b..8371440b23 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -131,15 +131,11 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe } dynamic_state.raw1 = 0; dynamic_state.raw2 = 0; - if (!extended_dynamic_state) { - dynamic_state.Refresh(regs); - } + dynamic_state.Refresh(regs); std::ranges::transform(regs.vertex_streams, vertex_strides.begin(), [](const auto& array) { return static_cast(array.stride.Value()); }); - if (!extended_dynamic_state_2) { - dynamic_state.Refresh2(regs, topology_, extended_dynamic_state_2); - } + dynamic_state.Refresh2(regs, topology_, false); if (maxwell3d.dirty.flags[Dirty::Blending]) { maxwell3d.dirty.flags[Dirty::Blending] = false; for (size_t index = 0; index < attachments.size(); ++index) { diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 030c62a883..77d44e6d49 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -254,11 +254,9 @@ struct FixedPipelineState { // When transform feedback is enabled, use the whole struct return sizeof(*this); } - if (extended_dynamic_state) { - // Exclude dynamic state - return offsetof(FixedPipelineState, vertex_strides); - } - // Default + // Always include the cached dynamic-state payload in the key. Some members of + // `dynamic_state` still feed static pipeline state even when EDS is enabled, + // and excluding the whole block causes incorrect pipeline reuse. return offsetof(FixedPipelineState, xfb_state); } }; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 8a96a11a22..7fb7ab9ba7 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -531,12 +531,7 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, } const void* const descriptor_data{guest_descriptor_queue.UpdateData()}; - FixedPipelineState::DynamicState dynamic_state{}; - if (!key.state.extended_dynamic_state) { - dynamic_state = key.state.dynamic_state; - } else { - dynamic_state.raw1 = key.state.dynamic_state.raw1; - } + const FixedPipelineState::DynamicState dynamic_state{key.state.dynamic_state}; scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(), is_rescaling, update_rescaling, uses_render_area = render_area.uses_render_area, @@ -687,9 +682,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pNext = nullptr, .flags = 0, - .viewportCount = num_viewports, + .viewportCount = key.state.extended_dynamic_state ? 0u : num_viewports, .pViewports = nullptr, - .scissorCount = num_viewports, + .scissorCount = key.state.extended_dynamic_state ? 0u : num_viewports, .pScissors = nullptr, }; if (device.IsNvViewportSwizzleSupported()) { @@ -901,6 +896,11 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { } if (key.state.extended_dynamic_state) { static constexpr std::array extended{ + VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT_EXT, + VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT_EXT, + VK_DYNAMIC_STATE_CULL_MODE_EXT, + VK_DYNAMIC_STATE_FRONT_FACE_EXT, + VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT, VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT, VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT, @@ -914,11 +914,6 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { dynamic_states.push_back(VK_DYNAMIC_STATE_SCISSOR); } - if (key.state.extended_dynamic_state) { - dynamic_states.push_back(VK_DYNAMIC_STATE_VIEWPORT); - dynamic_states.push_back(VK_DYNAMIC_STATE_SCISSOR); - } - // EDS2 - Core (3 states) if (key.state.extended_dynamic_state_2) { static constexpr std::array extended2{ diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index a8bbaadc33..0002fceec6 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -277,8 +277,9 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { if (!pipeline->Configure(is_indexed)) return; - if (pipeline->UsesExtendedDynamicState()) { - state_tracker.InvalidateStateEnableFlag(); + if (pipeline->UsesExtendedDynamicState() || pipeline->UsesExtendedDynamicState2() || + pipeline->UsesExtendedDynamicState2LogicOp()) { + state_tracker.InvalidateExtendedDynamicStates(); } HandleTransformFeedback(); @@ -1078,16 +1079,18 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateLineWidth(regs); UpdateLineStipple(regs); - // EDS1: DepthCompare, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest + // EDS1: CullMode, DepthCompare, FrontFace, PrimitiveTopology, StencilOp, + // DepthBoundsTest, DepthTest, DepthWrite, StencilTest if (device.IsExtExtendedDynamicStateSupported() && pipeline && pipeline->UsesExtendedDynamicState()) { + UpdateCullMode(regs); UpdateDepthCompareOp(regs); + UpdateFrontFace(regs); + UpdatePrimitiveTopology(regs); UpdateStencilOp(regs); - if (state_tracker.TouchStateEnable()) { - UpdateDepthBoundsTestEnable(regs); - UpdateDepthTestEnable(regs); - UpdateDepthWriteEnable(regs); - UpdateStencilTestEnable(regs); - } + UpdateDepthBoundsTestEnable(regs); + UpdateDepthTestEnable(regs); + UpdateDepthWriteEnable(regs); + UpdateStencilTestEnable(regs); } UpdateStencilFaces(regs); @@ -1157,8 +1160,16 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg .minDepth = 0.0f, .maxDepth = 1.0f, }; - scheduler.Record([viewport](vk::CommandBuffer cmdbuf) { - cmdbuf.SetViewport(0, viewport); + GraphicsPipeline* pipeline = pipeline_cache.CurrentGraphicsPipeline(); + const bool use_viewport_with_count = device.IsExtExtendedDynamicStateSupported() && + pipeline && pipeline->UsesExtendedDynamicState(); + scheduler.Record([viewport, use_viewport_with_count](vk::CommandBuffer cmdbuf) { + if (use_viewport_with_count) { + std::array viewports{viewport}; + cmdbuf.SetViewportWithCountEXT(viewports); + } else { + cmdbuf.SetViewport(0, viewport); + } }); return; } @@ -1174,10 +1185,17 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg GetViewportState(device, regs, 12, scale), GetViewportState(device, regs, 13, scale), GetViewportState(device, regs, 14, scale), GetViewportState(device, regs, 15, scale), }; - scheduler.Record([this, viewport_list](vk::CommandBuffer cmdbuf) { + GraphicsPipeline* pipeline = pipeline_cache.CurrentGraphicsPipeline(); + const bool use_viewport_with_count = device.IsExtExtendedDynamicStateSupported() && + pipeline && pipeline->UsesExtendedDynamicState(); + scheduler.Record([this, viewport_list, use_viewport_with_count](vk::CommandBuffer cmdbuf) { const u32 num_viewports = std::min(device.GetMaxViewports(), Maxwell::NumViewports); const vk::Span viewports(viewport_list.data(), num_viewports); - cmdbuf.SetViewport(0, viewports); + if (use_viewport_with_count) { + cmdbuf.SetViewportWithCountEXT(viewports); + } else { + cmdbuf.SetViewport(0, viewports); + } }); } @@ -1198,8 +1216,16 @@ void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs scissor.offset.y = static_cast(y); scissor.extent.width = width; scissor.extent.height = height; - scheduler.Record([scissor](vk::CommandBuffer cmdbuf) { - cmdbuf.SetScissor(0, scissor); + GraphicsPipeline* pipeline = pipeline_cache.CurrentGraphicsPipeline(); + const bool use_scissor_with_count = device.IsExtExtendedDynamicStateSupported() && + pipeline && pipeline->UsesExtendedDynamicState(); + scheduler.Record([scissor, use_scissor_with_count](vk::CommandBuffer cmdbuf) { + if (use_scissor_with_count) { + std::array scissors{scissor}; + cmdbuf.SetScissorWithCountEXT(scissors); + } else { + cmdbuf.SetScissor(0, scissor); + } }); return; } @@ -1227,10 +1253,17 @@ void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs GetScissorState(regs, 14, up_scale, down_shift), GetScissorState(regs, 15, up_scale, down_shift), }; - scheduler.Record([this, scissor_list](vk::CommandBuffer cmdbuf) { + GraphicsPipeline* pipeline = pipeline_cache.CurrentGraphicsPipeline(); + const bool use_scissor_with_count = device.IsExtExtendedDynamicStateSupported() && + pipeline && pipeline->UsesExtendedDynamicState(); + scheduler.Record([this, scissor_list, use_scissor_with_count](vk::CommandBuffer cmdbuf) { const u32 num_scissors = std::min(device.GetMaxViewports(), Maxwell::NumViewports); const vk::Span scissors(scissor_list.data(), num_scissors); - cmdbuf.SetScissor(0, scissors); + if (use_scissor_with_count) { + cmdbuf.SetScissorWithCountEXT(scissors); + } else { + cmdbuf.SetScissor(0, scissors); + } }); } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index dba18a91cb..d3ed9c50a6 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -154,9 +154,11 @@ void Scheduler::RequestOutsideRenderPassOperationContext() { bool Scheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) { if (state.graphics_pipeline == pipeline) { - if (pipeline && pipeline->UsesExtendedDynamicState() && + if (pipeline && + (pipeline->UsesExtendedDynamicState() || pipeline->UsesExtendedDynamicState2() || + pipeline->UsesExtendedDynamicState2LogicOp()) && state.needs_state_enable_refresh) { - state_tracker.InvalidateStateEnableFlag(); + state_tracker.InvalidateExtendedDynamicStates(); state.needs_state_enable_refresh = false; } return false; @@ -173,10 +175,11 @@ bool Scheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) { state_tracker.InvalidateExtendedDynamicStates(); } - if (!pipeline->UsesExtendedDynamicState()) { + if (!pipeline->UsesExtendedDynamicState() && !pipeline->UsesExtendedDynamicState2() && + !pipeline->UsesExtendedDynamicState2LogicOp()) { state.needs_state_enable_refresh = true; } else if (state.needs_state_enable_refresh) { - state_tracker.InvalidateStateEnableFlag(); + state_tracker.InvalidateExtendedDynamicStates(); state.needs_state_enable_refresh = false; } diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h index 47948ddc64..610dee618e 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.h +++ b/src/video_core/renderer_vulkan/vk_state_tracker.h @@ -98,9 +98,13 @@ public: (*flags)[Dirty::Viewports] = true; (*flags)[Dirty::Scissors] = true; (*flags)[Dirty::CullMode] = true; + (*flags)[Dirty::DepthBoundsEnable] = true; + (*flags)[Dirty::DepthTestEnable] = true; + (*flags)[Dirty::DepthWriteEnable] = true; (*flags)[Dirty::DepthCompareOp] = true; (*flags)[Dirty::FrontFace] = true; (*flags)[Dirty::StencilOp] = true; + (*flags)[Dirty::StencilTestEnable] = true; (*flags)[Dirty::StateEnable] = true; (*flags)[Dirty::PrimitiveRestartEnable] = true; (*flags)[Dirty::RasterizerDiscardEnable] = true; From 631ad933b2e91faaaf73f16728f4e4091574c56a Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 22:25:56 -0400 Subject: [PATCH 11/12] fix build --- src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 7fb7ab9ba7..a9854d430a 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -531,7 +531,6 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, } const void* const descriptor_data{guest_descriptor_queue.UpdateData()}; - const FixedPipelineState::DynamicState dynamic_state{key.state.dynamic_state}; scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(), is_rescaling, update_rescaling, uses_render_area = render_area.uses_render_area, From 64163f2d2a34e38d64395f5f2d074821be062c10 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 22:42:22 -0400 Subject: [PATCH 12/12] [vulkan] Adjusting few remanants of topologies + review of possible missing dynamic state handling --- .../renderer_vulkan/vk_graphics_pipeline.cpp | 67 ++++++++++++++----- .../renderer_vulkan/vk_rasterizer.cpp | 5 -- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index a9854d430a..f20127ce81 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -101,6 +101,37 @@ bool IsLine(VkPrimitiveTopology topology) { return std::ranges::find(line_topologies, topology) != line_topologies.end(); } +VkPrimitiveTopology DynamicTopologyClassRepresentative(VkPrimitiveTopology topology) { + switch (topology) { + case VK_PRIMITIVE_TOPOLOGY_POINT_LIST: + return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + case VK_PRIMITIVE_TOPOLOGY_LINE_LIST: + case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP: + return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST: + case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP: + case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + case VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY: + case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY: + return VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY; + case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY: + case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY; + case VK_PRIMITIVE_TOPOLOGY_PATCH_LIST: + return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; + default: + return topology; + } +} + +bool SupportsStaticPrimitiveRestart(const Device& device, VkPrimitiveTopology topology) { + if (topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) { + return device.IsPatchListPrimitiveRestartSupported(); + } + return SupportsPrimitiveRestart(topology) || device.IsTopologyListPrimitiveRestartSupported(); +} + VkViewportSwizzleNV UnpackViewportSwizzle(u16 swizzle) { union Swizzle { u32 raw; @@ -624,11 +655,13 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { vertex_input_ci.pNext = &input_divisor_ci; } const bool has_tess_stages = spv_modules[1] || spv_modules[2]; - auto input_assembly_topology = MaxwellToVK::PrimitiveTopology(device, key.state.topology); - if (input_assembly_topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) { + const bool dynamic_topology = key.state.extended_dynamic_state != 0; + const bool dynamic_primitive_restart = key.state.extended_dynamic_state_2 != 0; + auto exact_input_assembly_topology = MaxwellToVK::PrimitiveTopology(device, key.state.topology); + if (exact_input_assembly_topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) { if (!has_tess_stages) { LOG_WARNING(Render_Vulkan, "Patch topology used without tessellation, using points"); - input_assembly_topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + exact_input_assembly_topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; } } else { if (has_tess_stages) { @@ -636,25 +669,29 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { // shader stages. Forcing it fixes a crash on some drivers LOG_WARNING(Render_Vulkan, "Patch topology not used with tessellation, using patch list"); - input_assembly_topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; + exact_input_assembly_topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; } } + const VkPrimitiveTopology input_assembly_topology = + dynamic_topology && dynamic_primitive_restart + ? DynamicTopologyClassRepresentative(exact_input_assembly_topology) + : exact_input_assembly_topology; + const VkBool32 primitive_restart_enable = + // MoltenVK/Metal always has primitive restart enabled and cannot disable it + device.IsMoltenVK() + ? VK_TRUE + : (dynamic_primitive_restart + ? VK_FALSE + : (dynamic.primitive_restart_enable != 0 && + SupportsStaticPrimitiveRestart(device, input_assembly_topology) + ? VK_TRUE + : VK_FALSE)); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .pNext = nullptr, .flags = 0, .topology = input_assembly_topology, - .primitiveRestartEnable = - // MoltenVK/Metal always has primitive restart enabled and cannot disable it - device.IsMoltenVK() ? VK_TRUE : - (dynamic.primitive_restart_enable != 0 && - ((input_assembly_topology != VK_PRIMITIVE_TOPOLOGY_PATCH_LIST && - device.IsTopologyListPrimitiveRestartSupported()) || - SupportsPrimitiveRestart(input_assembly_topology) || - (input_assembly_topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST && - device.IsPatchListPrimitiveRestartSupported())) - ? VK_TRUE - : VK_FALSE), + .primitiveRestartEnable = primitive_restart_enable, }; const VkPipelineTessellationStateCreateInfo tessellation_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO, diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 0002fceec6..73d2d07c08 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -277,11 +277,6 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { if (!pipeline->Configure(is_indexed)) return; - if (pipeline->UsesExtendedDynamicState() || pipeline->UsesExtendedDynamicState2() || - pipeline->UsesExtendedDynamicState2LogicOp()) { - state_tracker.InvalidateExtendedDynamicStates(); - } - HandleTransformFeedback(); query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, maxwell3d->regs.zpass_pixel_count_enable);