Compare commits

..

3 Commits

Author SHA1 Message Date
Aydar Kamaltdinov d0a6e951ff shader_recompiler: gl_PrimitiveId scatter for geometry-stage stream compaction (#4330)
Guest shaders that reserve a compacted output slot via subgroup ballot
-> popcount -> atomic add cannot preserve the originating primitives'
relative order once run through the single-invocation subgroup
fallback for stages without subgroup support. gl_PrimitiveId is
already hardware-guaranteed unique and monotonically increasing per
primitive, so CompactionFallbackPass rewrites that pattern into a
gl_PrimitiveId-indexed scatter instead.

Confirmed fixing corrupted/order-scrambled compacted draws in NieR
Automata on a device without geometry-stage subgroup support.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4330
2026-08-31 11:25:25 +02:00
CamilleLaVey 1bd6656b3c Little adjustment 2026-08-30 15:01:41 -04:00
CamilleLaVey f6508c7c74 [shader, recompiler, spir-v] Reviewed community patch provided for warp intrinsects fallback within VOTE missing instructions for QCOM drivers 2026-08-30 12:19:30 -04:00
13 changed files with 116 additions and 139 deletions
+31 -57
View File
@@ -123,13 +123,11 @@ static IPSwitchRecord EscapeStringSequences(std::string_view sv) {
for (auto it = sv.cbegin(); it != sv.cend(); ) { for (auto it = sv.cbegin(); it != sv.cend(); ) {
if (*it == '\\' && it + 1 < sv.cend()) { if (*it == '\\' && it + 1 < sv.cend()) {
switch (it[1]) { switch (it[1]) {
case 'a': r.data[r.count] = '\a'; break;
case 'b': r.data[r.count] = '\b'; break;
case 'e': r.data[r.count] = '\e'; break;
case 'f': r.data[r.count] = '\f'; break;
case 'n': r.data[r.count] = '\n'; break; case 'n': r.data[r.count] = '\n'; break;
case 'r': r.data[r.count] = '\r'; break;
case 't': r.data[r.count] = '\t'; break; case 't': r.data[r.count] = '\t'; break;
case 'b': r.data[r.count] = '\b'; break;
case 'r': r.data[r.count] = '\r'; break;
case 'e': r.data[r.count] = '\e'; break;
case 'v': r.data[r.count] = '\v'; break; case 'v': r.data[r.count] = '\v'; break;
case '?': r.data[r.count] = '\?'; break; case '?': r.data[r.count] = '\?'; break;
default: r.data[r.count] = it[1]; break; default: r.data[r.count] = it[1]; break;
@@ -144,60 +142,40 @@ static IPSwitchRecord EscapeStringSequences(std::string_view sv) {
return r; return r;
} }
[[nodiscard]] static inline std::array<u8, 32> ReadNSOBuildId(std::string_view const s) {
std::array<u8, 32> r{};
for (std::size_t i = 0; i < s.size(); ++i)
r[i / 2] |= u8(u8(Common::ToHexNibble(s[i])) << u8((i % 2) * 4));
return r;
}
void IPSwitchCompiler::Parse(std::span<u8 const> bytes) { void IPSwitchCompiler::Parse(std::span<u8 const> bytes) {
LOG_INFO(Loader, "IPSwitchCompiler: '{}'", patch_text->GetName()); LOG_INFO(Loader, "IPSwitchCompiler: '{}'", patch_text->GetName());
bool is_little_endian = true; bool is_little_endian = false;
s64 offset_shift = 0; s64 offset_shift = 0;
//bool print_values = false; //bool print_values = false;
auto const parse_line = [&](std::string_view const line) { auto const parse_line = [&](std::string_view const line) {
// Keep in mind lines have trimmed spaces (at the end & start)! // Keep in mind lines have trimmed spaces (at the end & start)!
LOG_INFO(Loader, "<{}>", line); LOG_INFO(Loader, "<{}>", line);
// IPSwitch is case insensitive if (line.starts_with("@stop")) {
// Yes this is how the logic goes for the main reference parsers! return false; // Force stop
if (line.size() > 2 && line[0] == '@') { } else if (line.starts_with("@nsobid-")) { // NSO Build ID Specifier
switch (line[1]) { nso_build_id = ReadNSOBuildId(line.substr(8));
// yes, @nsobid too -- NSO Build ID Specifier } else if (line.starts_with("@enabled")) {
case 'n': patches.push_back({{}, true}); //enabled patch
case 'N': } else if (line.starts_with("@disabled")) {
nso_build_id = Common::HexStringToArray<0x20>(fmt::format("{:0<64}", line.substr(8))); patches.push_back({{}, false}); //disabled patch
break; } else if (line.starts_with("@flag offset_shift ")) {
// @stop offset_shift = std::strtoll(line.data() + 19, nullptr, 0); // Offset Shift Flag
case 's': } else if (line.starts_with("@little-endian")) {
case 'S': is_little_endian = true; // Set values to read as little endian
return false; } else if (line.starts_with("@big-endian")) {
// @enabled is_little_endian = false; // Set values to read as big endian
case 'e': } else if (line.starts_with("@flag print_values")) {
case 'E': //print_values = true; // Force printing of applied values
patches.push_back({{}, true}); } else if (line.starts_with("@")) {
break; LOG_WARNING(Loader, "Unknown flag {}", line);
// @disabled
case 'd':
case 'D':
patches.push_back({{}, false});
break;
// @flag
case 'f':
case 'F': {
if (line.starts_with("@flag offset_shift")) {
offset_shift = std::strtoll(line.data() + 19, nullptr, 0); // Offset Shift Flag
} else if (line.starts_with("@flag print_values")) {
//print_values = true; // Force printing of applied values
}
break;
}
case 'l':
case 'L':
is_little_endian = true;
break;
// IPS parsers dont support big endian no more, we do due to backcompat
case 'b':
case 'B':
is_little_endian = false;
break;
default:
LOG_WARNING(Loader, "Unknown flag {}", line);
break;
}
} else { } else {
size_t offset = size_t(std::strtoul(line.data(), nullptr, 16)); size_t offset = size_t(std::strtoul(line.data(), nullptr, 16));
offset += size_t(offset_shift); offset += size_t(offset_shift);
@@ -219,7 +197,6 @@ void IPSwitchCompiler::Parse(std::span<u8 const> bytes) {
auto const start = line.cbegin() + first_space + 1; auto const start = line.cbegin() + first_space + 1;
auto const end = line.cend(); auto const end = line.cend();
if (start <= line.cend() && end <= line.cend()) { if (start <= line.cend() && end <= line.cend()) {
// Actually IPS wants ordering from {lsb, ..., msb} -- so LE and BE are inverted, fun!
auto const hs = Common::HexStringToVector({start, end}, is_little_endian); auto const hs = Common::HexStringToVector({start, end}, is_little_endian);
std::memcpy(r.data.data(), hs.data(), hs.size()); std::memcpy(r.data.data(), hs.data(), hs.size());
r.count = hs.size(); r.count = hs.size();
@@ -254,8 +231,7 @@ void IPSwitchCompiler::Parse(std::span<u8 const> bytes) {
char quote = '\0'; char quote = '\0';
auto const sline_start = p; auto const sline_start = p;
for (; p < sline.cend(); ) { for (; p < sline.cend(); ) {
// we dont check for "//", IPS checks for '/' only... if ((!quote && p + 1 < sline.cend() && p[0] == '/' && p[1] == '/')
if ((!quote && p[0] == '/')
|| (!quote && p[0] == '#')) { || (!quote && p[0] == '#')) {
break; break;
} else if (p[0] == '\"' || p[0] == '\'') { } else if (p[0] == '\"' || p[0] == '\'') {
@@ -290,8 +266,6 @@ VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
if (record.first + replace_size > in_data.size()) if (record.first + replace_size > in_data.size())
replace_size = in_data.size() - record.first; replace_size = in_data.size() - record.first;
std::memcpy(in_data.data() + record.first, record.second.data.data(), replace_size); std::memcpy(in_data.data() + record.first, record.second.data.data(), replace_size);
} else {
LOG_WARNING(Loader, "record offs={:x},size={:x}", record.first, record.second.data.size());
} }
} }
} }
+1
View File
@@ -217,6 +217,7 @@ add_library(shader_recompiler STATIC
frontend/maxwell/translate_program.h frontend/maxwell/translate_program.h
host_translate_info.h host_translate_info.h
ir_opt/collect_shader_info_pass.cpp ir_opt/collect_shader_info_pass.cpp
ir_opt/compaction_fallback_pass.cpp
ir_opt/conditional_barrier_pass.cpp ir_opt/conditional_barrier_pass.cpp
ir_opt/constant_propagation_pass.cpp ir_opt/constant_propagation_pass.cpp
ir_opt/dead_code_elimination_pass.cpp ir_opt/dead_code_elimination_pass.cpp
@@ -440,8 +440,7 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
} }
if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id || if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id ||
info.uses_subgroup_shuffles) && info.uses_subgroup_shuffles) &&
profile.support_vote && profile.support_vote && profile.SupportsSubgroupStage(ctx.stage)) {
(ctx.stage != Stage::Geometry || profile.support_subgroup_in_geometry_stage)) {
ctx.AddCapability(spv::Capability::GroupNonUniformBallot); ctx.AddCapability(spv::Capability::GroupNonUniformBallot);
ctx.AddCapability(spv::Capability::GroupNonUniformShuffle); ctx.AddCapability(spv::Capability::GroupNonUniformShuffle);
if (!profile.warp_size_potentially_larger_than_guest) { if (!profile.warp_size_potentially_larger_than_guest) {
@@ -13,18 +13,6 @@ Id SubgroupScope(EmitContext& ctx) {
return ctx.Const(static_cast<u32>(spv::Scope::Subgroup)); return ctx.Const(static_cast<u32>(spv::Scope::Subgroup));
} }
// Some mobile GPUs (e.g. Adreno/Turnip) only advertise subgroup ballot/shuffle support for the
// fragment and compute stages (VkPhysicalDeviceSubgroupProperties::supportedStages), even though
// they support these operations elsewhere. Guest shaders that use VOTE/SHFL in a geometry program
// would otherwise emit GroupNonUniform* SPIR-V the driver never declared support for in that
// stage. There is no barrier in the geometry stage, so a real cross-invocation emulation can't be
// made correct; instead, treat the current invocation as if it were alone in its subgroup. This is
// semantically wrong for guest code that relies on genuine cross-lane communication, but it is
// well-defined, valid SPIR-V that doesn't depend on unsupported hardware capabilities.
bool NeedsGeometrySubgroupFallback(EmitContext& ctx) {
return ctx.stage == Stage::Geometry && !ctx.profile.support_subgroup_in_geometry_stage;
}
bool StageSupportsSubgroups(EmitContext& ctx) { bool StageSupportsSubgroups(EmitContext& ctx) {
return ctx.profile.SupportsSubgroupStage(ctx.stage); return ctx.profile.SupportsSubgroupStage(ctx.stage);
} }
@@ -106,9 +94,6 @@ Id AddPartitionBase(EmitContext& ctx, Id thread_id) {
} // Anonymous namespace } // Anonymous namespace
Id EmitLaneId(EmitContext& ctx) { Id EmitLaneId(EmitContext& ctx) {
if (NeedsGeometrySubgroupFallback(ctx)) {
return ctx.u32_zero_value;
}
const Id id{GetThreadId(ctx)}; const Id id{GetThreadId(ctx)};
if (!ctx.profile.warp_size_potentially_larger_than_guest) { if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return id; return id;
@@ -117,9 +102,6 @@ Id EmitLaneId(EmitContext& ctx) {
} }
Id EmitVoteAll(EmitContext& ctx, Id pred) { Id EmitVoteAll(EmitContext& ctx, Id pred) {
if (NeedsGeometrySubgroupFallback(ctx)) {
return pred;
}
if (!StageSupportsSubgroups(ctx)) { if (!StageSupportsSubgroups(ctx)) {
return pred; return pred;
} }
@@ -136,9 +118,6 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) {
} }
Id EmitVoteAny(EmitContext& ctx, Id pred) { Id EmitVoteAny(EmitContext& ctx, Id pred) {
if (NeedsGeometrySubgroupFallback(ctx)) {
return pred;
}
if (!StageSupportsSubgroups(ctx)) { if (!StageSupportsSubgroups(ctx)) {
return pred; return pred;
} }
@@ -155,9 +134,6 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) {
} }
Id EmitVoteEqual(EmitContext& ctx, Id pred) { Id EmitVoteEqual(EmitContext& ctx, Id pred) {
if (NeedsGeometrySubgroupFallback(ctx)) {
return ctx.true_value;
}
if (!StageSupportsSubgroups(ctx)) { if (!StageSupportsSubgroups(ctx)) {
return ctx.true_value; return ctx.true_value;
} }
@@ -175,16 +151,6 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) {
} }
Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
if (NeedsGeometrySubgroupFallback(ctx)) {
// Reflect only this invocation's own predicate. There is no way to observe other
// invocations' predicates without real subgroup hardware support in this stage, so this
// is a best-effort approximation: it keeps any branch gated on "did anyone match" live
// (rather than letting the SPIR-V optimizer prove it dead, which previously caused
// indirect draws fed by this shader to see indexCount=instanceCount=0), but any downstream
// math that assumes a real cross-lane population count (e.g. popcount-based compaction
// offsets) will not be correct.
return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1U), ctx.u32_zero_value);
}
if (!StageSupportsSubgroups(ctx)) { if (!StageSupportsSubgroups(ctx)) {
return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1u), ctx.u32_zero_value); return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1u), ctx.u32_zero_value);
} }
@@ -196,9 +162,6 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
} }
Id EmitSubgroupEqMask(EmitContext& ctx) { Id EmitSubgroupEqMask(EmitContext& ctx) {
if (NeedsGeometrySubgroupFallback(ctx)) {
return ctx.Const(1U);
}
if (!StageSupportsSubgroups(ctx)) { if (!StageSupportsSubgroups(ctx)) {
return ctx.Const(1u); return ctx.Const(1u);
} }
@@ -206,9 +169,6 @@ Id EmitSubgroupEqMask(EmitContext& ctx) {
} }
Id EmitSubgroupLtMask(EmitContext& ctx) { Id EmitSubgroupLtMask(EmitContext& ctx) {
if (NeedsGeometrySubgroupFallback(ctx)) {
return ctx.u32_zero_value;
}
if (!StageSupportsSubgroups(ctx)) { if (!StageSupportsSubgroups(ctx)) {
return ctx.u32_zero_value; return ctx.u32_zero_value;
} }
@@ -216,9 +176,6 @@ Id EmitSubgroupLtMask(EmitContext& ctx) {
} }
Id EmitSubgroupLeMask(EmitContext& ctx) { Id EmitSubgroupLeMask(EmitContext& ctx) {
if (NeedsGeometrySubgroupFallback(ctx)) {
return ctx.Const(1U);
}
if (!StageSupportsSubgroups(ctx)) { if (!StageSupportsSubgroups(ctx)) {
return ctx.Const(1u); return ctx.Const(1u);
} }
@@ -226,9 +183,6 @@ Id EmitSubgroupLeMask(EmitContext& ctx) {
} }
Id EmitSubgroupGtMask(EmitContext& ctx) { Id EmitSubgroupGtMask(EmitContext& ctx) {
if (NeedsGeometrySubgroupFallback(ctx)) {
return ctx.u32_zero_value;
}
if (!StageSupportsSubgroups(ctx)) { if (!StageSupportsSubgroups(ctx)) {
return ctx.u32_zero_value; return ctx.u32_zero_value;
} }
@@ -236,9 +190,6 @@ Id EmitSubgroupGtMask(EmitContext& ctx) {
} }
Id EmitSubgroupGeMask(EmitContext& ctx) { Id EmitSubgroupGeMask(EmitContext& ctx) {
if (NeedsGeometrySubgroupFallback(ctx)) {
return ctx.Const(1U);
}
if (!StageSupportsSubgroups(ctx)) { if (!StageSupportsSubgroups(ctx)) {
return ctx.Const(1u); return ctx.Const(1u);
} }
@@ -247,10 +198,6 @@ Id EmitSubgroupGeMask(EmitContext& ctx) {
Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) { Id segmentation_mask) {
if (NeedsGeometrySubgroupFallback(ctx)) {
SetInBoundsFlag(inst, ctx.false_value);
return value;
}
const Id not_seg_mask{ctx.OpNot(ctx.U32[1], segmentation_mask)}; const Id not_seg_mask{ctx.OpNot(ctx.U32[1], segmentation_mask)};
const Id thread_id{EmitLaneId(ctx)}; const Id thread_id{EmitLaneId(ctx)};
const Id min_thread_id{ComputeMinThreadId(ctx, thread_id, segmentation_mask)}; const Id min_thread_id{ComputeMinThreadId(ctx, thread_id, segmentation_mask)};
@@ -270,7 +217,7 @@ Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id cla
Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) { Id segmentation_mask) {
if (NeedsGeometrySubgroupFallback(ctx)) { if (!StageSupportsSubgroups(ctx)) {
SetInBoundsFlag(inst, ctx.false_value); SetInBoundsFlag(inst, ctx.false_value);
return value; return value;
} }
@@ -289,7 +236,7 @@ Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) { Id segmentation_mask) {
if (NeedsGeometrySubgroupFallback(ctx)) { if (!StageSupportsSubgroups(ctx)) {
SetInBoundsFlag(inst, ctx.false_value); SetInBoundsFlag(inst, ctx.false_value);
return value; return value;
} }
@@ -308,7 +255,7 @@ Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clam
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) { Id segmentation_mask) {
if (NeedsGeometrySubgroupFallback(ctx)) { if (!StageSupportsSubgroups(ctx)) {
SetInBoundsFlag(inst, ctx.false_value); SetInBoundsFlag(inst, ctx.false_value);
return value; return value;
} }
@@ -1455,14 +1455,13 @@ void EmitContext::DefineInputs(const IR::Program& program) {
if (info.uses_is_helper_invocation) { if (info.uses_is_helper_invocation) {
is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation); is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation);
} }
if (info.uses_subgroup_mask && if (info.uses_subgroup_mask && profile.SupportsSubgroupStage(stage)) {
(stage != Stage::Geometry || profile.support_subgroup_in_geometry_stage)) {
subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR); subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR);
subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR); subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR);
subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR); subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR);
subgroup_mask_gt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGtMaskKHR); subgroup_mask_gt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGtMaskKHR);
subgroup_mask_ge = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGeMaskKHR); subgroup_mask_ge = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGeMaskKHR);
if (profile.support_explicit_workgroup_layout) { if (stage == Stage::Fragment) {
Decorate(subgroup_mask_eq, spv::Decoration::Flat); Decorate(subgroup_mask_eq, spv::Decoration::Flat);
Decorate(subgroup_mask_lt, spv::Decoration::Flat); Decorate(subgroup_mask_lt, spv::Decoration::Flat);
Decorate(subgroup_mask_le, spv::Decoration::Flat); Decorate(subgroup_mask_le, spv::Decoration::Flat);
@@ -1470,11 +1469,10 @@ void EmitContext::DefineInputs(const IR::Program& program) {
Decorate(subgroup_mask_ge, spv::Decoration::Flat); Decorate(subgroup_mask_ge, spv::Decoration::Flat);
} }
} }
if (info.uses_fswzadd || if ((info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
((info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || (profile.warp_size_potentially_larger_than_guest &&
(profile.warp_size_potentially_larger_than_guest && (info.uses_subgroup_vote || info.uses_subgroup_mask))) &&
(info.uses_subgroup_vote || info.uses_subgroup_mask))) && profile.SupportsSubgroupStage(stage)) {
(stage != Stage::Geometry || profile.support_subgroup_in_geometry_stage))) {
AddCapability(spv::Capability::GroupNonUniform); AddCapability(spv::Capability::GroupNonUniform);
subgroup_local_invocation_id = subgroup_local_invocation_id =
DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId); DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId);
@@ -299,6 +299,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
Optimization::PositionPass(env, program); Optimization::PositionPass(env, program);
Optimization::GlobalMemoryToStorageBufferPass(program, normalized_host_info); Optimization::GlobalMemoryToStorageBufferPass(program, normalized_host_info);
Optimization::CompactionFallbackPass(program, normalized_host_info);
Optimization::TexturePass(env, program, normalized_host_info); Optimization::TexturePass(env, program, normalized_host_info);
if (Settings::values.resolution_info.active || Settings::values.rescale_hack.GetValue()) { if (Settings::values.resolution_info.active || Settings::values.rescale_hack.GetValue()) {
@@ -38,6 +38,7 @@ struct HostTranslateInfo {
///< passthrough shaders ///< passthrough shaders
bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
///< control flow ///< control flow
bool support_subgroup_in_geometry_stage{};
void ApplyDescriptorLimitPolicy() noexcept { void ApplyDescriptorLimitPolicy() noexcept {
if (min_ssbo_alignment == 0) { if (min_ssbo_alignment == 0) {
@@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "shader_recompiler/frontend/ir/ir_emitter.h"
#include "shader_recompiler/host_translate_info.h"
#include "shader_recompiler/ir_opt/passes.h"
namespace Shader::Optimization {
namespace {
constexpr int MAX_BALLOT_SEARCH_DEPTH = 8;
bool DependsOnSubgroupBallot(const IR::Value& value, int depth) {
if (depth > MAX_BALLOT_SEARCH_DEPTH || value.IsImmediate()) {
return false;
}
IR::Inst* const producer{value.Inst()};
if (producer->GetOpcode() == IR::Opcode::SubgroupBallot) {
return true;
}
if (producer->GetOpcode() == IR::Opcode::Phi) {
return false;
}
const size_t num_args{producer->NumArgs()};
for (size_t index = 0; index < num_args; ++index) {
if (DependsOnSubgroupBallot(producer->Arg(index), depth + 1)) {
return true;
}
}
return false;
}
void RewriteAtomic(IR::Block& block, IR::Inst& inst) {
const auto insert_point{IR::Block::InstructionList::s_iterator_to(inst)};
IR::IREmitter ir{block, insert_point};
const IR::U32 amount{inst.Arg(2)};
const IR::U32 primitive_id{ir.GetAttributeU32(IR::Attribute::PrimitiveId)};
const IR::U32 new_index{ir.IMul(primitive_id, amount)};
const IR::U32 new_atomic_value{ir.IAdd(new_index, amount)};
const IR::Value umax_result{&*block.PrependNewInst(
insert_point, IR::Opcode::StorageAtomicUMax32,
{inst.Arg(0), inst.Arg(1), new_atomic_value})};
static_cast<void>(umax_result);
inst.ReplaceUsesWith(new_index);
}
} // Anonymous namespace
void CompactionFallbackPass(IR::Program& program, const HostTranslateInfo& host_info) {
if (program.stage != Stage::Geometry || host_info.support_subgroup_in_geometry_stage) {
return;
}
for (IR::Block* const block : program.post_order_blocks) {
for (IR::Inst& inst : block->Instructions()) {
if (inst.GetOpcode() != IR::Opcode::StorageAtomicIAdd32) {
continue;
}
if (!DependsOnSubgroupBallot(inst.Arg(2), 0)) {
continue;
}
RewriteAtomic(*block, inst);
}
}
}
} // namespace Shader::Optimization
+1
View File
@@ -13,6 +13,7 @@ struct HostTranslateInfo;
namespace Shader::Optimization { namespace Shader::Optimization {
void CollectShaderInfoPass(Environment& env, IR::Program& program); void CollectShaderInfoPass(Environment& env, IR::Program& program);
void CompactionFallbackPass(IR::Program& program, const HostTranslateInfo& host_info);
void ConditionalBarrierPass(IR::Program& program); void ConditionalBarrierPass(IR::Program& program);
void ConstantPropagationPass(Environment& env, IR::Program& program); void ConstantPropagationPass(Environment& env, IR::Program& program);
void DeadCodeEliminationPass(IR::Program& program); void DeadCodeEliminationPass(IR::Program& program);
-6
View File
@@ -41,12 +41,6 @@ struct Profile {
bool support_quad_shuffles{}; bool support_quad_shuffles{};
bool support_vote{}; bool support_vote{};
u32 supported_subgroup_stages{0x7F}; u32 supported_subgroup_stages{0x7F};
bool support_subgroup_in_geometry_stage{}; ///< True when the device advertises subgroup
///< ballot/shuffle support for VK_SHADER_STAGE_GEOMETRY_BIT
///< (VkPhysicalDeviceSubgroupProperties::supportedStages).
///< Many mobile GPUs support subgroup ops only in
///< fragment/compute; guest shaders using VOTE/SHFL in a
///< geometry program need a non-subgroup fallback there.
bool support_viewport_index_layer_non_geometry{}; bool support_viewport_index_layer_non_geometry{};
bool support_viewport_mask{}; bool support_viewport_mask{};
bool support_typeless_image_loads{}; bool support_typeless_image_loads{};
@@ -269,6 +269,7 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_viewport_index_layer = device.HasVertexViewportLayer(), .support_viewport_index_layer = device.HasVertexViewportLayer(),
.support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(), .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(),
.support_conditional_barrier = device.SupportsConditionalBarriers(), .support_conditional_barrier = device.SupportsConditionalBarriers(),
.support_subgroup_in_geometry_stage = true,
} { } {
host_info.ApplyDescriptorLimitPolicy(); host_info.ApplyDescriptorLimitPolicy();
if (use_asynchronous_shaders) { if (use_asynchronous_shaders) {
@@ -410,11 +410,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_quad_shuffles = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_QUAD_BIT), .support_quad_shuffles = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_QUAD_BIT),
.support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT), .support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT),
.supported_subgroup_stages = supported_subgroup_stages, .supported_subgroup_stages = supported_subgroup_stages,
.support_subgroup_in_geometry_stage =
device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT) &&
device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BALLOT_BIT) &&
device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_BIT) &&
device.IsSubgroupFeatureSupportedInStage(VK_SHADER_STAGE_GEOMETRY_BIT),
.support_viewport_index_layer_non_geometry = .support_viewport_index_layer_non_geometry =
device.IsExtShaderViewportIndexLayerSupported(), device.IsExtShaderViewportIndexLayerSupported(),
.support_viewport_mask = device.IsNvViewportArray2Supported(), .support_viewport_mask = device.IsNvViewportArray2Supported(),
@@ -484,6 +479,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(), .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(),
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
.support_conditional_barrier = device.SupportsConditionalBarriers(), .support_conditional_barrier = device.SupportsConditionalBarriers(),
.support_subgroup_in_geometry_stage = profile.SupportsSubgroupStage(Shader::Stage::Geometry),
}; };
host_info.ApplyDescriptorLimitPolicy(); host_info.ApplyDescriptorLimitPolicy();
@@ -479,11 +479,6 @@ FN_MAX_LIMIT_LIST
return properties.subgroup_properties.supportedStages; return properties.subgroup_properties.supportedStages;
} }
/// Returns true if the device supports subgroup ballot/shuffle in the given shader stage.
bool IsSubgroupFeatureSupportedInStage(VkShaderStageFlagBits stage) const {
return properties.subgroup_properties.supportedStages & stage;
}
/// Returns the maximum number of push descriptors. /// Returns the maximum number of push descriptors.
u32 MaxPushDescriptors() const { u32 MaxPushDescriptors() const {
return properties.push_descriptor.maxPushDescriptors; return properties.push_descriptor.maxPushDescriptors;