Compare commits

..

10 Commits

Author SHA1 Message Date
CamilleLaVey ee833674fb Shader OpCapability fallback + fp16 ban adjustments 2026-07-02 19:37:50 -04:00
CamilleLaVey 538b535676 Quick review over workgroup changes 2026-07-02 18:03:41 -04:00
CamilleLaVey a67eaaaace Removal of unneeded depthFormats 2026-07-02 17:12:35 -04:00
CamilleLaVey 9b760e7c99 [TEST] Loggins for depth floats vs missing/ unsupported DepthBiasControl 2026-07-02 16:41:51 -04:00
CamilleLaVey 148586d2f6 [texture_cache, pipeline_cache] Maintenance for some clamps + promoting to general purpose 2026-07-02 14:09:58 -04:00
CamilleLaVey 1ba7705279 [vulkan] Relaxing Workgroup Memory Explicit Layout flags + INT64 Atomics 2026-07-02 13:37:17 -04:00
CamilleLaVey 84b78abe1c [qcom] Removal of SignedZeroInfNan feature bit from stock drivers 2026-07-02 12:55:04 -04:00
CamilleLaVey c717ad0f59 [vulkan, qcom] Extending handling of shader stages + handling of float controls 2026-07-02 05:32:16 -04:00
xbzk 54c3d10b86 [vk, renderdoc] (VUID-02997) avoid vk_image_view as VK_NULL_HANDLE when feature nullDescriptor is unavailable (#4056)
This minor change in src\video_core\renderer_vulkan\pipeline_helper.h allows renderdoc capture on mhr sunbreak.
Maybe it sanitizes some crashes on old vulkan GPUs.

Device log (nvidia kepler gpu vulkan 1.1.117):
[  17.605262] Render.Vulkan <Info> video_core\vulkan_common\vulkan_device.cpp:1041:GetSuitability: Device doesn't support feature nullDescriptor

VUID-VkWriteDescriptorSet-descriptorType-02997
For sampled/combined/storage image descriptors, Khronos says imageView must be valid or VK_NULL_HANDLE
(https://docs.vulkan.org/refpages/latest/refpages/source/VkPhysicalDeviceRobustness2FeaturesKHR.html)

but then it says: if nullDescriptor is not enabled, imageView must not be VK_NULL_HANDLE.
(https://docs.vulkan.org/spec/latest/chapters/descriptorsets.html)
nullDescriptor is exactly the feature that allows descriptors to be written with null handles.

The fix relies on conditionally replacing the null sampled view with Eden’s dummy null-image-view object (ImageView constructor checks HasNullDescriptor which checks features.robustness2.nullDescriptor. cheap checks, and only for null handles, so no notable cost).

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4056
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-07-02 03:23:08 +02:00
xbzk bdf60d79a0 [video_core] gate Mesa EXIT terminator detection behind non-proprietary driver (#4151)
FIXES A REGRESSION FROM 4012.

TryFindSize's Mesa fallback matched the unconditional @PT EXIT (0xE30000000007000F) that nv50_ir emits at program end.
That exact word is also emitted by NVN mid-program for unconditional early-outs, so on retail titles (e.g. Super Mario 3D World) the scan truncated at the first early EXIT and dropped the rest of the shader, making textures vanish.

Mesa's terminal EXIT and NVN's mid-program EXIT are bit-identical, so no mask can separate them. Gate the heuristic on !is_proprietary_driver:

NVN binaries (bindless cbuf slot 2) keep self-branch-only sizing, while nouveau/Mesa homebrew (single terminal EXIT, no self-branch trailer) still uses the EXIT terminator. nv50_ir emits exactly one EXIT (early returns lower to BRA-to-exit, discard to DISCARD), so a single terminal match is correct for that path.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4151
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-07-01 13:06:00 +02:00
16 changed files with 281 additions and 71 deletions
+1 -5
View File
@@ -370,7 +370,6 @@ function(AddPackage)
URL
KEY
SOURCE_SUBDIR
BUNDLED_PACKAGE
FORCE_BUNDLED_PACKAGE
@@ -435,9 +434,7 @@ function(AddPackage)
cpm_utils_message(DEBUG ${PKG_ARGS_NAME} "Download URL is ${pkg_url}")
if(DEFINED PKG_ARGS_KEY AND NOT PKG_ARGS_KEY STREQUAL "")
set(pkg_key ${PKG_ARGS_KEY})
elseif(DEFINED PKG_ARGS_SHA)
if(DEFINED PKG_ARGS_SHA)
string(SUBSTRING ${PKG_ARGS_SHA} 0 4 pkg_key)
elseif(DEFINED PKG_ARGS_VERSION)
set(pkg_key ${PKG_ARGS_VERSION})
@@ -720,7 +717,6 @@ function(AddCIPackage)
TAG "v${ARTIFACT_VERSION}"
MIN_VERSION ${ARTIFACT_VERSION}
ARTIFACT ${ARTIFACT}
KEY "${pkgname}-${ARTIFACT_VERSION}"
HASH ${sha512sum_hash}
FORCE_BUNDLED_PACKAGE ON
${EXTRA_ARGS})
@@ -332,6 +332,9 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
void SetupDenormControl(const Profile& profile, const IR::Program& program, EmitContext& ctx,
Id main_func) {
const Info& info{program.info};
if (profile.has_broken_fp16_float_controls && info.uses_fp16) {
return;
}
if (info.uses_fp32_denorms_flush && info.uses_fp32_denorms_preserve) {
LOG_DEBUG(Shader_SPIRV, "Fp32 denorm flush and preserve on the same shader");
} else if (info.uses_fp32_denorms_flush) {
@@ -432,7 +435,7 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
}
if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id ||
info.uses_subgroup_shuffles) &&
profile.support_vote) {
profile.support_vote && profile.SupportsSubgroupStage(ctx.stage)) {
ctx.AddCapability(spv::Capability::GroupNonUniformBallot);
ctx.AddCapability(spv::Capability::GroupNonUniformShuffle);
if (!profile.warp_size_potentially_larger_than_guest) {
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -15,7 +18,7 @@ Id SharedPointer(EmitContext& ctx, Id offset, u32 index_offset = 0) {
if (index_offset > 0) {
index = ctx.OpIAdd(ctx.U32[1], index, ctx.Const(index_offset));
}
return ctx.profile.support_explicit_workgroup_layout
return ctx.uses_explicit_workgroup_layout
? ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)
: ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index);
}
@@ -155,7 +158,7 @@ Id EmitSharedAtomicExchange32(EmitContext& ctx, Id offset, Id value) {
}
Id EmitSharedAtomicExchange64(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_int64_atomics && ctx.profile.support_explicit_workgroup_layout) {
if (ctx.profile.support_shared_int64_atomics && ctx.uses_explicit_workgroup_layout) {
const Id shift_id{ctx.Const(3U)};
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
const Id pointer{
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -28,7 +31,7 @@ std::pair<Id, Id> ExtractArgs(EmitContext& ctx, Id offset, u32 mask, u32 count)
} // Anonymous namespace
Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer));
@@ -39,7 +42,7 @@ Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer));
@@ -50,7 +53,7 @@ Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
} else {
@@ -60,7 +63,7 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
} else {
@@ -70,7 +73,7 @@ Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)};
return ctx.OpLoad(ctx.U32[1], pointer);
} else {
@@ -79,7 +82,7 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
return ctx.OpLoad(ctx.U32[2], pointer);
} else {
@@ -94,7 +97,7 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
return ctx.OpLoad(ctx.U32[4], pointer);
}
@@ -110,7 +113,7 @@ Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
}
void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
ctx.OpStore(pointer, ctx.OpUConvert(ctx.U8, value));
@@ -120,7 +123,7 @@ void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
}
void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value));
} else {
@@ -130,7 +133,7 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
Id pointer{};
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2);
} else {
const Id shift{ctx.Const(2U)};
@@ -141,7 +144,7 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
}
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
ctx.OpStore(pointer, value);
return;
@@ -156,7 +159,7 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
}
void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_explicit_workgroup_layout) {
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
ctx.OpStore(pointer, value);
return;
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -10,7 +13,14 @@ Id SubgroupScope(EmitContext& ctx) {
return ctx.Const(static_cast<u32>(spv::Scope::Subgroup));
}
bool StageSupportsSubgroups(EmitContext& ctx) {
return ctx.profile.SupportsSubgroupStage(ctx.stage);
}
Id GetThreadId(EmitContext& ctx) {
if (!StageSupportsSubgroups(ctx)) {
return ctx.u32_zero_value;
}
return ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id);
}
@@ -68,6 +78,9 @@ Id GetMaxThreadId(EmitContext& ctx, Id thread_id, Id clamp, Id segmentation_mask
}
Id SelectValue(EmitContext& ctx, Id in_range, Id value, Id src_thread_id) {
if (!StageSupportsSubgroups(ctx)) {
return value;
}
return ctx.OpSelect(
ctx.U32[1], in_range,
ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id), value);
@@ -89,6 +102,9 @@ Id EmitLaneId(EmitContext& ctx) {
}
Id EmitVoteAll(EmitContext& ctx, Id pred) {
if (!StageSupportsSubgroups(ctx)) {
return pred;
}
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpGroupNonUniformAll(ctx.U1, SubgroupScope(ctx), pred);
}
@@ -102,6 +118,9 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) {
}
Id EmitVoteAny(EmitContext& ctx, Id pred) {
if (!StageSupportsSubgroups(ctx)) {
return pred;
}
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpGroupNonUniformAny(ctx.U1, SubgroupScope(ctx), pred);
}
@@ -115,6 +134,9 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) {
}
Id EmitVoteEqual(EmitContext& ctx, Id pred) {
if (!StageSupportsSubgroups(ctx)) {
return ctx.true_value;
}
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpGroupNonUniformAllEqual(ctx.U1, SubgroupScope(ctx), pred);
}
@@ -129,6 +151,9 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) {
}
Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
if (!StageSupportsSubgroups(ctx)) {
return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1u), ctx.u32_zero_value);
}
const Id ballot{ctx.OpGroupNonUniformBallot(ctx.U32[4], SubgroupScope(ctx), pred)};
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpCompositeExtract(ctx.U32[1], ballot, 0U);
@@ -137,22 +162,37 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
}
Id EmitSubgroupEqMask(EmitContext& ctx) {
if (!StageSupportsSubgroups(ctx)) {
return ctx.Const(1u);
}
return LoadMask(ctx, ctx.subgroup_mask_eq);
}
Id EmitSubgroupLtMask(EmitContext& ctx) {
if (!StageSupportsSubgroups(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_lt);
}
Id EmitSubgroupLeMask(EmitContext& ctx) {
if (!StageSupportsSubgroups(ctx)) {
return ctx.Const(1u);
}
return LoadMask(ctx, ctx.subgroup_mask_le);
}
Id EmitSubgroupGtMask(EmitContext& ctx) {
if (!StageSupportsSubgroups(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_gt);
}
Id EmitSubgroupGeMask(EmitContext& ctx) {
if (!StageSupportsSubgroups(ctx)) {
return ctx.Const(1u);
}
return LoadMask(ctx, ctx.subgroup_mask_ge);
}
@@ -222,7 +262,7 @@ Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id
Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) {
const Id three{ctx.Const(3U)};
Id mask{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)};
Id mask{GetThreadId(ctx)};
mask = ctx.OpBitwiseAnd(ctx.U32[1], mask, three);
mask = ctx.OpShiftLeftLogical(ctx.U32[1], mask, ctx.Const(1U));
mask = ctx.OpShiftRightLogical(ctx.U32[1], swizzle, mask);
@@ -371,7 +371,7 @@ Id CasFunction(EmitContext& ctx, Operation operation, Id value_type) {
Id CasLoop(EmitContext& ctx, Operation operation, Id array_pointer, Id element_pointer,
Id value_type, Id memory_type, spv::Scope scope) {
const bool is_shared{scope == spv::Scope::Workgroup};
const bool is_struct{!is_shared || ctx.profile.support_explicit_workgroup_layout};
const bool is_struct{!is_shared || ctx.uses_explicit_workgroup_layout};
const Id cas_func{CasFunction(ctx, operation, value_type)};
const Id zero{ctx.u32_zero_value};
const Id scope_id{ctx.Const(static_cast<u32>(scope))};
@@ -620,7 +620,11 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) {
return std::make_tuple(variable, element_pointer, pointer);
}};
if (profile.support_explicit_workgroup_layout) {
uses_explicit_workgroup_layout =
profile.support_explicit_workgroup_layout &&
(!program.info.uses_int8 || profile.support_workgroup_layout_8bit_access) &&
(!program.info.uses_int16 || profile.support_workgroup_layout_16bit_access);
if (uses_explicit_workgroup_layout) {
AddExtension("SPV_KHR_workgroup_memory_explicit_layout");
AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
if (program.info.uses_int8) {
@@ -938,6 +942,10 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
if (!info.uses_global_memory || !profile.support_int64) {
return;
}
if (!profile.support_descriptor_aliasing) {
DefineGlobalMemoryFunctionsU32Fallback(info);
return;
}
using DefPtr = Id StorageDefinitions::*;
const Id zero{u32_zero_value};
const auto define_body{[&](DefPtr ssbo_member, Id addr, Id element_pointer, u32 shift,
@@ -1015,6 +1023,111 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
define(&StorageDefinitions::U32x4, storage_types.U32x4, U32[4], sizeof(u32[4]));
}
void EmitContext::DefineGlobalMemoryFunctionsU32Fallback(const Info& info) {
const Id zero{u32_zero_value};
const auto define_body{[&](Id addr, u32 num_words, auto&& callback) {
AddLabel();
const size_t num_buffers{info.storage_buffers_descriptors.size()};
for (size_t index = 0; index < num_buffers; ++index) {
if (!info.nvn_buffer_used[index]) {
continue;
}
const auto& ssbo{info.storage_buffers_descriptors[index]};
const u32 addr_word{ssbo.cbuf_offset / 4};
const u32 addr_lo_comp{addr_word % 4};
const Id cbuf{cbufs[ssbo.cbuf_index].U32x4};
const Id addr_vec_pointer{
OpAccessChain(uniform_types.U32x4, cbuf, zero, Const(addr_word / 4))};
const Id addr_vec{OpLoad(U32[4], addr_vec_pointer)};
const Id addr_lo{OpCompositeExtract(U32[1], addr_vec, addr_lo_comp)};
const Id addr_hi{OpCompositeExtract(U32[1], addr_vec, addr_lo_comp + 1U)};
const Id unaligned_addr{
OpBitcast(U64, OpCompositeConstruct(U32[2], addr_lo, addr_hi))};
const u64 ssbo_align_mask{~(profile.min_ssbo_alignment - 1U)};
const Id ssbo_addr{OpBitwiseAnd(U64, unaligned_addr, Constant(U64, ssbo_align_mask))};
const u32 size_word{addr_word + 2};
Id size_vec{addr_vec};
if (size_word / 4 != addr_word / 4) {
const Id size_vec_pointer{
OpAccessChain(uniform_types.U32x4, cbuf, zero, Const(size_word / 4))};
size_vec = OpLoad(U32[4], size_vec_pointer);
}
const Id ssbo_size{
OpUConvert(U64, OpCompositeExtract(U32[1], size_vec, size_word % 4))};
const Id ssbo_end{OpIAdd(U64, ssbo_addr, ssbo_size)};
const Id cond{OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, ssbo_addr),
OpULessThan(U1, addr, ssbo_end))};
const Id then_label{OpLabel()};
const Id else_label{OpLabel()};
OpSelectionMerge(else_label, spv::SelectionControlMask::MaskNone);
OpBranchConditional(cond, then_label, else_label);
AddLabel(then_label);
const Id ssbo_id{ssbos[index].U32};
const Id ssbo_offset{OpUConvert(U32[1], OpISub(U64, addr, ssbo_addr))};
const Id base_word{OpShiftRightLogical(U32[1], ssbo_offset, Const(2U))};
std::array<Id, 4> word_pointers{};
for (u32 word = 0; word < num_words; ++word) {
const Id word_index{word == 0 ? base_word
: OpIAdd(U32[1], base_word, Const(word))};
word_pointers[word] =
OpAccessChain(storage_types.U32.element, ssbo_id, zero, word_index);
}
callback(word_pointers);
AddLabel(else_label);
}
}};
const auto define_load{[&](Id type, u32 num_words) {
const Id function_type{TypeFunction(type, U64)};
const Id func_id{OpFunction(type, spv::FunctionControlMask::MaskNone, function_type)};
const Id addr{OpFunctionParameter(U64)};
define_body(addr, num_words, [&](const std::array<Id, 4>& pointers) {
std::array<Id, 4> words{};
for (u32 word = 0; word < num_words; ++word) {
words[word] = OpLoad(U32[1], pointers[word]);
}
switch (num_words) {
case 1:
OpReturnValue(words[0]);
break;
case 2:
OpReturnValue(OpCompositeConstruct(type, words[0], words[1]));
break;
default:
OpReturnValue(
OpCompositeConstruct(type, words[0], words[1], words[2], words[3]));
break;
}
});
OpReturnValue(ConstantNull(type));
OpFunctionEnd();
return func_id;
}};
const auto define_write{[&](Id type, u32 num_words) {
const Id function_type{TypeFunction(void_id, U64, type)};
const Id func_id{OpFunction(void_id, spv::FunctionControlMask::MaskNone, function_type)};
const Id addr{OpFunctionParameter(U64)};
const Id data{OpFunctionParameter(type)};
define_body(addr, num_words, [&](const std::array<Id, 4>& pointers) {
for (u32 word = 0; word < num_words; ++word) {
const Id value{num_words == 1 ? data : OpCompositeExtract(U32[1], data, word)};
OpStore(pointers[word], value);
}
OpReturn();
});
OpReturn();
OpFunctionEnd();
return func_id;
}};
load_global_func_u32 = define_load(U32[1], 1);
load_global_func_u32x2 = define_load(U32[2], 2);
load_global_func_u32x4 = define_load(U32[4], 4);
write_global_func_u32 = define_write(U32[1], 1);
write_global_func_u32x2 = define_write(U32[2], 2);
write_global_func_u32x4 = define_write(U32[4], 4);
}
void EmitContext::DefineRescalingInput(const Info& info) {
if (!info.uses_rescaling_uniform) {
return;
@@ -1438,7 +1551,7 @@ void EmitContext::DefineInputs(const IR::Program& program) {
if (info.uses_is_helper_invocation) {
is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation);
}
if (info.uses_subgroup_mask) {
if (info.uses_subgroup_mask && profile.SupportsSubgroupStage(stage)) {
subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR);
subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR);
subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR);
@@ -1452,9 +1565,10 @@ void EmitContext::DefineInputs(const IR::Program& program) {
Decorate(subgroup_mask_ge, spv::Decoration::Flat);
}
}
if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
(profile.warp_size_potentially_larger_than_guest &&
(info.uses_subgroup_vote || info.uses_subgroup_mask))) {
if ((info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
(profile.warp_size_potentially_larger_than_guest &&
(info.uses_subgroup_vote || info.uses_subgroup_mask))) &&
profile.SupportsSubgroupStage(stage)) {
AddCapability(spv::Capability::GroupNonUniform);
subgroup_local_invocation_id =
DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId);
@@ -310,6 +310,10 @@ public:
Id local_memory{};
/// True when this shader's shared memory uses SPV_KHR_workgroup_memory_explicit_layout.
/// False when the host lacks the extension or a width this shader accesses natively.
bool uses_explicit_workgroup_layout{};
Id shared_memory_u8{};
Id shared_memory_u16{};
Id shared_memory_u32{};
@@ -387,6 +391,7 @@ private:
void DefineAttributeMemAccess(const Info& info);
void DefineWriteStorageCasLoopFunction(const Info& info);
void DefineGlobalMemoryFunctions(const Info& info);
void DefineGlobalMemoryFunctionsU32Fallback(const Info& info);
void DefineRescalingInput(const Info& info);
void DefineRescalingInputPushConstant();
void DefineRescalingInputUniformConstant();
+10
View File
@@ -10,6 +10,8 @@
namespace Shader {
enum class Stage : u32;
struct Profile {
u32 supported_spirv{0x00010000};
bool unified_descriptor_binding{};
@@ -29,12 +31,16 @@ struct Profile {
bool support_fp32_signed_zero_nan_preserve{};
bool support_fp64_signed_zero_nan_preserve{};
bool support_explicit_workgroup_layout{};
bool support_workgroup_layout_8bit_access{};
bool support_workgroup_layout_16bit_access{};
bool support_vote{};
u32 supported_subgroup_stages{0x7F};
bool support_viewport_index_layer_non_geometry{};
bool support_viewport_mask{};
bool support_typeless_image_loads{};
bool support_demote_to_helper_invocation{};
bool support_int64_atomics{};
bool support_shared_int64_atomics{};
bool support_derivative_control{};
bool support_geometry_shader_passthrough{};
bool support_native_ndc{};
@@ -93,6 +99,10 @@ struct Profile {
u64 min_ssbo_alignment{};
u32 max_user_clip_distances{};
bool SupportsSubgroupStage(Stage stage) const {
return (supported_subgroup_stages & (1u << static_cast<u32>(stage))) != 0;
}
};
} // namespace Shader
@@ -203,7 +203,11 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
const VideoCommon::ImageViewId image_view_id{(views++)->id};
const VideoCommon::SamplerId sampler_id{*(samplers++)};
ImageView& image_view{texture_cache.GetImageView(image_view_id)};
const VkImageView vk_image_view{image_view.Handle(desc.type)};
VkImageView vk_image_view{image_view.Handle(desc.type)};
if (vk_image_view == VK_NULL_HANDLE) {
const VkImageView null_image_view{texture_cache.GetImageView(VideoCommon::NULL_IMAGE_VIEW_ID).Handle(desc.type)};
if (null_image_view != VK_NULL_HANDLE) vk_image_view = null_image_view;
}
const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
!image_view.SupportsAnisotropy()};
@@ -378,6 +378,20 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
serialization_thread(1, "VkPipelineSerialization") {
const auto& float_control{device.FloatControlProperties()};
const VkDriverId driver_id{device.GetDriverID()};
const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()};
const auto subgroup_stage_bit{[subgroup_stages](VkShaderStageFlags flag, Shader::Stage stage) {
return (subgroup_stages & flag) != 0 ? (1u << static_cast<u32>(stage)) : 0u;
}};
const u32 supported_subgroup_stages{
subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexA) |
subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexB) |
subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
Shader::Stage::TessellationControl) |
subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
Shader::Stage::TessellationEval) |
subgroup_stage_bit(VK_SHADER_STAGE_GEOMETRY_BIT, Shader::Stage::Geometry) |
subgroup_stage_bit(VK_SHADER_STAGE_FRAGMENT_BIT, Shader::Stage::Fragment) |
subgroup_stage_bit(VK_SHADER_STAGE_COMPUTE_BIT, Shader::Stage::Compute)};
profile = Shader::Profile{
.supported_spirv = device.SupportedSpirvVersion(),
.unified_descriptor_binding = true,
@@ -402,7 +416,12 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_fp64_signed_zero_nan_preserve =
float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE,
.support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(),
.support_workgroup_layout_8bit_access =
device.IsWorkgroupMemoryExplicitLayout8BitSupported(),
.support_workgroup_layout_16bit_access =
device.IsWorkgroupMemoryExplicitLayout16BitSupported(),
.support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT),
.supported_subgroup_stages = supported_subgroup_stages,
.support_viewport_index_layer_non_geometry =
device.IsExtShaderViewportIndexLayerSupported(),
.support_viewport_mask = device.IsNvViewportArray2Supported(),
@@ -410,6 +429,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_demote_to_helper_invocation =
device.IsExtShaderDemoteToHelperInvocationSupported(),
.support_int64_atomics = device.IsExtShaderAtomicInt64Supported(),
.support_shared_int64_atomics = device.IsSharedInt64AtomicsSupported(),
.support_derivative_control = true,
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
.support_native_ndc = device.IsExtDepthClipControlSupported(),
@@ -433,7 +453,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.has_broken_spirv_position_input = driver_id == false,
.has_broken_unsigned_image_offsets = false,
.has_broken_signed_operations = false,
.has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY,
.has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY ||
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.ignore_nan_fp_comparisons = false,
.has_broken_spirv_subgroup_mask_vector_extract_dynamic = false,
.has_broken_robust =
@@ -904,12 +925,8 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
}
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
const VkDriverIdKHR driver_id = device.GetDriverID();
const bool needs_shared_mem_clamp =
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
const u32 max_shared_memory = device.GetMaxComputeSharedMemorySize();
if (needs_shared_mem_clamp && program.shared_memory_size > max_shared_memory) {
if (program.shared_memory_size > max_shared_memory) {
LOG_WARNING(Render_Vulkan,
"Compute shader 0x{:016x} requests {}KB shared memory but device max is {}KB - clamping",
key.unique_hash,
@@ -1255,6 +1255,18 @@ void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs) {
}
}
const bool is_float_depth =
regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT ||
regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT;
if (is_float_depth && units != 0.0f && !device.IsExtDepthBiasControlSupported()) {
static bool logged_float_bias_warning = false;
if (!logged_float_bias_warning) {
logged_float_bias_warning = true;
LOG_WARNING(Render_Vulkan,
"Depth bias on a float depth target without VK_EXT_depth_bias_control");
}
}
scheduler.Record([constant = units, clamp = regs.depth_bias_clamp,
factor = regs.slope_scale_depth_bias, this](vk::CommandBuffer cmdbuf) {
if (device.IsExtDepthBiasControlSupported()) {
+2 -3
View File
@@ -255,8 +255,7 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
static constexpr u64 SELF_BRANCH_A = 0xE2400FFFFF87000FULL;
static constexpr u64 SELF_BRANCH_B = 0xE2400FFFFF07000FULL;
static constexpr u64 MESA_EXIT_MASK = 0xFFF00000000F001FULL;
static constexpr u64 MESA_EXIT_VALUE = (0xE30ULL << 52) | (0x7ULL << 16) | 0xFULL;
static constexpr u64 EXIT_VALUE = 0xE30000000007000FULL;
code.resize(MAXIMUM_SIZE / INST_SIZE);
@@ -271,7 +270,7 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
if (inst == SELF_BRANCH_A || inst == SELF_BRANCH_B) {
return offset + index;
}
if ((inst & MESA_EXIT_MASK) == MESA_EXIT_VALUE) {
if (!is_proprietary_driver && inst == EXIT_VALUE) {
return offset + index + INST_SIZE;
}
}
+4 -1
View File
@@ -1915,13 +1915,16 @@ void TextureCache<P>::TrimInactiveSamplers(size_t budget) {
ankerl::unordered_dense::set<SamplerId> active_sampler_ids;
for (auto const& e : channel_state->sampler_ids)
active_sampler_ids.insert(e.second);
if constexpr (requires { runtime.Finish(); }) {
runtime.Finish();
}
// Elements in the map must be necesarily valid
size_t removed = 0;
for (auto it = channel_state->samplers.begin(); it != channel_state->samplers.end();) {
const SamplerId sampler_id = it->second;
if (!sampler_id || sampler_id == CORRUPT_ID) {
it = channel_state->samplers.erase(it);
} else if (std::ranges::find(active_sampler_ids, sampler_id) != active_sampler_ids.end()) {
} else if (active_sampler_ids.contains(sampler_id)) {
++it;
} else {
slot_samplers.erase(sampler_id);
+5 -31
View File
@@ -501,14 +501,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
LOG_WARNING(Render_Vulkan,
"Qualcomm drivers have slow push descriptor implementation");
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
LOG_WARNING(Render_Vulkan,
"Disabling shader float controls and 64-bit integer features on Qualcomm proprietary drivers");
RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64,
VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
features.shader_atomic_int64.shaderBufferInt64Atomics = false;
features.shader_atomic_int64.shaderSharedInt64Atomics = false;
features.features.shaderInt64 = false;
#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64)
// BCn patching only safe on Android 9+ (API 28+). Older versions crash on driver load.
@@ -561,7 +553,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
features.shader_float16_int8.shaderFloat16 = false;
}
// Mali/ NVIDIA proprietary drivers: Shader stencil export not supported
// Use hardware depth/stencil blits instead when available
if (!extensions.shader_stencil_export) {
LOG_INFO(Render_Vulkan,
@@ -574,8 +565,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (!is_blit_depth24_stencil8_supported && !is_blit_depth32_stencil8_supported) {
LOG_WARNING(Render_Vulkan,
"NVIDIA: Neither shader export nor hardware blits available for "
"depth/stencil. Performance may be degraded.");
"Neither shader export nor hardware blits available for "
"depth/stencil.");
}
}
}
@@ -653,21 +644,13 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (is_turnip || is_qualcomm) {
LOG_WARNING(Render_Vulkan, "Driver requires higher-than-reported binding limits");
properties.properties.limits.maxVertexInputBindings = 32;
properties.properties.limits.maxVertexInputBindings =
(std::max)(properties.properties.limits.maxVertexInputBindings, 32U);
}
const auto dyna_state = Settings::values.dyna_state.GetValue();
// Base dynamic states (VIEWPORT, SCISSOR, DEPTH_BIAS, etc.) are ALWAYS active in vk_graphics_pipeline.cpp
// This slider controls EXTENDED dynamic states with accumulative levels per Vulkan specs:
// Level 0 = Core Dynamic States only (Vulkan 1.0)
// Level 1 = Core + VK_EXT_extended_dynamic_state
// Level 2 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2
// Level 3 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2 + VK_EXT_extended_dynamic_state3
switch (dyna_state) {
case Settings::ExtendedDynamicState::Disabled:
// Level 0: Disable all extended dynamic state extensions
RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state,
VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
@@ -678,7 +661,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
dynamic_state3_enables = false;
break;
case Settings::ExtendedDynamicState::EDS1:
// Level 1: Enable EDS1, disable EDS2 and EDS3
RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3,
@@ -687,7 +669,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
dynamic_state3_enables = false;
break;
case Settings::ExtendedDynamicState::EDS2:
// Level 2: Enable EDS1 + EDS2, disable EDS3
RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3,
VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
dynamic_state3_blending = false;
@@ -695,12 +676,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
break;
case Settings::ExtendedDynamicState::EDS3:
default:
// Level 3: Enable all (EDS1 + EDS2 + EDS3)
break;
}
// VK_EXT_vertex_input_dynamic_state is independent from EDS
// It can be enabled even without extended_dynamic_state
if (!Settings::values.vertex_input_dynamic_state.GetValue()) {
RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
}
@@ -1282,8 +1260,7 @@ void Device::RemoveUnsuitableExtensions() {
VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME);
// VK_KHR_shader_atomic_int64
extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics &&
features.shader_atomic_int64.shaderSharedInt64Atomics;
extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics;
RemoveExtensionFeatureIfUnsuitable(extensions.shader_atomic_int64, features.shader_atomic_int64,
VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
@@ -1332,10 +1309,7 @@ void Device::RemoveUnsuitableExtensions() {
// VK_KHR_workgroup_memory_explicit_layout
extensions.workgroup_memory_explicit_layout =
features.features.shaderInt16 &&
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout &&
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess &&
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess &&
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout;
RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout,
features.workgroup_memory_explicit_layout,
+26 -2
View File
@@ -403,6 +403,11 @@ FN_MAX_LIMIT_LIST
return properties.subgroup_properties.supportedOperations & feature;
}
/// Returns the shader stages that support subgroup operations.
VkShaderStageFlags GetSubgroupSupportedStages() const {
return properties.subgroup_properties.supportedStages;
}
/// Returns the maximum number of push descriptors.
u32 MaxPushDescriptors() const {
return properties.push_descriptor.maxPushDescriptors;
@@ -483,6 +488,18 @@ FN_MAX_LIMIT_LIST
return extensions.workgroup_memory_explicit_layout;
}
/// Returns true if the device supports 8-bit accesses to workgroup explicit layout memory.
bool IsWorkgroupMemoryExplicitLayout8BitSupported() const {
return extensions.workgroup_memory_explicit_layout &&
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess;
}
/// Returns true if the device supports 16-bit accesses to workgroup explicit layout memory.
bool IsWorkgroupMemoryExplicitLayout16BitSupported() const {
return extensions.workgroup_memory_explicit_layout && features.features.shaderInt16 &&
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess;
}
/// Returns true if the device supports VK_KHR_image_format_list.
bool IsKhrImageFormatListSupported() const {
return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2;
@@ -711,9 +728,16 @@ FN_MAX_LIMIT_LIST
features.provoking_vertex.transformFeedbackPreservesProvokingVertex;
}
/// Returns true if the device supports VK_KHR_shader_atomic_int64.
/// Returns true if the device supports int64 atomics on storage buffers.
bool IsExtShaderAtomicInt64Supported() const {
return extensions.shader_atomic_int64;
return extensions.shader_atomic_int64 &&
features.shader_atomic_int64.shaderBufferInt64Atomics;
}
/// Returns true if the device supports int64 atomics on workgroup (shared) memory.
bool IsSharedInt64AtomicsSupported() const {
return extensions.shader_atomic_int64 &&
features.shader_atomic_int64.shaderSharedInt64Atomics;
}
bool IsExtConditionalRendering() const {