From 8e87cf5a66bb8c2d5b48f6ca4e73e030d9101dfc Mon Sep 17 00:00:00 2001 From: SethRamey <141290181+SethRamey@users.noreply.github.com> Date: Fri, 6 Nov 2026 06:56:01 -0500 Subject: [PATCH 1/3] vulkan: intel arc fixes for MKW Before these changes, intel dGPU device loss would occur soon after boot. These changes also fix several intel dGPU crashes and banding. Task shaders: payload word 1 was never set which caused Intel device loss. Now zeroed at start, followed by a barrier. Banding: shaders that previously lost a conditional barrier raced on Intel dGPU, those shaders are now forced to subgroup size 32. Pipeline cache: Intel driver crashed when cache was read during pipeline builds. Flush is now skipped for the driver. Closing the game: state tracker kept stale pointer to channel state which crashed on shutdown. ClearChannel() resets it. Size 32 is a workaround, not a real barrier Tested A770, driver 32.0.101.9150, Windows, MKW Assisted-by: Claude Opus 5.5 (debugging, code, testing) --- src/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp | 7 +++++++ src/shader_recompiler/ir_opt/conditional_barrier_pass.cpp | 1 + src/shader_recompiler/shader_info.h | 1 + src/video_core/renderer_vulkan/vk_compute_pipeline.cpp | 6 +++++- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 3 ++- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 6 ++++++ src/video_core/renderer_vulkan/vk_rasterizer.h | 1 + src/video_core/renderer_vulkan/vk_state_tracker.h | 5 +++++ src/video_core/vulkan_common/vulkan_device.h | 6 ++++++ 9 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp index 6c599528cc..e0f3f437f6 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp @@ -207,6 +207,13 @@ void EmitIsbeWriteMapU32(EmitContext& ctx, const IR::Value& address, Id value) { } void EmitMeshPrologue(EmitContext& ctx) { + if (ctx.stage == Stage::Task) { + // A task that culls all its threads never writes the mesh workgroup count, and the payload + // starts undefined. Intel's garbage there launches enough workgroups to lose the device. + ctx.OpStore(PayloadWord(ctx, ctx.Const(1U)), ctx.u32_zero_value); + Barrier(ctx); + return; + } if (ctx.stage != Stage::Mesh) { return; } diff --git a/src/shader_recompiler/ir_opt/conditional_barrier_pass.cpp b/src/shader_recompiler/ir_opt/conditional_barrier_pass.cpp index c3ed27f4f2..8a792b2ff2 100644 --- a/src/shader_recompiler/ir_opt/conditional_barrier_pass.cpp +++ b/src/shader_recompiler/ir_opt/conditional_barrier_pass.cpp @@ -31,6 +31,7 @@ void ConditionalBarrierPass(IR::Program& program) { inst.GetOpcode() == IR::Opcode::Barrier) { LOG_WARNING(Shader, "Barrier within conditional control flow"); inst.ReplaceOpcode(IR::Opcode::Identity); + program.info.removed_conditional_barrier = true; } } break; diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index 4e5fb5c6e4..d2e2c807e0 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -320,6 +320,7 @@ struct Info { bool uses_sparse_residency{}; bool uses_demote_to_helper_invocation{}; bool uses_subgroup_vote{}; + bool removed_conditional_barrier{}; bool uses_subgroup_mask{}; bool uses_fswzadd{}; bool uses_derivatives{}; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index ae008b48a7..5fa13eb0fa 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -88,7 +88,11 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk }; // Only warp-level ops observe the subgroup size. Forcing 32 on other shaders makes Intel // compile SIMD32, which can spill a register-heavy kernel for minutes (Mario Kart World). - const bool uses_warp_ops = info.uses_subgroup_vote || info.uses_subgroup_invocation_id || + // A shader that lost a conditional barrier relies on a whole warp running in step: at a + // smaller subgroup size it reads stale shared memory, which shows as banding over the image + // in Mario Kart World on Intel's Windows driver. + const bool uses_warp_ops = info.removed_conditional_barrier || + info.uses_subgroup_vote || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || info.uses_subgroup_mask || info.uses_quad_shuffles || info.uses_fswzadd; VkPipelineCreateFlags flags{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 18aa74b29e..adb9f04786 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -912,7 +912,8 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading } void PipelineCache::QueueVulkanPipelineCacheFlush() { - if (!use_vulkan_pipeline_cache || vulkan_pipeline_cache_filename.empty()) { + if (!use_vulkan_pipeline_cache || vulkan_pipeline_cache_filename.empty() || + !device.CanReadPipelineCacheDuringBuilds()) { return; } if (++pipelines_since_flush < VULKAN_CACHE_FLUSH_PIPELINES) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index bb8c7f57a3..f51182f044 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -246,6 +246,8 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra RasterizerVulkan::~RasterizerVulkan() { VideoCommon::GpuSplit::Get().SetIdleHook(nullptr, nullptr); + // The channels are gone by now, and submitting invalidates the state tracker's flags. + state_tracker.ClearChannel(); scheduler.WaitWorker(); scheduler.Finish(); } @@ -2553,9 +2555,13 @@ void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) { query_cache.BindToChannel(channel_id); state_tracker.ChangeChannel(channel); state_tracker.InvalidateState(); + bound_channel_id = channel_id; } void RasterizerVulkan::ReleaseChannel(s32 channel_id) { + if (channel_id == bound_channel_id) { + state_tracker.ClearChannel(); + } EraseChannel(channel_id); { std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 1e550a27ca..65de3105d2 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -224,6 +224,7 @@ class RasterizerVulkan final : public VideoCore::RasterizerInterface, const Device& device; MemoryAllocator& memory_allocator; StateTracker& state_tracker; + s32 bound_channel_id = -1; Scheduler& scheduler; StagingBufferPool staging_pool; diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h index 6b47ba4176..d47fa9e0e2 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.h +++ b/src/video_core/renderer_vulkan/vk_state_tracker.h @@ -290,6 +290,11 @@ class StateTracker { void ChangeChannel(Tegra::Control::ChannelState& channel_state); + /// Stops using the bound channel's flags, for when it goes away. + void ClearChannel() { + flags = &default_flags; + } + void InvalidateState(); private: diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index f3f9209c99..c69edb6d2d 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -369,6 +369,12 @@ class Device { id != VK_DRIVER_ID_MESA_RADV; } + /// Whether a pipeline cache can be read while pipelines build into it, as Vulkan allows. Intel's + /// Windows driver crashes in vkGetPipelineCacheData when a build is running. + bool CanReadPipelineCacheDuringBuilds() const { + return GetDriverID() != VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS; + } + /// Returns true for tile-based deferred renderers. bool IsTiler() const { switch (GetDriverID()) { From f9de7d8551bcb3e6ce8a3c80a4caebec4d74da91 Mon Sep 17 00:00:00 2001 From: SethRamey <141290181+SethRamey@users.noreply.github.com> Date: Fri, 6 Nov 2026 18:44:01 -0500 Subject: [PATCH 2/3] vulkan: wait for shader builds before the cache save on close Pipeline Cache now waits for shader build workers before saving cache on drivers where CanReadPipelineCacheDuringBuilds() is false. Tested on A770, MKW. Closing during shader builds closed cleanly. Assisted-by: Claude Opus 5.5 (debugging, code, testing) --- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index adb9f04786..7be731e3bc 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -661,6 +661,9 @@ bool PreloadAllPipelines() { PipelineCache::~PipelineCache() { SaveUsedPipelines(); if (use_vulkan_pipeline_cache && !vulkan_pipeline_cache_filename.empty()) { + if (!device.CanReadPipelineCacheDuringBuilds()) { + workers.WaitForRequests(); + } SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache, CACHE_VERSION); } From 37a3ad3ad95de36f372fbe14732cadd6a3987225 Mon Sep 17 00:00:00 2001 From: SethRamey <141290181+SethRamey@users.noreply.github.com> Date: Fri, 6 Nov 2026 19:24:32 -0500 Subject: [PATCH 3/3] vulkan: drop queued shader builds on close New ClearRequests() in ThreadWorker, close waits only for running builds Tested A770, MKW, closing during shader builds Assisted-by: Claude Opus 5.5 (debugging, code, testing) --- src/common/thread_worker.h | 7 +++++++ src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/common/thread_worker.h b/src/common/thread_worker.h index ec3ca0af49..a88713665d 100644 --- a/src/common/thread_worker.h +++ b/src/common/thread_worker.h @@ -117,6 +117,13 @@ class StatefulThreadWorker { }); } + // Drops tasks no worker has started. Running tasks still finish. + void ClearRequests() { + std::scoped_lock lock{queue_mutex}; + work_scheduled -= requests.size(); + requests = std::queue{}; + } + private: std::queue requests; std::mutex queue_mutex; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 7be731e3bc..e926a0a1f8 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -662,6 +662,8 @@ PipelineCache::~PipelineCache() { SaveUsedPipelines(); if (use_vulkan_pipeline_cache && !vulkan_pipeline_cache_filename.empty()) { if (!device.CanReadPipelineCacheDuringBuilds()) { + // Drop queued builds, as destroying the workers would; running ones must end first. + workers.ClearRequests(); workers.WaitForRequests(); } SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache,