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/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..e926a0a1f8 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -661,6 +661,11 @@ bool PreloadAllPipelines() { 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, CACHE_VERSION); } @@ -912,7 +917,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()) {