Skip to content
19 changes: 19 additions & 0 deletions relower_words.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Reads "w0 w1 w2 w3" hex lines on stdin; lowers each instruction followed by EXIT.
// Build: c++ -std=c++20 -O1 -w -I <repo>/src relower_words.cpp -lfmt -o relower
#include <cstdio>
#include <vector>
#include "shader_recompiler/frontend/ampere/lower_to_maxwell.h"

using namespace Shader::Ampere;

int main(int argc, char**) {
const bool tensor = argc > 1; // any argument: emulate HMMA as EDEN_NX2_TENSOR would
unsigned w0, w1, w2, w3;
while (std::scanf("%x %x %x %x", &w0, &w1, &w2, &w3) == 4) {
const std::vector<Instruction> code{Instruction{{w0, w1, w2, w3}},
Instruction{{0x0000794d, 0, 0x03800000, 0}}};
const auto lowered = LowerToMaxwell(code, LoweringOptions{.tensor = tensor});
std::printf("%08x %08x %08x %08x: %s%.*s\n", w0, w1, w2, w3, lowered ? "OK" : "FAIL ",
static_cast<int>(lowered.error.size()), lowered.error.data());
}
}
7 changes: 7 additions & 0 deletions src/common/thread_worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Task>{};
}

private:
std::queue<Task> requests;
std::mutex queue_mutex;
Expand Down
7 changes: 7 additions & 0 deletions src/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions src/shader_recompiler/ir_opt/conditional_barrier_pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/shader_recompiler/shader_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,8 @@ struct Info {
bool uses_sparse_residency{};
bool uses_demote_to_helper_invocation{};
bool uses_subgroup_vote{};
/// Set when a barrier in conditional control flow was dropped, see ConditionalBarrierPass.
bool removed_conditional_barrier{};
bool uses_subgroup_mask{};
bool uses_fswzadd{};
bool uses_derivatives{};
Expand Down Expand Up @@ -374,6 +376,13 @@ struct Info {
/// and storage buffer words (see EvaluateHandleProgram), evaluated for each draw.
std::vector<std::vector<u32>> handle_programs;
ImageDescriptors image_descriptors;

/// True when the shader only runs correctly if a subgroup spans a whole guest warp.
[[nodiscard]] bool UsesWarpOps() const noexcept {
return removed_conditional_barrier || uses_subgroup_vote || uses_subgroup_invocation_id ||
uses_subgroup_shuffles || uses_subgroup_mask || uses_quad_shuffles ||
uses_fswzadd;
}
};

template <typename Descriptors>
Expand Down
7 changes: 4 additions & 3 deletions src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ 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 ||
info.uses_subgroup_shuffles || info.uses_subgroup_mask ||
info.uses_quad_shuffles || info.uses_fswzadd;
// A shader that lost a conditional barrier counts too: it relies on a whole warp running
// in step, and 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.UsesWarpOps();
VkPipelineCreateFlags flags{};
if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) {
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
Expand Down
11 changes: 9 additions & 2 deletions src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1138,7 +1138,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
.dynamicStateCount = static_cast<u32>(dynamic_states.size()),
.pDynamicStates = dynamic_states.data(),
};
[[maybe_unused]] const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
.pNext = nullptr,
.requiredSubgroupSize = GuestWarpSize,
Expand All @@ -1148,7 +1148,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
if (!spv_modules[stage]) {
continue;
}
[[maybe_unused]] auto& stage_ci =
auto& stage_ci =
shader_stages.emplace_back(VkPipelineShaderStageCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext = nullptr,
Expand All @@ -1159,6 +1159,13 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
.pName = "main",
.pSpecializationInfo = nullptr,
});
const Shader::Info& info{stage_infos[stage]};
if ((stage_ci.stage == VK_SHADER_STAGE_MESH_BIT_EXT ||
stage_ci.stage == VK_SHADER_STAGE_TASK_BIT_EXT) &&
info.UsesWarpOps() && device.IsExtSubgroupSizeControlSupported() &&
device.IsGuestWarpSizeSupported(stage_ci.stage)) {
stage_ci.pNext = &subgroup_size_ci;
}
}
VkPipelineCreateFlags flags{};
if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) {
Expand Down
8 changes: 7 additions & 1 deletion src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions src/video_core/renderer_vulkan/vk_rasterizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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};
Expand Down
1 change: 1 addition & 0 deletions src/video_core/renderer_vulkan/vk_rasterizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/video_core/renderer_vulkan/vk_state_tracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions src/video_core/vulkan_common/vulkan_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down