Compare commits
73 Commits
android/fi
...
test-rever
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54fdc40793 | ||
|
|
d4943218d6 | ||
|
|
aea945b671 | ||
|
|
c52fda760a | ||
|
|
c168755c65 | ||
|
|
8a83cf0271 | ||
|
|
3db41fbce6 | ||
|
|
5e7fb6eead | ||
|
|
bcc5390943 | ||
|
|
a51d875d91 | ||
|
|
6134a57367 | ||
|
|
c845b6086f | ||
|
|
31c168efe1 | ||
|
|
8bd87204f5 | ||
|
|
e72a206aee | ||
|
|
6a62fa7ee3 | ||
|
|
52b630dfdc | ||
|
|
4860050358 | ||
|
|
47f0563c1b | ||
|
|
b1208f03ee | ||
|
|
0fd603c094 | ||
|
|
1ca19af7fb | ||
|
|
ddd78c3b37 | ||
|
|
2e68f8795d | ||
|
|
d3595fd2b1 | ||
|
|
033531509b | ||
|
|
b9954de1ca | ||
|
|
5f88deeebf | ||
|
|
d25da944ed | ||
|
|
ec274a855e | ||
|
|
8133d4a8b4 | ||
|
|
4f3e4bf9cb | ||
|
|
ec9e0f37ea | ||
|
|
b5f7735dba | ||
|
|
5f501d6ec0 | ||
|
|
e820f304a5 | ||
|
|
3527a33430 | ||
|
|
ee5565077c | ||
|
|
9085ff1229 | ||
|
|
6eff1779a2 | ||
|
|
3228cffd23 | ||
|
|
9d9530efe0 | ||
|
|
aaaa7c7601 | ||
|
|
7f8a507b79 | ||
|
|
c28ae059e8 | ||
|
|
7f1369f9a8 | ||
|
|
6b05c164a1 | ||
|
|
a3f9d3b59c | ||
|
|
b066a6ffa0 | ||
|
|
a14cba7f11 | ||
|
|
2d85b70373 | ||
|
|
aa8cc4da38 | ||
|
|
baddaf0040 | ||
|
|
35b4e34e09 | ||
|
|
b574e9c334 | ||
|
|
d6b5a3e181 | ||
|
|
a65a35432e | ||
|
|
6e575364eb | ||
|
|
71a1442ab6 | ||
|
|
4a17762ed7 | ||
|
|
447c4de73d | ||
|
|
cd2c4d8caf | ||
|
|
ee64c945fb | ||
|
|
eec5d48220 | ||
|
|
75cc43a57a | ||
|
|
0078094b86 | ||
|
|
3cd33fce44 | ||
|
|
ccafe0ed91 | ||
|
|
94af9ff51f | ||
|
|
d229fdca32 | ||
|
|
e636e940ed | ||
|
|
2798174b00 | ||
|
|
46f2084114 |
@@ -121,7 +121,7 @@ else()
|
|||||||
-Werror=unused
|
-Werror=unused
|
||||||
|
|
||||||
-Wno-attributes
|
-Wno-attributes
|
||||||
-Wno-invalid-offsetof
|
$<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>
|
||||||
-Wno-unused-parameter
|
-Wno-unused-parameter
|
||||||
-Wno-missing-field-initializers
|
-Wno-missing-field-initializers
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -730,7 +730,9 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length,
|
|||||||
ASSERT(virtual_offset % PageAlignment == 0);
|
ASSERT(virtual_offset % PageAlignment == 0);
|
||||||
ASSERT(host_offset % PageAlignment == 0);
|
ASSERT(host_offset % PageAlignment == 0);
|
||||||
ASSERT(length % PageAlignment == 0);
|
ASSERT(length % PageAlignment == 0);
|
||||||
ASSERT(virtual_offset + length <= virtual_size);
|
if (impl && virtual_base) {
|
||||||
|
ASSERT(virtual_offset + length <= virtual_size);
|
||||||
|
}
|
||||||
ASSERT(host_offset + length <= backing_size);
|
ASSERT(host_offset + length <= backing_size);
|
||||||
if (length == 0 || !virtual_base || !impl) {
|
if (length == 0 || !virtual_base || !impl) {
|
||||||
return;
|
return;
|
||||||
@@ -741,7 +743,9 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length,
|
|||||||
void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap) {
|
void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap) {
|
||||||
ASSERT(virtual_offset % PageAlignment == 0);
|
ASSERT(virtual_offset % PageAlignment == 0);
|
||||||
ASSERT(length % PageAlignment == 0);
|
ASSERT(length % PageAlignment == 0);
|
||||||
ASSERT(virtual_offset + length <= virtual_size);
|
if (impl && virtual_base) {
|
||||||
|
ASSERT(virtual_offset + length <= virtual_size);
|
||||||
|
}
|
||||||
if (length == 0 || !virtual_base || !impl) {
|
if (length == 0 || !virtual_base || !impl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -751,7 +755,9 @@ void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap)
|
|||||||
void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission perm) {
|
void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission perm) {
|
||||||
ASSERT(virtual_offset % PageAlignment == 0);
|
ASSERT(virtual_offset % PageAlignment == 0);
|
||||||
ASSERT(length % PageAlignment == 0);
|
ASSERT(length % PageAlignment == 0);
|
||||||
ASSERT(virtual_offset + length <= virtual_size);
|
if (impl && virtual_base) {
|
||||||
|
ASSERT(virtual_offset + length <= virtual_size);
|
||||||
|
}
|
||||||
if (length == 0 || !virtual_base || !impl) {
|
if (length == 0 || !virtual_base || !impl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -393,6 +396,24 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
|
|||||||
const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
|
const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
|
||||||
BufferDescriptorB()[buffer_index].Size()};
|
BufferDescriptorB()[buffer_index].Size()};
|
||||||
const std::size_t buffer_size{GetWriteBufferSize(buffer_index)};
|
const std::size_t buffer_size{GetWriteBufferSize(buffer_index)};
|
||||||
|
|
||||||
|
// Defensive check: if client didn't provide output buffer, log detailed error but don't crash
|
||||||
|
if (buffer_size == 0) {
|
||||||
|
LOG_ERROR(Core,
|
||||||
|
"WriteBuffer called but client provided NO output buffer! "
|
||||||
|
"Requested size: 0x{:X}, buffer_index: {}, is_buffer_b: {}, "
|
||||||
|
"BufferB count: {}, BufferC count: {}",
|
||||||
|
size, buffer_index, is_buffer_b, BufferDescriptorB().size(),
|
||||||
|
BufferDescriptorC().size());
|
||||||
|
|
||||||
|
// Log command context for debugging
|
||||||
|
LOG_ERROR(Core, "IPC Command: 0x{:X}, Type: {}", GetCommand(),
|
||||||
|
static_cast<u32>(GetCommandType()));
|
||||||
|
|
||||||
|
// Return 0 instead of crashing - let service handle error
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (size > buffer_size) {
|
if (size > buffer_size) {
|
||||||
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
|
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
|
||||||
buffer_size);
|
buffer_size);
|
||||||
|
|||||||
@@ -380,13 +380,14 @@ void EmitContext::SetupExtensions() {
|
|||||||
if (info.uses_int64 && profile.support_int64) {
|
if (info.uses_int64 && profile.support_int64) {
|
||||||
header += "#extension GL_ARB_gpu_shader_int64 : enable\n";
|
header += "#extension GL_ARB_gpu_shader_int64 : enable\n";
|
||||||
}
|
}
|
||||||
if (info.uses_int64_bit_atomics) {
|
if (info.uses_int64_bit_atomics && profile.support_gl_shader_atomic_int64) {
|
||||||
header += "#extension GL_NV_shader_atomic_int64 : enable\n";
|
header += "#extension GL_NV_shader_atomic_int64 : enable\n";
|
||||||
}
|
}
|
||||||
if (info.uses_atomic_f32_add) {
|
if (info.uses_atomic_f32_add && profile.support_gl_shader_atomic_float) {
|
||||||
header += "#extension GL_NV_shader_atomic_float : enable\n";
|
header += "#extension GL_NV_shader_atomic_float : enable\n";
|
||||||
}
|
}
|
||||||
if (info.uses_atomic_f16x2_add || info.uses_atomic_f16x2_min || info.uses_atomic_f16x2_max) {
|
if ((info.uses_atomic_f16x2_add || info.uses_atomic_f16x2_min || info.uses_atomic_f16x2_max) &&
|
||||||
|
profile.support_gl_shader_atomic_fp16_vector) {
|
||||||
header += "#extension GL_NV_shader_atomic_fp16_vector : enable\n";
|
header += "#extension GL_NV_shader_atomic_fp16_vector : enable\n";
|
||||||
}
|
}
|
||||||
if (info.uses_fp16) {
|
if (info.uses_fp16) {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -92,7 +95,7 @@ void EmitLoadGlobalS16(EmitContext&) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitLoadGlobal32(EmitContext& ctx, Id address) {
|
Id EmitLoadGlobal32(EmitContext& ctx, Id address) {
|
||||||
if (ctx.profile.support_int64) {
|
if (ctx.SupportsNativeInt64() || ctx.UsesInt64Emulation()) {
|
||||||
return ctx.OpFunctionCall(ctx.U32[1], ctx.load_global_func_u32, address);
|
return ctx.OpFunctionCall(ctx.U32[1], ctx.load_global_func_u32, address);
|
||||||
}
|
}
|
||||||
LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation");
|
LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation");
|
||||||
@@ -100,7 +103,7 @@ Id EmitLoadGlobal32(EmitContext& ctx, Id address) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitLoadGlobal64(EmitContext& ctx, Id address) {
|
Id EmitLoadGlobal64(EmitContext& ctx, Id address) {
|
||||||
if (ctx.profile.support_int64) {
|
if (ctx.SupportsNativeInt64() || ctx.UsesInt64Emulation()) {
|
||||||
return ctx.OpFunctionCall(ctx.U32[2], ctx.load_global_func_u32x2, address);
|
return ctx.OpFunctionCall(ctx.U32[2], ctx.load_global_func_u32x2, address);
|
||||||
}
|
}
|
||||||
LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation");
|
LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation");
|
||||||
@@ -108,7 +111,7 @@ Id EmitLoadGlobal64(EmitContext& ctx, Id address) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitLoadGlobal128(EmitContext& ctx, Id address) {
|
Id EmitLoadGlobal128(EmitContext& ctx, Id address) {
|
||||||
if (ctx.profile.support_int64) {
|
if (ctx.SupportsNativeInt64() || ctx.UsesInt64Emulation()) {
|
||||||
return ctx.OpFunctionCall(ctx.U32[4], ctx.load_global_func_u32x4, address);
|
return ctx.OpFunctionCall(ctx.U32[4], ctx.load_global_func_u32x4, address);
|
||||||
}
|
}
|
||||||
LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation");
|
LOG_WARNING(Shader_SPIRV, "Int64 not supported, ignoring memory operation");
|
||||||
@@ -132,7 +135,7 @@ void EmitWriteGlobalS16(EmitContext&) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EmitWriteGlobal32(EmitContext& ctx, Id address, Id value) {
|
void EmitWriteGlobal32(EmitContext& ctx, Id address, Id value) {
|
||||||
if (ctx.profile.support_int64) {
|
if (ctx.SupportsNativeInt64() || ctx.UsesInt64Emulation()) {
|
||||||
ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32, address, value);
|
ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32, address, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -140,7 +143,7 @@ void EmitWriteGlobal32(EmitContext& ctx, Id address, Id value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EmitWriteGlobal64(EmitContext& ctx, Id address, Id value) {
|
void EmitWriteGlobal64(EmitContext& ctx, Id address, Id value) {
|
||||||
if (ctx.profile.support_int64) {
|
if (ctx.SupportsNativeInt64() || ctx.UsesInt64Emulation()) {
|
||||||
ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32x2, address, value);
|
ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32x2, address, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -148,7 +151,7 @@ void EmitWriteGlobal64(EmitContext& ctx, Id address, Id value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EmitWriteGlobal128(EmitContext& ctx, Id address, Id value) {
|
void EmitWriteGlobal128(EmitContext& ctx, Id address, Id value) {
|
||||||
if (ctx.profile.support_int64) {
|
if (ctx.SupportsNativeInt64() || ctx.UsesInt64Emulation()) {
|
||||||
ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32x4, address, value);
|
ctx.OpFunctionCall(ctx.void_id, ctx.write_global_func_u32x4, address, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -460,9 +460,14 @@ void VectorTypes::Define(Sirit::Module& sirit_ctx, Id base_type, std::string_vie
|
|||||||
|
|
||||||
EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_info_,
|
EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_info_,
|
||||||
IR::Program& program, Bindings& bindings)
|
IR::Program& program, Bindings& bindings)
|
||||||
: Sirit::Module(profile_.supported_spirv), profile{profile_}, runtime_info{runtime_info_},
|
: Sirit::Module(profile_.supported_spirv), profile{profile_}, runtime_info{runtime_info_},
|
||||||
stage{program.stage}, texture_rescaling_index{bindings.texture_scaling_index},
|
stage{program.stage},
|
||||||
image_rescaling_index{bindings.image_scaling_index} {
|
// Enable int64 emulation if host lacks int64 but we either use int64 ops
|
||||||
|
// or we need 64-bit addressing for global memory operations.
|
||||||
|
emulate_int64{!profile.support_int64 &&
|
||||||
|
(program.info.uses_int64 || program.info.uses_global_memory)},
|
||||||
|
texture_rescaling_index{bindings.texture_scaling_index},
|
||||||
|
image_rescaling_index{bindings.image_scaling_index} {
|
||||||
const bool is_unified{profile.unified_descriptor_binding};
|
const bool is_unified{profile.unified_descriptor_binding};
|
||||||
u32& uniform_binding{is_unified ? bindings.unified : bindings.uniform_buffer};
|
u32& uniform_binding{is_unified ? bindings.unified : bindings.uniform_buffer};
|
||||||
u32& storage_binding{is_unified ? bindings.unified : bindings.storage_buffer};
|
u32& storage_binding{is_unified ? bindings.unified : bindings.storage_buffer};
|
||||||
@@ -932,11 +937,163 @@ void EmitContext::DefineWriteStorageCasLoopFunction(const Info& info) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
|
void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
|
||||||
if (!info.uses_global_memory || !profile.support_int64) {
|
if (!info.uses_global_memory) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
using DefPtr = Id StorageDefinitions::*;
|
using DefPtr = Id StorageDefinitions::*;
|
||||||
const Id zero{u32_zero_value};
|
const Id zero{u32_zero_value};
|
||||||
|
|
||||||
|
if (SupportsNativeInt64()) {
|
||||||
|
const auto define_body{[&](DefPtr ssbo_member, Id addr, Id element_pointer, u32 shift,
|
||||||
|
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 Id ssbo_addr_cbuf_offset{Const(ssbo.cbuf_offset / 8)};
|
||||||
|
const Id ssbo_size_cbuf_offset{Const(ssbo.cbuf_offset / 4 + 2)};
|
||||||
|
const Id ssbo_addr_pointer{OpAccessChain(
|
||||||
|
uniform_types.U32x2, cbufs[ssbo.cbuf_index].U32x2, zero,
|
||||||
|
ssbo_addr_cbuf_offset)};
|
||||||
|
const Id ssbo_size_pointer{OpAccessChain(
|
||||||
|
uniform_types.U32, cbufs[ssbo.cbuf_index].U32, zero, ssbo_size_cbuf_offset)};
|
||||||
|
|
||||||
|
const u64 ssbo_align_mask{~(profile.min_ssbo_alignment - 1U)};
|
||||||
|
const Id unaligned_addr{OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer))};
|
||||||
|
const Id ssbo_addr{OpBitwiseAnd(U64, unaligned_addr, Constant(U64, ssbo_align_mask))};
|
||||||
|
const Id ssbo_size{OpUConvert(U64, OpLoad(U32[1], ssbo_size_pointer))};
|
||||||
|
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].*ssbo_member};
|
||||||
|
const Id ssbo_offset{OpUConvert(U32[1], OpISub(U64, addr, ssbo_addr))};
|
||||||
|
const Id ssbo_index{OpShiftRightLogical(U32[1], ssbo_offset, Const(shift))};
|
||||||
|
const Id ssbo_pointer{OpAccessChain(element_pointer, ssbo_id, zero, ssbo_index)};
|
||||||
|
callback(ssbo_pointer);
|
||||||
|
AddLabel(else_label);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
const auto define_load{[&](DefPtr ssbo_member, Id element_pointer, Id type, u32 shift) {
|
||||||
|
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(ssbo_member, addr, element_pointer, shift,
|
||||||
|
[&](Id ssbo_pointer) { OpReturnValue(OpLoad(type, ssbo_pointer)); });
|
||||||
|
OpReturnValue(ConstantNull(type));
|
||||||
|
OpFunctionEnd();
|
||||||
|
return func_id;
|
||||||
|
}};
|
||||||
|
const auto define_write{[&](DefPtr ssbo_member, Id element_pointer, Id type, u32 shift) {
|
||||||
|
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(ssbo_member, addr, element_pointer, shift, [&](Id ssbo_pointer) {
|
||||||
|
OpStore(ssbo_pointer, data);
|
||||||
|
OpReturn();
|
||||||
|
});
|
||||||
|
OpReturn();
|
||||||
|
OpFunctionEnd();
|
||||||
|
return func_id;
|
||||||
|
}};
|
||||||
|
const auto define{
|
||||||
|
[&](DefPtr ssbo_member, const StorageTypeDefinition& type_def, Id type, size_t size) {
|
||||||
|
const Id element_type{type_def.element};
|
||||||
|
const u32 shift{static_cast<u32>(std::countr_zero(size))};
|
||||||
|
const Id load_func{define_load(ssbo_member, element_type, type, shift)};
|
||||||
|
const Id write_func{define_write(ssbo_member, element_type, type, shift)};
|
||||||
|
return std::make_pair(load_func, write_func);
|
||||||
|
}};
|
||||||
|
std::tie(load_global_func_u32, write_global_func_u32) =
|
||||||
|
define(&StorageDefinitions::U32, storage_types.U32, U32[1], sizeof(u32));
|
||||||
|
std::tie(load_global_func_u32x2, write_global_func_u32x2) =
|
||||||
|
define(&StorageDefinitions::U32x2, storage_types.U32x2, U32[2], sizeof(u32[2]));
|
||||||
|
std::tie(load_global_func_u32x4, write_global_func_u32x4) =
|
||||||
|
define(&StorageDefinitions::U32x4, storage_types.U32x4, U32[4], sizeof(u32[4]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UsesInt64Emulation()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto make_pair = [&](Id lo, Id hi) {
|
||||||
|
return OpCompositeConstruct(U32[2], lo, hi);
|
||||||
|
};
|
||||||
|
const auto split_pair = [&](Id value) {
|
||||||
|
return std::array<Id, 2>{OpCompositeExtract(U32[1], value, 0U),
|
||||||
|
OpCompositeExtract(U32[1], value, 1U)};
|
||||||
|
};
|
||||||
|
const auto bool_to_u32 = [&](Id predicate) {
|
||||||
|
return OpSelect(U32[1], predicate, Const(1u), zero);
|
||||||
|
};
|
||||||
|
const auto and_pair = [&](Id value, Id mask) {
|
||||||
|
const auto value_parts{split_pair(value)};
|
||||||
|
const auto mask_parts{split_pair(mask)};
|
||||||
|
return make_pair(OpBitwiseAnd(U32[1], value_parts[0], mask_parts[0]),
|
||||||
|
OpBitwiseAnd(U32[1], value_parts[1], mask_parts[1]));
|
||||||
|
};
|
||||||
|
const auto add_pair = [&](Id lhs, Id rhs) {
|
||||||
|
const auto lhs_parts{split_pair(lhs)};
|
||||||
|
const auto rhs_parts{split_pair(rhs)};
|
||||||
|
const Id sum_lo{OpIAdd(U32[1], lhs_parts[0], rhs_parts[0])};
|
||||||
|
const Id carry{OpULessThan(U1, sum_lo, lhs_parts[0])};
|
||||||
|
Id sum_hi{OpIAdd(U32[1], lhs_parts[1], rhs_parts[1])};
|
||||||
|
sum_hi = OpIAdd(U32[1], sum_hi, bool_to_u32(carry));
|
||||||
|
return make_pair(sum_lo, sum_hi);
|
||||||
|
};
|
||||||
|
const auto sub_pair = [&](Id lhs, Id rhs) {
|
||||||
|
const auto lhs_parts{split_pair(lhs)};
|
||||||
|
const auto rhs_parts{split_pair(rhs)};
|
||||||
|
const Id borrow{OpULessThan(U1, lhs_parts[0], rhs_parts[0])};
|
||||||
|
const Id diff_lo{OpISub(U32[1], lhs_parts[0], rhs_parts[0])};
|
||||||
|
Id diff_hi{OpISub(U32[1], lhs_parts[1], rhs_parts[1])};
|
||||||
|
diff_hi = OpISub(U32[1], diff_hi, bool_to_u32(borrow));
|
||||||
|
return make_pair(diff_lo, diff_hi);
|
||||||
|
};
|
||||||
|
const auto shift_right_pair = [&](Id value, u32 shift) {
|
||||||
|
if (shift == 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
const auto parts{split_pair(value)};
|
||||||
|
const Id shift_id{Const(shift)};
|
||||||
|
const Id high_shifted{OpShiftRightLogical(U32[1], parts[1], shift_id)};
|
||||||
|
Id low_shifted{OpShiftRightLogical(U32[1], parts[0], shift_id)};
|
||||||
|
const Id carry_bits{OpShiftLeftLogical(U32[1], parts[1], Const(32u - shift))};
|
||||||
|
low_shifted = OpBitwiseOr(U32[1], low_shifted, carry_bits);
|
||||||
|
return make_pair(low_shifted, high_shifted);
|
||||||
|
};
|
||||||
|
const auto greater_equal_pair = [&](Id lhs, Id rhs) {
|
||||||
|
const auto lhs_parts{split_pair(lhs)};
|
||||||
|
const auto rhs_parts{split_pair(rhs)};
|
||||||
|
const Id hi_gt{OpUGreaterThan(U1, lhs_parts[1], rhs_parts[1])};
|
||||||
|
const Id hi_eq{OpIEqual(U1, lhs_parts[1], rhs_parts[1])};
|
||||||
|
const Id lo_ge{OpUGreaterThanEqual(U1, lhs_parts[0], rhs_parts[0])};
|
||||||
|
return OpLogicalOr(U1, hi_gt, OpLogicalAnd(U1, hi_eq, lo_ge));
|
||||||
|
};
|
||||||
|
const auto less_than_pair = [&](Id lhs, Id rhs) {
|
||||||
|
const auto lhs_parts{split_pair(lhs)};
|
||||||
|
const auto rhs_parts{split_pair(rhs)};
|
||||||
|
const Id hi_lt{OpULessThan(U1, lhs_parts[1], rhs_parts[1])};
|
||||||
|
const Id hi_eq{OpIEqual(U1, lhs_parts[1], rhs_parts[1])};
|
||||||
|
const Id lo_lt{OpULessThan(U1, lhs_parts[0], rhs_parts[0])};
|
||||||
|
return OpLogicalOr(U1, hi_lt, OpLogicalAnd(U1, hi_eq, lo_lt));
|
||||||
|
};
|
||||||
|
|
||||||
|
const u64 ssbo_align_mask_value{~(profile.min_ssbo_alignment - 1U)};
|
||||||
|
const Id ssbo_align_mask{
|
||||||
|
Const(static_cast<u32>(ssbo_align_mask_value & 0xFFFFFFFFu),
|
||||||
|
static_cast<u32>(ssbo_align_mask_value >> 32))};
|
||||||
|
|
||||||
const auto define_body{[&](DefPtr ssbo_member, Id addr, Id element_pointer, u32 shift,
|
const auto define_body{[&](DefPtr ssbo_member, Id addr, Id element_pointer, u32 shift,
|
||||||
auto&& callback) {
|
auto&& callback) {
|
||||||
AddLabel();
|
AddLabel();
|
||||||
@@ -953,40 +1110,44 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
|
|||||||
const Id ssbo_size_pointer{OpAccessChain(uniform_types.U32, cbufs[ssbo.cbuf_index].U32,
|
const Id ssbo_size_pointer{OpAccessChain(uniform_types.U32, cbufs[ssbo.cbuf_index].U32,
|
||||||
zero, ssbo_size_cbuf_offset)};
|
zero, ssbo_size_cbuf_offset)};
|
||||||
|
|
||||||
const u64 ssbo_align_mask{~(profile.min_ssbo_alignment - 1U)};
|
const Id unaligned_addr_pair{OpLoad(U32[2], ssbo_addr_pointer)};
|
||||||
const Id unaligned_addr{OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer))};
|
const Id ssbo_addr_pair{and_pair(unaligned_addr_pair, ssbo_align_mask)};
|
||||||
const Id ssbo_addr{OpBitwiseAnd(U64, unaligned_addr, Constant(U64, ssbo_align_mask))};
|
const Id ssbo_size_value{OpLoad(U32[1], ssbo_size_pointer)};
|
||||||
const Id ssbo_size{OpUConvert(U64, OpLoad(U32[1], ssbo_size_pointer))};
|
const Id ssbo_size_pair{make_pair(ssbo_size_value, zero)};
|
||||||
const Id ssbo_end{OpIAdd(U64, ssbo_addr, ssbo_size)};
|
const Id ssbo_end_pair{add_pair(ssbo_addr_pair, ssbo_size_pair)};
|
||||||
const Id cond{OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, ssbo_addr),
|
const Id cond{OpLogicalAnd(U1, greater_equal_pair(addr, ssbo_addr_pair),
|
||||||
OpULessThan(U1, addr, ssbo_end))};
|
less_than_pair(addr, ssbo_end_pair))};
|
||||||
const Id then_label{OpLabel()};
|
const Id then_label{OpLabel()};
|
||||||
const Id else_label{OpLabel()};
|
const Id else_label{OpLabel()};
|
||||||
OpSelectionMerge(else_label, spv::SelectionControlMask::MaskNone);
|
OpSelectionMerge(else_label, spv::SelectionControlMask::MaskNone);
|
||||||
OpBranchConditional(cond, then_label, else_label);
|
OpBranchConditional(cond, then_label, else_label);
|
||||||
AddLabel(then_label);
|
AddLabel(then_label);
|
||||||
const Id ssbo_id{ssbos[index].*ssbo_member};
|
const Id ssbo_id{ssbos[index].*ssbo_member};
|
||||||
const Id ssbo_offset{OpUConvert(U32[1], OpISub(U64, addr, ssbo_addr))};
|
const Id ssbo_offset_pair{sub_pair(addr, ssbo_addr_pair)};
|
||||||
const Id ssbo_index{OpShiftRightLogical(U32[1], ssbo_offset, Const(shift))};
|
const Id ssbo_index_pair{shift_right_pair(ssbo_offset_pair, shift)};
|
||||||
|
const Id ssbo_index{OpCompositeExtract(U32[1], ssbo_index_pair, 0U)};
|
||||||
const Id ssbo_pointer{OpAccessChain(element_pointer, ssbo_id, zero, ssbo_index)};
|
const Id ssbo_pointer{OpAccessChain(element_pointer, ssbo_id, zero, ssbo_index)};
|
||||||
callback(ssbo_pointer);
|
callback(ssbo_pointer);
|
||||||
AddLabel(else_label);
|
AddLabel(else_label);
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
|
||||||
const auto define_load{[&](DefPtr ssbo_member, Id element_pointer, Id type, u32 shift) {
|
const auto define_load{[&](DefPtr ssbo_member, Id element_pointer, Id type, u32 shift) {
|
||||||
const Id function_type{TypeFunction(type, U64)};
|
const Id function_type{TypeFunction(type, U32[2])};
|
||||||
const Id func_id{OpFunction(type, spv::FunctionControlMask::MaskNone, function_type)};
|
const Id func_id{OpFunction(type, spv::FunctionControlMask::MaskNone, function_type)};
|
||||||
const Id addr{OpFunctionParameter(U64)};
|
const Id addr{OpFunctionParameter(U32[2])};
|
||||||
define_body(ssbo_member, addr, element_pointer, shift,
|
define_body(ssbo_member, addr, element_pointer, shift,
|
||||||
[&](Id ssbo_pointer) { OpReturnValue(OpLoad(type, ssbo_pointer)); });
|
[&](Id ssbo_pointer) { OpReturnValue(OpLoad(type, ssbo_pointer)); });
|
||||||
OpReturnValue(ConstantNull(type));
|
OpReturnValue(ConstantNull(type));
|
||||||
OpFunctionEnd();
|
OpFunctionEnd();
|
||||||
return func_id;
|
return func_id;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
const auto define_write{[&](DefPtr ssbo_member, Id element_pointer, Id type, u32 shift) {
|
const auto define_write{[&](DefPtr ssbo_member, Id element_pointer, Id type, u32 shift) {
|
||||||
const Id function_type{TypeFunction(void_id, U64, type)};
|
const Id function_type{TypeFunction(void_id, U32[2], type)};
|
||||||
const Id func_id{OpFunction(void_id, spv::FunctionControlMask::MaskNone, function_type)};
|
const Id func_id{
|
||||||
const Id addr{OpFunctionParameter(U64)};
|
OpFunction(void_id, spv::FunctionControlMask::MaskNone, function_type)};
|
||||||
|
const Id addr{OpFunctionParameter(U32[2])};
|
||||||
const Id data{OpFunctionParameter(type)};
|
const Id data{OpFunctionParameter(type)};
|
||||||
define_body(ssbo_member, addr, element_pointer, shift, [&](Id ssbo_pointer) {
|
define_body(ssbo_member, addr, element_pointer, shift, [&](Id ssbo_pointer) {
|
||||||
OpStore(ssbo_pointer, data);
|
OpStore(ssbo_pointer, data);
|
||||||
@@ -996,6 +1157,7 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
|
|||||||
OpFunctionEnd();
|
OpFunctionEnd();
|
||||||
return func_id;
|
return func_id;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
const auto define{
|
const auto define{
|
||||||
[&](DefPtr ssbo_member, const StorageTypeDefinition& type_def, Id type, size_t size) {
|
[&](DefPtr ssbo_member, const StorageTypeDefinition& type_def, Id type, size_t size) {
|
||||||
const Id element_type{type_def.element};
|
const Id element_type{type_def.element};
|
||||||
@@ -1004,6 +1166,7 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
|
|||||||
const Id write_func{define_write(ssbo_member, element_type, type, shift)};
|
const Id write_func{define_write(ssbo_member, element_type, type, shift)};
|
||||||
return std::make_pair(load_func, write_func);
|
return std::make_pair(load_func, write_func);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
std::tie(load_global_func_u32, write_global_func_u32) =
|
std::tie(load_global_func_u32, write_global_func_u32) =
|
||||||
define(&StorageDefinitions::U32, storage_types.U32, U32[1], sizeof(u32));
|
define(&StorageDefinitions::U32, storage_types.U32, U32[1], sizeof(u32));
|
||||||
std::tie(load_global_func_u32x2, write_global_func_u32x2) =
|
std::tie(load_global_func_u32x2, write_global_func_u32x2) =
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -207,6 +210,15 @@ public:
|
|||||||
const Profile& profile;
|
const Profile& profile;
|
||||||
const RuntimeInfo& runtime_info;
|
const RuntimeInfo& runtime_info;
|
||||||
Stage stage{};
|
Stage stage{};
|
||||||
|
const bool emulate_int64{};
|
||||||
|
|
||||||
|
bool SupportsNativeInt64() const {
|
||||||
|
return profile.support_int64;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UsesInt64Emulation() const {
|
||||||
|
return emulate_int64;
|
||||||
|
}
|
||||||
|
|
||||||
Id void_id{};
|
Id void_id{};
|
||||||
Id U1{};
|
Id U1{};
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -293,6 +296,14 @@ std::optional<LowAddrInfo> TrackLowAddress(IR::Inst* inst) {
|
|||||||
}
|
}
|
||||||
// This address is expected to either be a PackUint2x32, a IAdd64, or a CompositeConstructU32x2
|
// This address is expected to either be a PackUint2x32, a IAdd64, or a CompositeConstructU32x2
|
||||||
IR::Inst* addr_inst{addr.InstRecursive()};
|
IR::Inst* addr_inst{addr.InstRecursive()};
|
||||||
|
// Unwrap Identity ops introduced by lowerings (e.g., PackUint2x32 -> Identity)
|
||||||
|
while (addr_inst->GetOpcode() == IR::Opcode::Identity) {
|
||||||
|
const IR::Value id_arg{addr_inst->Arg(0)};
|
||||||
|
if (id_arg.IsImmediate()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
addr_inst = id_arg.InstRecursive();
|
||||||
|
}
|
||||||
s32 imm_offset{0};
|
s32 imm_offset{0};
|
||||||
if (addr_inst->GetOpcode() == IR::Opcode::IAdd64) {
|
if (addr_inst->GetOpcode() == IR::Opcode::IAdd64) {
|
||||||
// If it's an IAdd64, get the immediate offset it is applying and grab the address
|
// If it's an IAdd64, get the immediate offset it is applying and grab the address
|
||||||
@@ -308,6 +319,14 @@ std::optional<LowAddrInfo> TrackLowAddress(IR::Inst* inst) {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
addr_inst = iadd_addr.InstRecursive();
|
addr_inst = iadd_addr.InstRecursive();
|
||||||
|
// Unwrap Identity again if present after folding IAdd64
|
||||||
|
while (addr_inst->GetOpcode() == IR::Opcode::Identity) {
|
||||||
|
const IR::Value id_arg{addr_inst->Arg(0)};
|
||||||
|
if (id_arg.IsImmediate()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
addr_inst = id_arg.InstRecursive();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// With IAdd64 handled, now PackUint2x32 is expected
|
// With IAdd64 handled, now PackUint2x32 is expected
|
||||||
if (addr_inst->GetOpcode() == IR::Opcode::PackUint2x32) {
|
if (addr_inst->GetOpcode() == IR::Opcode::PackUint2x32) {
|
||||||
@@ -317,6 +336,14 @@ std::optional<LowAddrInfo> TrackLowAddress(IR::Inst* inst) {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
addr_inst = vector.InstRecursive();
|
addr_inst = vector.InstRecursive();
|
||||||
|
// Unwrap Identity that may replace PackUint2x32
|
||||||
|
while (addr_inst->GetOpcode() == IR::Opcode::Identity) {
|
||||||
|
const IR::Value id_arg{addr_inst->Arg(0)};
|
||||||
|
if (id_arg.IsImmediate()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
addr_inst = id_arg.InstRecursive();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// The vector is expected to be a CompositeConstructU32x2
|
// The vector is expected to be a CompositeConstructU32x2
|
||||||
if (addr_inst->GetOpcode() != IR::Opcode::CompositeConstructU32x2) {
|
if (addr_inst->GetOpcode() != IR::Opcode::CompositeConstructU32x2) {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -38,6 +41,9 @@ struct Profile {
|
|||||||
bool support_gl_nv_gpu_shader_5{};
|
bool support_gl_nv_gpu_shader_5{};
|
||||||
bool support_gl_amd_gpu_shader_half_float{};
|
bool support_gl_amd_gpu_shader_half_float{};
|
||||||
bool support_gl_texture_shadow_lod{};
|
bool support_gl_texture_shadow_lod{};
|
||||||
|
bool support_gl_shader_atomic_float{};
|
||||||
|
bool support_gl_shader_atomic_fp16_vector{};
|
||||||
|
bool support_gl_shader_atomic_int64{};
|
||||||
bool support_gl_warp_intrinsics{};
|
bool support_gl_warp_intrinsics{};
|
||||||
bool support_gl_variable_aoffi{};
|
bool support_gl_variable_aoffi{};
|
||||||
bool support_gl_sparse_textures{};
|
bool support_gl_sparse_textures{};
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ set(SHADER_FILES
|
|||||||
vulkan_quad_indexed.comp
|
vulkan_quad_indexed.comp
|
||||||
vulkan_turbo_mode.comp
|
vulkan_turbo_mode.comp
|
||||||
vulkan_uint8.comp
|
vulkan_uint8.comp
|
||||||
|
vulkan_qcom_msaa_resolve.frag
|
||||||
convert_rgba8_to_bgra8.frag
|
convert_rgba8_to_bgra8.frag
|
||||||
convert_yuv420_to_rgb.comp
|
convert_yuv420_to_rgb.comp
|
||||||
convert_rgb_to_yuv420.comp
|
convert_rgb_to_yuv420.comp
|
||||||
|
|||||||
39
src/video_core/host_shaders/vulkan_qcom_msaa_resolve.frag
Normal file
39
src/video_core/host_shaders/vulkan_qcom_msaa_resolve.frag
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
// VK_QCOM_render_pass_shader_resolve fragment shader
|
||||||
|
// Resolves MSAA attachment to single-sample within render pass
|
||||||
|
// Requires VK_SUBPASS_DESCRIPTION_SHADER_RESOLVE_BIT_QCOM in subpass flags
|
||||||
|
|
||||||
|
// Use combined image sampler for MSAA texture instead of input attachment
|
||||||
|
// This allows us to sample MSAA textures from previous rendering
|
||||||
|
layout(set = 0, binding = 0) uniform sampler2DMS msaa_texture;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 color_output;
|
||||||
|
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
vec2 tex_scale;
|
||||||
|
vec2 tex_offset;
|
||||||
|
} push_constants;
|
||||||
|
|
||||||
|
// Custom MSAA resolve using box filter (simple average)
|
||||||
|
// Assumes 4x MSAA (can be extended with push constant for dynamic sample count)
|
||||||
|
void main() {
|
||||||
|
ivec2 coord = ivec2(gl_FragCoord.xy);
|
||||||
|
ivec2 tex_size = textureSize(msaa_texture);
|
||||||
|
|
||||||
|
// Clamp coordinates to texture bounds
|
||||||
|
coord = clamp(coord, ivec2(0), tex_size - ivec2(1));
|
||||||
|
|
||||||
|
vec4 accumulated_color = vec4(0.0);
|
||||||
|
int sample_count = 4; // Adreno typically uses 4x MSAA max
|
||||||
|
|
||||||
|
// Box filter: simple average of all MSAA samples
|
||||||
|
for (int i = 0; i < sample_count; i++) {
|
||||||
|
accumulated_color += texelFetch(msaa_texture, coord, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
color_output = accumulated_color / float(sample_count);
|
||||||
|
}
|
||||||
@@ -225,6 +225,9 @@ Device::Device(Core::Frontend::EmuWindow& emu_window) {
|
|||||||
has_amd_shader_half_float = GLAD_GL_AMD_gpu_shader_half_float;
|
has_amd_shader_half_float = GLAD_GL_AMD_gpu_shader_half_float;
|
||||||
has_sparse_texture_2 = GLAD_GL_ARB_sparse_texture2;
|
has_sparse_texture_2 = GLAD_GL_ARB_sparse_texture2;
|
||||||
has_draw_texture = GLAD_GL_NV_draw_texture;
|
has_draw_texture = GLAD_GL_NV_draw_texture;
|
||||||
|
has_shader_atomic_float = GLAD_GL_NV_shader_atomic_float;
|
||||||
|
has_shader_atomic_fp16_vector = GLAD_GL_NV_shader_atomic_fp16_vector;
|
||||||
|
has_shader_atomic_int64 = GLAD_GL_NV_shader_atomic_int64;
|
||||||
warp_size_potentially_larger_than_guest = !is_nvidia && !is_intel;
|
warp_size_potentially_larger_than_guest = !is_nvidia && !is_intel;
|
||||||
need_fastmath_off = is_nvidia;
|
need_fastmath_off = is_nvidia;
|
||||||
can_report_memory = GLAD_GL_NVX_gpu_memory_info;
|
can_report_memory = GLAD_GL_NVX_gpu_memory_info;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -152,6 +155,18 @@ public:
|
|||||||
return has_draw_texture;
|
return has_draw_texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HasShaderAtomicFloat() const {
|
||||||
|
return has_shader_atomic_float;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasShaderAtomicFp16Vector() const {
|
||||||
|
return has_shader_atomic_fp16_vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasShaderAtomicInt64() const {
|
||||||
|
return has_shader_atomic_int64;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsWarpSizePotentiallyLargerThanGuest() const {
|
bool IsWarpSizePotentiallyLargerThanGuest() const {
|
||||||
return warp_size_potentially_larger_than_guest;
|
return warp_size_potentially_larger_than_guest;
|
||||||
}
|
}
|
||||||
@@ -235,6 +250,9 @@ private:
|
|||||||
bool has_amd_shader_half_float{};
|
bool has_amd_shader_half_float{};
|
||||||
bool has_sparse_texture_2{};
|
bool has_sparse_texture_2{};
|
||||||
bool has_draw_texture{};
|
bool has_draw_texture{};
|
||||||
|
bool has_shader_atomic_float{};
|
||||||
|
bool has_shader_atomic_fp16_vector{};
|
||||||
|
bool has_shader_atomic_int64{};
|
||||||
bool warp_size_potentially_larger_than_guest{};
|
bool warp_size_potentially_larger_than_guest{};
|
||||||
bool need_fastmath_off{};
|
bool need_fastmath_off{};
|
||||||
bool has_cbuf_ftou_bug{};
|
bool has_cbuf_ftou_bug{};
|
||||||
|
|||||||
@@ -215,6 +215,9 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
|||||||
.support_gl_nv_gpu_shader_5 = device.HasNvGpuShader5(),
|
.support_gl_nv_gpu_shader_5 = device.HasNvGpuShader5(),
|
||||||
.support_gl_amd_gpu_shader_half_float = device.HasAmdShaderHalfFloat(),
|
.support_gl_amd_gpu_shader_half_float = device.HasAmdShaderHalfFloat(),
|
||||||
.support_gl_texture_shadow_lod = device.HasTextureShadowLod(),
|
.support_gl_texture_shadow_lod = device.HasTextureShadowLod(),
|
||||||
|
.support_gl_shader_atomic_float = device.HasShaderAtomicFloat(),
|
||||||
|
.support_gl_shader_atomic_fp16_vector = device.HasShaderAtomicFp16Vector(),
|
||||||
|
.support_gl_shader_atomic_int64 = device.HasShaderAtomicInt64(),
|
||||||
.support_gl_warp_intrinsics = false,
|
.support_gl_warp_intrinsics = false,
|
||||||
.support_gl_variable_aoffi = device.HasVariableAoffi(),
|
.support_gl_variable_aoffi = device.HasVariableAoffi(),
|
||||||
.support_gl_sparse_textures = device.HasSparseTexture2(),
|
.support_gl_sparse_textures = device.HasSparseTexture2(),
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -97,6 +100,10 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CanDownloadMSAA() const noexcept {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void CopyImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies);
|
void CopyImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies);
|
||||||
|
|
||||||
void CopyImageMSAA(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies);
|
void CopyImageMSAA(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies);
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
#include "video_core/host_shaders/convert_rgba16f_to_rgba8_frag_spv.h"
|
#include "video_core/host_shaders/convert_rgba16f_to_rgba8_frag_spv.h"
|
||||||
#include "video_core/host_shaders/dither_temporal_frag_spv.h"
|
#include "video_core/host_shaders/dither_temporal_frag_spv.h"
|
||||||
#include "video_core/host_shaders/dynamic_resolution_scale_comp_spv.h"
|
#include "video_core/host_shaders/dynamic_resolution_scale_comp_spv.h"
|
||||||
|
#include "video_core/host_shaders/vulkan_qcom_msaa_resolve_frag_spv.h"
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
@@ -545,6 +546,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
|
|||||||
convert_rgba16f_to_rgba8_frag(BuildShader(device, CONVERT_RGBA16F_TO_RGBA8_FRAG_SPV)),
|
convert_rgba16f_to_rgba8_frag(BuildShader(device, CONVERT_RGBA16F_TO_RGBA8_FRAG_SPV)),
|
||||||
dither_temporal_frag(BuildShader(device, DITHER_TEMPORAL_FRAG_SPV)),
|
dither_temporal_frag(BuildShader(device, DITHER_TEMPORAL_FRAG_SPV)),
|
||||||
dynamic_resolution_scale_comp(BuildShader(device, DYNAMIC_RESOLUTION_SCALE_COMP_SPV)),
|
dynamic_resolution_scale_comp(BuildShader(device, DYNAMIC_RESOLUTION_SCALE_COMP_SPV)),
|
||||||
|
qcom_msaa_resolve_frag(BuildShader(device, VULKAN_QCOM_MSAA_RESOLVE_FRAG_SPV)),
|
||||||
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
|
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
|
||||||
nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
|
nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
|
||||||
|
|
||||||
@@ -1240,4 +1242,30 @@ void BlitImageHelper::ApplyDynamicResolutionScale(const Framebuffer* dst_framebu
|
|||||||
Convert(*dynamic_resolution_scale_pipeline, dst_framebuffer, src_image_view);
|
Convert(*dynamic_resolution_scale_pipeline, dst_framebuffer, src_image_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlitImageHelper::ResolveMSAAQcom(const Framebuffer* dst_framebuffer,
|
||||||
|
const ImageView& src_image_view) {
|
||||||
|
// VK_QCOM_render_pass_shader_resolve implementation
|
||||||
|
// This must be used within a render pass with VK_SUBPASS_DESCRIPTION_SHADER_RESOLVE_BIT_QCOM
|
||||||
|
ConvertPipeline(qcom_msaa_resolve_pipeline,
|
||||||
|
dst_framebuffer->RenderPass(),
|
||||||
|
false);
|
||||||
|
|
||||||
|
RecordShaderReadBarrier(scheduler, src_image_view);
|
||||||
|
scheduler.RequestRenderpass(dst_framebuffer);
|
||||||
|
|
||||||
|
const VkImageView src_view = src_image_view.Handle(Shader::TextureType::Color2D);
|
||||||
|
const VkPipelineLayout layout = *one_texture_pipeline_layout;
|
||||||
|
const VkPipeline pipeline = *qcom_msaa_resolve_pipeline;
|
||||||
|
|
||||||
|
scheduler.Record([this, src_view, layout, pipeline](vk::CommandBuffer cmdbuf) {
|
||||||
|
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
|
||||||
|
UpdateOneTextureDescriptorSet(device, descriptor_set, *nearest_sampler, src_view);
|
||||||
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, nullptr);
|
||||||
|
cmdbuf.Draw(3, 1, 0, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
scheduler.InvalidateState();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ public:
|
|||||||
void ConvertRGBA16FtoRGBA8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
void ConvertRGBA16FtoRGBA8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||||
void ApplyDitherTemporal(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
void ApplyDitherTemporal(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||||
void ApplyDynamicResolutionScale(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
void ApplyDynamicResolutionScale(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||||
|
|
||||||
|
void ResolveMSAAQcom(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
|
void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
|
||||||
@@ -159,6 +161,7 @@ private:
|
|||||||
vk::ShaderModule convert_rgba16f_to_rgba8_frag;
|
vk::ShaderModule convert_rgba16f_to_rgba8_frag;
|
||||||
vk::ShaderModule dither_temporal_frag;
|
vk::ShaderModule dither_temporal_frag;
|
||||||
vk::ShaderModule dynamic_resolution_scale_comp;
|
vk::ShaderModule dynamic_resolution_scale_comp;
|
||||||
|
vk::ShaderModule qcom_msaa_resolve_frag;
|
||||||
vk::Sampler linear_sampler;
|
vk::Sampler linear_sampler;
|
||||||
vk::Sampler nearest_sampler;
|
vk::Sampler nearest_sampler;
|
||||||
|
|
||||||
@@ -188,6 +191,7 @@ private:
|
|||||||
vk::Pipeline convert_rgba16f_to_rgba8_pipeline;
|
vk::Pipeline convert_rgba16f_to_rgba8_pipeline;
|
||||||
vk::Pipeline dither_temporal_pipeline;
|
vk::Pipeline dither_temporal_pipeline;
|
||||||
vk::Pipeline dynamic_resolution_scale_pipeline;
|
vk::Pipeline dynamic_resolution_scale_pipeline;
|
||||||
|
vk::Pipeline qcom_msaa_resolve_pipeline;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ struct FormatTuple {
|
|||||||
{VK_FORMAT_ASTC_8x6_SRGB_BLOCK}, // ASTC_2D_8X6_SRGB
|
{VK_FORMAT_ASTC_8x6_SRGB_BLOCK}, // ASTC_2D_8X6_SRGB
|
||||||
{VK_FORMAT_ASTC_6x5_UNORM_BLOCK}, // ASTC_2D_6X5_UNORM
|
{VK_FORMAT_ASTC_6x5_UNORM_BLOCK}, // ASTC_2D_6X5_UNORM
|
||||||
{VK_FORMAT_ASTC_6x5_SRGB_BLOCK}, // ASTC_2D_6X5_SRGB
|
{VK_FORMAT_ASTC_6x5_SRGB_BLOCK}, // ASTC_2D_6X5_SRGB
|
||||||
{VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT
|
{VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, Attachable | Storage}, // E5B9G9R9_FLOAT
|
||||||
|
|
||||||
// Depth formats
|
// Depth formats
|
||||||
{VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
|
{VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include <ranges>
|
|
||||||
#include "video_core/renderer_vulkan/present/util.h"
|
#include "video_core/renderer_vulkan/present/util.h"
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|||||||
@@ -101,8 +101,15 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
|
|||||||
};
|
};
|
||||||
descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples));
|
descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples));
|
||||||
}
|
}
|
||||||
|
VkSubpassDescriptionFlags subpass_flags = 0;
|
||||||
|
if (key.qcom_shader_resolve) {
|
||||||
|
// VK_QCOM_render_pass_shader_resolve: enables custom shader resolve in fragment shader
|
||||||
|
// This must be the last subpass in the dependency chain
|
||||||
|
subpass_flags |= 0x00000004; // VK_SUBPASS_DESCRIPTION_SHADER_RESOLVE_BIT_QCOM
|
||||||
|
}
|
||||||
|
|
||||||
const VkSubpassDescription subpass{
|
const VkSubpassDescription subpass{
|
||||||
.flags = 0,
|
.flags = subpass_flags,
|
||||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
.inputAttachmentCount = 0,
|
.inputAttachmentCount = 0,
|
||||||
.pInputAttachments = nullptr,
|
.pInputAttachments = nullptr,
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -17,6 +20,14 @@ struct RenderPassKey {
|
|||||||
std::array<VideoCore::Surface::PixelFormat, 8> color_formats;
|
std::array<VideoCore::Surface::PixelFormat, 8> color_formats;
|
||||||
VideoCore::Surface::PixelFormat depth_format;
|
VideoCore::Surface::PixelFormat depth_format;
|
||||||
VkSampleCountFlagBits samples;
|
VkSampleCountFlagBits samples;
|
||||||
|
|
||||||
|
// TBDR optimization hints - only affect tile-based GPUs (Qualcomm, ARM, Imagination)
|
||||||
|
// These flags indicate the expected usage pattern to optimize load/store operations
|
||||||
|
bool tbdr_will_clear{false}; // Attachment will be cleared with vkCmdClearAttachments
|
||||||
|
bool tbdr_discard_after{false}; // Attachment won't be read after render pass
|
||||||
|
|
||||||
|
// VK_QCOM_render_pass_shader_resolve support
|
||||||
|
bool qcom_shader_resolve{false}; // Use shader resolve instead of fixed-function (last subpass)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -171,6 +171,10 @@ void Swapchain::Create(
|
|||||||
|
|
||||||
resource_ticks.clear();
|
resource_ticks.clear();
|
||||||
resource_ticks.resize(image_count);
|
resource_ticks.resize(image_count);
|
||||||
|
|
||||||
|
// Initialize incremental-present probe flags for this swapchain.
|
||||||
|
incremental_present_usable = device.IsKhrIncrementalPresentSupported();
|
||||||
|
incremental_present_probed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Swapchain::AcquireNextImage() {
|
bool Swapchain::AcquireNextImage() {
|
||||||
@@ -202,7 +206,13 @@ bool Swapchain::AcquireNextImage() {
|
|||||||
|
|
||||||
void Swapchain::Present(VkSemaphore render_semaphore) {
|
void Swapchain::Present(VkSemaphore render_semaphore) {
|
||||||
const auto present_queue{device.GetPresentQueue()};
|
const auto present_queue{device.GetPresentQueue()};
|
||||||
const VkPresentInfoKHR present_info{
|
// If the device advertises VK_KHR_incremental_present, we attempt a one-time probe
|
||||||
|
// on the first present to validate the driver/compositor accepts present-region info.
|
||||||
|
VkPresentRegionsKHR present_regions{};
|
||||||
|
VkPresentRegionKHR region{};
|
||||||
|
VkRectLayerKHR layer{};
|
||||||
|
|
||||||
|
VkPresentInfoKHR present_info{
|
||||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.waitSemaphoreCount = render_semaphore ? 1U : 0U,
|
.waitSemaphoreCount = render_semaphore ? 1U : 0U,
|
||||||
@@ -212,6 +222,20 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
|
|||||||
.pImageIndices = &image_index,
|
.pImageIndices = &image_index,
|
||||||
.pResults = nullptr,
|
.pResults = nullptr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (incremental_present_usable && !incremental_present_probed) {
|
||||||
|
// Build a minimal present-region describing a single 1x1 dirty rect at (0,0).
|
||||||
|
layer.offset = {0, 0};
|
||||||
|
layer.extent = {1, 1};
|
||||||
|
region.rectangleCount = 1;
|
||||||
|
region.pRectangles = &layer;
|
||||||
|
present_regions.sType = VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR;
|
||||||
|
present_regions.pNext = nullptr;
|
||||||
|
present_regions.swapchainCount = 1;
|
||||||
|
present_regions.pRegions = ®ion;
|
||||||
|
|
||||||
|
present_info.pNext = &present_regions;
|
||||||
|
}
|
||||||
std::scoped_lock lock{scheduler.submit_mutex};
|
std::scoped_lock lock{scheduler.submit_mutex};
|
||||||
switch (const VkResult result = present_queue.Present(present_info)) {
|
switch (const VkResult result = present_queue.Present(present_info)) {
|
||||||
case VK_SUCCESS:
|
case VK_SUCCESS:
|
||||||
@@ -227,8 +251,18 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_CRITICAL(Render_Vulkan, "Failed to present with error {}", string_VkResult(result));
|
LOG_CRITICAL(Render_Vulkan, "Failed to present with error {}", string_VkResult(result));
|
||||||
|
// If the first present with incremental-present pNext failed, disable future use.
|
||||||
|
if (incremental_present_usable && !incremental_present_probed) {
|
||||||
|
incremental_present_usable = false;
|
||||||
|
LOG_WARNING(Render_Vulkan, "Disabling VK_KHR_incremental_present for this swapchain due to present failure: {}", string_VkResult(result));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (incremental_present_usable && !incremental_present_probed) {
|
||||||
|
// Mark probe as completed if we reached here (success or handled failure above).
|
||||||
|
incremental_present_probed = true;
|
||||||
|
LOG_INFO(Render_Vulkan, "VK_KHR_incremental_present probe completed: usable={}", incremental_present_usable);
|
||||||
|
}
|
||||||
++frame_index;
|
++frame_index;
|
||||||
if (frame_index >= image_count) {
|
if (frame_index >= image_count) {
|
||||||
frame_index = 0;
|
frame_index = 0;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -158,6 +161,8 @@ private:
|
|||||||
|
|
||||||
bool is_outdated{};
|
bool is_outdated{};
|
||||||
bool is_suboptimal{};
|
bool is_suboptimal{};
|
||||||
|
bool incremental_present_usable{};
|
||||||
|
bool incremental_present_probed{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -857,6 +857,9 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
|
|||||||
astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
|
astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
|
||||||
compute_pass_descriptor_queue, memory_allocator);
|
compute_pass_descriptor_queue, memory_allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MSAA copy support via compute shader (only for non-Qualcomm with shaderStorageImageMultisample)
|
||||||
|
// Qualcomm uses VK_QCOM_render_pass_shader_resolve (fragment shader in render pass)
|
||||||
if (device.IsStorageImageMultisampleSupported()) {
|
if (device.IsStorageImageMultisampleSupported()) {
|
||||||
msaa_copy_pass = std::make_unique<MSAACopyPass>(
|
msaa_copy_pass = std::make_unique<MSAACopyPass>(
|
||||||
device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue);
|
device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue);
|
||||||
@@ -1549,6 +1552,24 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
|
|||||||
MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32);
|
MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Proactive warning for problematic HDR format + MSAA combinations on Android
|
||||||
|
// These combinations commonly cause texture flickering/black screens across multiple game engines
|
||||||
|
// Note: MSAA is native Switch rendering technique, cannot be disabled by emulator
|
||||||
|
if (info.num_samples > 1) {
|
||||||
|
const auto vk_format = MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal,
|
||||||
|
false, info.format).format;
|
||||||
|
const bool is_hdr_format = vk_format == VK_FORMAT_B10G11R11_UFLOAT_PACK32 ||
|
||||||
|
vk_format == VK_FORMAT_E5B9G9R9_UFLOAT_PACK32;
|
||||||
|
|
||||||
|
if (is_hdr_format) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Creating MSAA image ({}x samples) with HDR format {} (Maxwell: {}). "
|
||||||
|
"Driver support may be limited on Android (Qualcomm < 800, Mali pre-maintenance5). "
|
||||||
|
"Format fallback to RGBA16F should prevent issues.",
|
||||||
|
info.num_samples, vk_format, info.format);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBase{params} {}
|
Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBase{params} {}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
@@ -85,6 +88,10 @@ public:
|
|||||||
return msaa_copy_pass.operator bool();
|
return msaa_copy_pass.operator bool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CanDownloadMSAA() const noexcept {
|
||||||
|
return msaa_copy_pass.operator bool();
|
||||||
|
}
|
||||||
|
|
||||||
void AccelerateImageUpload(Image&, const StagingBufferRef&,
|
void AccelerateImageUpload(Image&, const StagingBufferRef&,
|
||||||
std::span<const VideoCommon::SwizzleParameters>);
|
std::span<const VideoCommon::SwizzleParameters>);
|
||||||
|
|
||||||
|
|||||||
@@ -131,10 +131,6 @@ bool ImageBase::IsSafeDownload() const noexcept {
|
|||||||
if (True(flags & ImageFlagBits::CpuModified)) {
|
if (True(flags & ImageFlagBits::CpuModified)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (info.num_samples > 1) {
|
|
||||||
LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,8 +101,12 @@ void TextureCache<P>::RunGarbageCollector() {
|
|||||||
if (!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) {
|
if (!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const bool must_download =
|
const bool supports_msaa_download = HasMsaaDownloadSupport(image.info);
|
||||||
image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
|
if (!supports_msaa_download && image.info.num_samples > 1) {
|
||||||
|
LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented");
|
||||||
|
}
|
||||||
|
const bool must_download = supports_msaa_download && image.IsSafeDownload() &&
|
||||||
|
False(image.flags & ImageFlagBits::BadOverlap);
|
||||||
if (!high_priority_mode && must_download) {
|
if (!high_priority_mode && must_download) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -548,10 +552,14 @@ void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) {
|
|||||||
template <class P>
|
template <class P>
|
||||||
void TextureCache<P>::DownloadMemory(DAddr cpu_addr, size_t size) {
|
void TextureCache<P>::DownloadMemory(DAddr cpu_addr, size_t size) {
|
||||||
boost::container::small_vector<ImageId, 16> images;
|
boost::container::small_vector<ImageId, 16> images;
|
||||||
ForEachImageInRegion(cpu_addr, size, [&images](ImageId image_id, ImageBase& image) {
|
ForEachImageInRegion(cpu_addr, size, [this, &images](ImageId image_id, ImageBase& image) {
|
||||||
if (!image.IsSafeDownload()) {
|
if (!image.IsSafeDownload()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!HasMsaaDownloadSupport(image.info)) {
|
||||||
|
LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented");
|
||||||
|
return;
|
||||||
|
}
|
||||||
image.flags &= ~ImageFlagBits::GpuModified;
|
image.flags &= ~ImageFlagBits::GpuModified;
|
||||||
images.push_back(image_id);
|
images.push_back(image_id);
|
||||||
});
|
});
|
||||||
@@ -930,6 +938,17 @@ ImageId TextureCache<P>::DmaImageId(const Tegra::DMA::ImageOperand& operand, boo
|
|||||||
return NULL_IMAGE_ID;
|
return NULL_IMAGE_ID;
|
||||||
}
|
}
|
||||||
auto& image = slot_images[dst_id];
|
auto& image = slot_images[dst_id];
|
||||||
|
if (image.info.num_samples > 1) {
|
||||||
|
if (is_upload) {
|
||||||
|
if (!HasMsaaUploadSupport(image.info)) {
|
||||||
|
return NULL_IMAGE_ID;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!HasMsaaDownloadSupport(image.info)) {
|
||||||
|
return NULL_IMAGE_ID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (False(image.flags & ImageFlagBits::GpuModified)) {
|
if (False(image.flags & ImageFlagBits::GpuModified)) {
|
||||||
// No need to waste time on an image that's synced with guest
|
// No need to waste time on an image that's synced with guest
|
||||||
return NULL_IMAGE_ID;
|
return NULL_IMAGE_ID;
|
||||||
@@ -1056,7 +1075,7 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
|
|||||||
image.flags &= ~ImageFlagBits::CpuModified;
|
image.flags &= ~ImageFlagBits::CpuModified;
|
||||||
TrackImage(image, image_id);
|
TrackImage(image, image_id);
|
||||||
|
|
||||||
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
|
if (!HasMsaaUploadSupport(image.info)) {
|
||||||
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
|
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
|
||||||
runtime.TransitionImageLayout(image);
|
runtime.TransitionImageLayout(image);
|
||||||
return;
|
return;
|
||||||
@@ -1274,6 +1293,16 @@ u64 TextureCache<P>::GetScaledImageSizeBytes(const ImageBase& image) {
|
|||||||
return fitted_size;
|
return fitted_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
bool TextureCache<P>::HasMsaaUploadSupport(const ImageInfo& info) const noexcept {
|
||||||
|
return info.num_samples <= 1 || runtime.CanUploadMSAA();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
bool TextureCache<P>::HasMsaaDownloadSupport(const ImageInfo& info) const noexcept {
|
||||||
|
return info.num_samples <= 1 || runtime.CanDownloadMSAA();
|
||||||
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) {
|
void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) {
|
||||||
UNIMPLEMENTED_IF(False(image.flags & ImageFlagBits::Converted));
|
UNIMPLEMENTED_IF(False(image.flags & ImageFlagBits::Converted));
|
||||||
@@ -1491,7 +1520,31 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA
|
|||||||
for (const ImageId overlap_id : join_ignore_textures) {
|
for (const ImageId overlap_id : join_ignore_textures) {
|
||||||
Image& overlap = slot_images[overlap_id];
|
Image& overlap = slot_images[overlap_id];
|
||||||
if (True(overlap.flags & ImageFlagBits::GpuModified)) {
|
if (True(overlap.flags & ImageFlagBits::GpuModified)) {
|
||||||
UNIMPLEMENTED();
|
// Merge GPU-modified contents from the overlapping image into the newly
|
||||||
|
// created image to preserve guest-visible data. Compute shrink/scale
|
||||||
|
// copies and dispatch a GPU-side copy. This mirrors the behavior used
|
||||||
|
// for overlaps handled in join_copies_to_do above.
|
||||||
|
new_image.flags |= ImageFlagBits::GpuModified;
|
||||||
|
const auto& resolution = Settings::values.resolution_info;
|
||||||
|
const auto base_opt = new_image.TryFindBase(overlap.gpu_addr);
|
||||||
|
if (base_opt) {
|
||||||
|
const SubresourceBase base = base_opt.value();
|
||||||
|
const u32 up_scale = can_rescale ? resolution.up_scale : 1;
|
||||||
|
const u32 down_shift = can_rescale ? resolution.down_shift : 0;
|
||||||
|
auto copies = MakeShrinkImageCopies(new_info, overlap.info, base, up_scale, down_shift);
|
||||||
|
if (overlap.info.num_samples != new_image.info.num_samples) {
|
||||||
|
runtime.CopyImageMSAA(new_image, overlap, FixSmallVectorADL(copies));
|
||||||
|
} else {
|
||||||
|
runtime.CopyImage(new_image, overlap, FixSmallVectorADL(copies));
|
||||||
|
}
|
||||||
|
new_image.modification_tick = overlap.modification_tick;
|
||||||
|
} else {
|
||||||
|
// If we cannot determine a base mapping, fallback to preserving the
|
||||||
|
// overlap (avoid deleting GPU-modified data) and log the event so
|
||||||
|
// it can be investigated, we're trying to pinpoint the issue of texture flickering.
|
||||||
|
LOG_WARNING(HW_GPU, "Could not map overlap gpu_addr {:#x} into new image; preserving overlap", u64(overlap.gpu_addr));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (True(overlap.flags & ImageFlagBits::Tracked)) {
|
if (True(overlap.flags & ImageFlagBits::Tracked)) {
|
||||||
UntrackImage(overlap, overlap_id);
|
UntrackImage(overlap, overlap_id);
|
||||||
@@ -1551,6 +1604,10 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA
|
|||||||
for (const auto& copy_object : join_copies_to_do) {
|
for (const auto& copy_object : join_copies_to_do) {
|
||||||
Image& overlap = slot_images[copy_object.id];
|
Image& overlap = slot_images[copy_object.id];
|
||||||
if (copy_object.is_alias) {
|
if (copy_object.is_alias) {
|
||||||
|
if (!HasMsaaDownloadSupport(overlap.info)) {
|
||||||
|
LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!overlap.IsSafeDownload()) {
|
if (!overlap.IsSafeDownload()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2467,8 +2524,13 @@ void TextureCache<P>::BindRenderTarget(ImageViewId* old_id, ImageViewId new_id)
|
|||||||
if (new_id) {
|
if (new_id) {
|
||||||
const ImageViewBase& old_view = slot_image_views[new_id];
|
const ImageViewBase& old_view = slot_image_views[new_id];
|
||||||
if (True(old_view.flags & ImageViewFlagBits::PreemtiveDownload)) {
|
if (True(old_view.flags & ImageViewFlagBits::PreemtiveDownload)) {
|
||||||
const PendingDownload new_download{true, 0, old_view.image_id};
|
const ImageBase& image = slot_images[old_view.image_id];
|
||||||
uncommitted_downloads.emplace_back(new_download);
|
if (!HasMsaaDownloadSupport(image.info)) {
|
||||||
|
LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented");
|
||||||
|
} else {
|
||||||
|
const PendingDownload new_download{true, 0, old_view.image_id};
|
||||||
|
uncommitted_downloads.emplace_back(new_download);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*old_id = new_id;
|
*old_id = new_id;
|
||||||
|
|||||||
@@ -426,6 +426,8 @@ private:
|
|||||||
bool ScaleUp(Image& image);
|
bool ScaleUp(Image& image);
|
||||||
bool ScaleDown(Image& image);
|
bool ScaleDown(Image& image);
|
||||||
u64 GetScaledImageSizeBytes(const ImageBase& image);
|
u64 GetScaledImageSizeBytes(const ImageBase& image);
|
||||||
|
[[nodiscard]] bool HasMsaaUploadSupport(const ImageInfo& info) const noexcept;
|
||||||
|
[[nodiscard]] bool HasMsaaDownloadSupport(const ImageInfo& info) const noexcept;
|
||||||
|
|
||||||
void QueueAsyncDecode(Image& image, ImageId image_id);
|
void QueueAsyncDecode(Image& image, ImageId image_id);
|
||||||
void TickAsyncDecode();
|
void TickAsyncDecode();
|
||||||
|
|||||||
@@ -22,6 +22,34 @@
|
|||||||
|
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#ifndef VK_KHR_MAINTENANCE_1_EXTENSION_NAME
|
||||||
|
# define VK_KHR_MAINTENANCE_1_EXTENSION_NAME "VK_KHR_maintenance1"
|
||||||
|
#endif
|
||||||
|
#ifndef VK_KHR_MAINTENANCE_2_EXTENSION_NAME
|
||||||
|
# define VK_KHR_MAINTENANCE_2_EXTENSION_NAME "VK_KHR_maintenance2"
|
||||||
|
#endif
|
||||||
|
#ifndef VK_KHR_MAINTENANCE_3_EXTENSION_NAME
|
||||||
|
# define VK_KHR_MAINTENANCE_3_EXTENSION_NAME "VK_KHR_maintenance3"
|
||||||
|
#endif
|
||||||
|
#ifndef VK_KHR_MAINTENANCE_4_EXTENSION_NAME
|
||||||
|
# define VK_KHR_MAINTENANCE_4_EXTENSION_NAME "VK_KHR_maintenance4"
|
||||||
|
#endif
|
||||||
|
#ifndef VK_KHR_MAINTENANCE_5_EXTENSION_NAME
|
||||||
|
# define VK_KHR_MAINTENANCE_5_EXTENSION_NAME "VK_KHR_maintenance5"
|
||||||
|
#endif
|
||||||
|
#ifndef VK_KHR_MAINTENANCE_6_EXTENSION_NAME
|
||||||
|
# define VK_KHR_MAINTENANCE_6_EXTENSION_NAME "VK_KHR_maintenance6"
|
||||||
|
#endif
|
||||||
|
#ifndef VK_KHR_MAINTENANCE_7_EXTENSION_NAME
|
||||||
|
# define VK_KHR_MAINTENANCE_7_EXTENSION_NAME "VK_KHR_maintenance7"
|
||||||
|
#endif
|
||||||
|
#ifndef VK_KHR_MAINTENANCE_8_EXTENSION_NAME
|
||||||
|
# define VK_KHR_MAINTENANCE_8_EXTENSION_NAME "VK_KHR_maintenance8"
|
||||||
|
#endif
|
||||||
|
#ifndef VK_KHR_MAINTENANCE_9_EXTENSION_NAME
|
||||||
|
# define VK_KHR_MAINTENANCE_9_EXTENSION_NAME "VK_KHR_maintenance9"
|
||||||
|
#endif
|
||||||
|
|
||||||
// Sanitize macros
|
// Sanitize macros
|
||||||
#undef CreateEvent
|
#undef CreateEvent
|
||||||
#undef CreateSemaphore
|
#undef CreateSemaphore
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -90,6 +89,23 @@ constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{
|
|||||||
VK_FORMAT_UNDEFINED,
|
VK_FORMAT_UNDEFINED,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// B10G11R11_UFLOAT (R11G11B10 float) is used by Unreal Engine 5 for HDR textures
|
||||||
|
// Some Android drivers (Qualcomm pre-800, Mali pre-maintenance5) have issues with this format
|
||||||
|
// when used with MSAA or certain tiling modes, causing texture flickering/black screens
|
||||||
|
constexpr std::array B10G11R11_UFLOAT_PACK32{
|
||||||
|
VK_FORMAT_R16G16B16A16_SFLOAT, // Fallback: RGBA16F (more memory, but widely supported)
|
||||||
|
VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, // Alternative: E5B9G9R9 shared exponent format
|
||||||
|
VK_FORMAT_UNDEFINED,
|
||||||
|
};
|
||||||
|
|
||||||
|
// E5B9G9R9_UFLOAT (shared exponent RGB9E5) used by various engines (Unity, custom engines)
|
||||||
|
// Also problematic on some Android drivers, especially with MSAA and as render target
|
||||||
|
constexpr std::array E5B9G9R9_UFLOAT_PACK32{
|
||||||
|
VK_FORMAT_R16G16B16A16_SFLOAT, // Fallback: RGBA16F (safest option)
|
||||||
|
VK_FORMAT_B10G11R11_UFLOAT_PACK32, // Alternative: might work if E5B9G9R9 fails
|
||||||
|
VK_FORMAT_UNDEFINED,
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Alternatives
|
} // namespace Alternatives
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -122,6 +138,10 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
|
|||||||
return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data();
|
return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data();
|
||||||
case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT:
|
case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT:
|
||||||
return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data();
|
return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data();
|
||||||
|
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
|
||||||
|
return Alternatives::B10G11R11_UFLOAT_PACK32.data();
|
||||||
|
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
|
||||||
|
return Alternatives::E5B9G9R9_UFLOAT_PACK32.data();
|
||||||
default:
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -416,7 +436,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
const bool is_suitable = GetSuitability(surface != nullptr);
|
const bool is_suitable = GetSuitability(surface != nullptr);
|
||||||
|
|
||||||
const VkDriverId driver_id = properties.driver.driverID;
|
const VkDriverId driver_id = properties.driver.driverID;
|
||||||
const auto device_id = properties.properties.deviceID;
|
// uncomment this if you want per-device overrides :P
|
||||||
|
// const u32 device_id = properties.properties.deviceID;
|
||||||
|
|
||||||
const bool is_radv = driver_id == VK_DRIVER_ID_MESA_RADV;
|
const bool is_radv = driver_id == VK_DRIVER_ID_MESA_RADV;
|
||||||
const bool is_amd_driver =
|
const bool is_amd_driver =
|
||||||
driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE;
|
driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE;
|
||||||
@@ -427,7 +449,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
const bool is_mvk = driver_id == VK_DRIVER_ID_MOLTENVK;
|
const bool is_mvk = driver_id == VK_DRIVER_ID_MOLTENVK;
|
||||||
const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
|
const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
|
||||||
const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP;
|
const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP;
|
||||||
const bool is_s8gen2 = device_id == 0x43050a01;
|
|
||||||
const bool is_arm = driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
|
const bool is_arm = driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
|
||||||
|
|
||||||
if ((is_mvk || is_qualcomm || is_turnip || is_arm) && !is_suitable) {
|
if ((is_mvk || is_qualcomm || is_turnip || is_arm) && !is_suitable) {
|
||||||
@@ -494,11 +515,23 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
CollectPhysicalMemoryInfo();
|
CollectPhysicalMemoryInfo();
|
||||||
CollectToolingInfo();
|
CollectToolingInfo();
|
||||||
|
|
||||||
if (is_qualcomm || is_turnip) {
|
// Driver-specific handling for VK_EXT_custom_border_color
|
||||||
LOG_WARNING(Render_Vulkan,
|
// On some Qualcomm/Turnip/ARM drivers the extension may be partially implemented.
|
||||||
"Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color");
|
// Enable it if ANY useful feature bit is reported; otherwise, let the removal pass drop it.
|
||||||
//RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color,
|
if (is_qualcomm || is_turnip || is_arm) {
|
||||||
//VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
|
const bool has_any_custom_border_color =
|
||||||
|
features.custom_border_color.customBorderColors ||
|
||||||
|
features.custom_border_color.customBorderColorWithoutFormat;
|
||||||
|
if (!has_any_custom_border_color) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Disabling VK_EXT_custom_border_color on '{}' — no usable custom border color features reported",
|
||||||
|
properties.driver.driverName);
|
||||||
|
// Do not clear here; final removal happens in RemoveUnsuitableExtensions based on bits.
|
||||||
|
} else {
|
||||||
|
LOG_INFO(Render_Vulkan,
|
||||||
|
"Partial VK_EXT_custom_border_color support detected on '{}' — enabling available features",
|
||||||
|
properties.driver.driverName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_qualcomm) {
|
if (is_qualcomm) {
|
||||||
@@ -681,9 +714,40 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
has_broken_compute =
|
has_broken_compute =
|
||||||
CheckBrokenCompute(properties.driver.driverID, properties.properties.driverVersion) &&
|
CheckBrokenCompute(properties.driver.driverID, properties.properties.driverVersion) &&
|
||||||
!Settings::values.enable_compute_pipelines.GetValue();
|
!Settings::values.enable_compute_pipelines.GetValue();
|
||||||
if (is_intel_anv || (is_qualcomm && !is_s8gen2)) {
|
must_emulate_bgr565 = false; // Default: assume emulation isn't required
|
||||||
LOG_WARNING(Render_Vulkan, "Driver does not support native BGR format");
|
|
||||||
|
if (is_intel_anv) {
|
||||||
|
LOG_WARNING(Render_Vulkan, "Intel ANV driver does not support native BGR format");
|
||||||
must_emulate_bgr565 = true;
|
must_emulate_bgr565 = true;
|
||||||
|
} else if (is_qualcomm) {
|
||||||
|
// Qualcomm driver version where VK_KHR_maintenance5 and A1B5G5R5 become reliable
|
||||||
|
constexpr uint32_t QUALCOMM_FIXED_DRIVER_VERSION = VK_MAKE_VERSION(512, 800, 1);
|
||||||
|
// Check if VK_KHR_maintenance5 is supported
|
||||||
|
if (extensions.maintenance5 && properties.properties.driverVersion >= QUALCOMM_FIXED_DRIVER_VERSION) {
|
||||||
|
LOG_INFO(Render_Vulkan, "Qualcomm driver supports VK_KHR_maintenance5, disabling BGR emulation");
|
||||||
|
must_emulate_bgr565 = false;
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Render_Vulkan, "Qualcomm driver doesn't support native BGR, emulating formats");
|
||||||
|
must_emulate_bgr565 = true;
|
||||||
|
}
|
||||||
|
} else if (is_turnip) {
|
||||||
|
// Mesa Turnip added support for maintenance5 in Mesa 25.0
|
||||||
|
if (extensions.maintenance5) {
|
||||||
|
LOG_INFO(Render_Vulkan, "Turnip driver supports VK_KHR_maintenance5, disabling BGR emulation");
|
||||||
|
must_emulate_bgr565 = false;
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Render_Vulkan, "Turnip driver doesn't support native BGR, emulating formats");
|
||||||
|
must_emulate_bgr565 = true;
|
||||||
|
}
|
||||||
|
} else if (is_arm) {
|
||||||
|
// ARM Mali: stop emulating BGR5 formats when VK_KHR_maintenance5 is available
|
||||||
|
if (extensions.maintenance5) {
|
||||||
|
LOG_INFO(Render_Vulkan, "ARM driver supports VK_KHR_maintenance5, disabling BGR emulation");
|
||||||
|
must_emulate_bgr565 = false;
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Render_Vulkan, "ARM driver doesn't support native BGR, emulating formats");
|
||||||
|
must_emulate_bgr565 = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (extensions.push_descriptor && is_intel_anv) {
|
if (extensions.push_descriptor && is_intel_anv) {
|
||||||
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
||||||
@@ -800,15 +864,33 @@ Device::~Device() {
|
|||||||
VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
|
VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
|
||||||
FormatType format_type) const {
|
FormatType format_type) const {
|
||||||
if (IsFormatSupported(wanted_format, wanted_usage, format_type)) {
|
if (IsFormatSupported(wanted_format, wanted_usage, format_type)) {
|
||||||
return wanted_format;
|
// CRITICAL FIX: Even if format is "supported", check for STORAGE + HDR + no MSAA support
|
||||||
|
// Driver may report STORAGE_IMAGE_BIT but shaderStorageImageMultisample=false means
|
||||||
|
// it will fail at runtime when used with MSAA (CopyImageMSAA silently fails)
|
||||||
|
const bool requests_storage = (wanted_usage & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
||||||
|
const bool is_hdr_format = wanted_format == VK_FORMAT_B10G11R11_UFLOAT_PACK32 ||
|
||||||
|
wanted_format == VK_FORMAT_E5B9G9R9_UFLOAT_PACK32;
|
||||||
|
|
||||||
|
// If driver doesn't support shader storage image with MSAA, and we're requesting storage
|
||||||
|
// for an HDR format (which will likely be used with MSAA), force fallback
|
||||||
|
if (requests_storage && is_hdr_format && !features.features.shaderStorageImageMultisample) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Format {} reports STORAGE_IMAGE_BIT but driver doesn't support "
|
||||||
|
"shaderStorageImageMultisample. Forcing fallback for MSAA compatibility.",
|
||||||
|
wanted_format);
|
||||||
|
// Continue to alternatives search below
|
||||||
|
} else {
|
||||||
|
return wanted_format;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// The wanted format is not supported by hardware, search for alternatives
|
// The wanted format is not supported by hardware, search for alternatives
|
||||||
const VkFormat* alternatives = GetFormatAlternatives(wanted_format);
|
const VkFormat* alternatives = GetFormatAlternatives(wanted_format);
|
||||||
if (alternatives == nullptr) {
|
if (alternatives == nullptr) {
|
||||||
LOG_ERROR(Render_Vulkan,
|
LOG_ERROR(Render_Vulkan,
|
||||||
"Format={} with usage={} and type={} has no defined alternatives and host "
|
"Format={} (0x{:X}) with usage={} and type={} has no defined alternatives and host "
|
||||||
"hardware does not support it",
|
"hardware does not support it. Driver: {} Device: {}",
|
||||||
wanted_format, wanted_usage, format_type);
|
wanted_format, static_cast<u32>(wanted_format), wanted_usage, format_type,
|
||||||
|
GetDriverName(), properties.properties.deviceName);
|
||||||
return wanted_format;
|
return wanted_format;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,9 +899,22 @@ VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags
|
|||||||
if (!IsFormatSupported(alternative, wanted_usage, format_type)) {
|
if (!IsFormatSupported(alternative, wanted_usage, format_type)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
LOG_DEBUG(Render_Vulkan,
|
// Special logging for HDR formats (common across multiple engines) on problematic drivers
|
||||||
|
if (wanted_format == VK_FORMAT_B10G11R11_UFLOAT_PACK32) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Emulating B10G11R11_UFLOAT (HDR format: UE5, custom engines) with {} on {}. "
|
||||||
|
"Native format not supported by driver, using fallback.",
|
||||||
|
alternative, properties.properties.deviceName);
|
||||||
|
} else if (wanted_format == VK_FORMAT_E5B9G9R9_UFLOAT_PACK32) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Emulating E5B9G9R9_UFLOAT (HDR format: Unity, RE Engine) with {} on {}. "
|
||||||
|
"Native format not supported by driver, using fallback.",
|
||||||
|
alternative, properties.properties.deviceName);
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG(Render_Vulkan,
|
||||||
"Emulating format={} with alternative format={} with usage={} and type={}",
|
"Emulating format={} with alternative format={} with usage={} and type={}",
|
||||||
wanted_format, alternative, wanted_usage, format_type);
|
wanted_format, alternative, wanted_usage, format_type);
|
||||||
|
}
|
||||||
return alternative;
|
return alternative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1267,6 +1362,43 @@ void Device::RemoveUnsuitableExtensions() {
|
|||||||
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
|
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VK_KHR_shader_float16_int8
|
||||||
|
const bool float16_int8_requested = extensions.shader_float16_int8;
|
||||||
|
const bool float16_int8_usable =
|
||||||
|
features.shader_float16_int8.shaderFloat16 || features.shader_float16_int8.shaderInt8;
|
||||||
|
if (float16_int8_requested && !float16_int8_usable) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Disabling VK_KHR_shader_float16_int8 — no shaderFloat16/shaderInt8 features reported");
|
||||||
|
}
|
||||||
|
extensions.shader_float16_int8 = float16_int8_requested && float16_int8_usable;
|
||||||
|
RemoveExtensionFeatureIfUnsuitable(float16_int8_usable, features.shader_float16_int8,
|
||||||
|
VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME);
|
||||||
|
|
||||||
|
// VK_EXT_shader_atomic_float
|
||||||
|
const bool atomic_float_requested = extensions.shader_atomic_float;
|
||||||
|
const auto& atomic_float_features = features.shader_atomic_float;
|
||||||
|
const bool supports_buffer_f32 = atomic_float_features.shaderBufferFloat32Atomics ||
|
||||||
|
atomic_float_features.shaderBufferFloat32AtomicAdd;
|
||||||
|
const bool supports_shared_f32 = atomic_float_features.shaderSharedFloat32Atomics ||
|
||||||
|
atomic_float_features.shaderSharedFloat32AtomicAdd;
|
||||||
|
const bool supports_image_f32 = atomic_float_features.shaderImageFloat32Atomics ||
|
||||||
|
atomic_float_features.shaderImageFloat32AtomicAdd;
|
||||||
|
const bool supports_sparse_f32 = atomic_float_features.sparseImageFloat32Atomics ||
|
||||||
|
atomic_float_features.sparseImageFloat32AtomicAdd;
|
||||||
|
const bool supports_buffer_f64 = atomic_float_features.shaderBufferFloat64Atomics ||
|
||||||
|
atomic_float_features.shaderBufferFloat64AtomicAdd;
|
||||||
|
const bool supports_shared_f64 = atomic_float_features.shaderSharedFloat64Atomics ||
|
||||||
|
atomic_float_features.shaderSharedFloat64AtomicAdd;
|
||||||
|
const bool atomic_float_usable = supports_buffer_f32 || supports_shared_f32 || supports_image_f32 ||
|
||||||
|
supports_sparse_f32 || supports_buffer_f64 || supports_shared_f64;
|
||||||
|
if (atomic_float_requested && !atomic_float_usable) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Disabling VK_EXT_shader_atomic_float — no usable atomic float feature bits reported");
|
||||||
|
}
|
||||||
|
extensions.shader_atomic_float = atomic_float_requested && atomic_float_usable;
|
||||||
|
RemoveExtensionFeatureIfUnsuitable(atomic_float_usable, features.shader_atomic_float,
|
||||||
|
VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME);
|
||||||
|
|
||||||
// VK_KHR_shader_atomic_int64
|
// VK_KHR_shader_atomic_int64
|
||||||
extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics &&
|
extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics &&
|
||||||
features.shader_atomic_int64.shaderSharedInt64Atomics;
|
features.shader_atomic_int64.shaderSharedInt64Atomics;
|
||||||
@@ -1300,6 +1432,21 @@ void Device::RemoveUnsuitableExtensions() {
|
|||||||
RemoveExtensionFeatureIfUnsuitable(extensions.transform_feedback, features.transform_feedback,
|
RemoveExtensionFeatureIfUnsuitable(extensions.transform_feedback, features.transform_feedback,
|
||||||
VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
|
VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
|
||||||
|
|
||||||
|
// VK_EXT_robustness2
|
||||||
|
extensions.robustness_2 =
|
||||||
|
features.robustness2.robustBufferAccess2 && features.robustness2.robustImageAccess2;
|
||||||
|
RemoveExtensionFeatureIfUnsuitable(extensions.robustness_2, features.robustness2,
|
||||||
|
VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||||
|
|
||||||
|
// VK_EXT_image_robustness
|
||||||
|
extensions.image_robustness = features.image_robustness.robustImageAccess;
|
||||||
|
RemoveExtensionFeatureIfUnsuitable(extensions.image_robustness, features.image_robustness,
|
||||||
|
VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME);
|
||||||
|
|
||||||
|
// VK_EXT_swapchain_maintenance1
|
||||||
|
extensions.swapchain_maintenance1 = loaded_extensions.contains(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME);
|
||||||
|
RemoveExtensionIfUnsuitable(extensions.swapchain_maintenance1, VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME);
|
||||||
|
|
||||||
// VK_EXT_vertex_input_dynamic_state
|
// VK_EXT_vertex_input_dynamic_state
|
||||||
extensions.vertex_input_dynamic_state =
|
extensions.vertex_input_dynamic_state =
|
||||||
features.vertex_input_dynamic_state.vertexInputDynamicState;
|
features.vertex_input_dynamic_state.vertexInputDynamicState;
|
||||||
|
|||||||
@@ -49,9 +49,11 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
|||||||
FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \
|
FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \
|
||||||
FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \
|
FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \
|
||||||
FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \
|
FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \
|
||||||
|
FEATURE(EXT, ShaderAtomicFloat, SHADER_ATOMIC_FLOAT, shader_atomic_float) \
|
||||||
FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
|
FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
|
||||||
FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
|
FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
|
||||||
FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
|
FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
|
||||||
|
FEATURE(EXT, ImageRobustness, IMAGE_ROBUSTNESS, image_robustness) \
|
||||||
FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
|
FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
|
||||||
primitive_topology_list_restart) \
|
primitive_topology_list_restart) \
|
||||||
FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \
|
FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \
|
||||||
@@ -82,7 +84,9 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
|||||||
EXTENSION(KHR, SHADER_FLOAT_CONTROLS, shader_float_controls) \
|
EXTENSION(KHR, SHADER_FLOAT_CONTROLS, shader_float_controls) \
|
||||||
EXTENSION(KHR, SPIRV_1_4, spirv_1_4) \
|
EXTENSION(KHR, SPIRV_1_4, spirv_1_4) \
|
||||||
EXTENSION(KHR, SWAPCHAIN, swapchain) \
|
EXTENSION(KHR, SWAPCHAIN, swapchain) \
|
||||||
|
EXTENSION(KHR, INCREMENTAL_PRESENT, incremental_present) \
|
||||||
EXTENSION(KHR, SWAPCHAIN_MUTABLE_FORMAT, swapchain_mutable_format) \
|
EXTENSION(KHR, SWAPCHAIN_MUTABLE_FORMAT, swapchain_mutable_format) \
|
||||||
|
EXTENSION(EXT, SWAPCHAIN_MAINTENANCE_1, swapchain_maintenance1) \
|
||||||
EXTENSION(KHR, IMAGE_FORMAT_LIST, image_format_list) \
|
EXTENSION(KHR, IMAGE_FORMAT_LIST, image_format_list) \
|
||||||
EXTENSION(NV, DEVICE_DIAGNOSTICS_CONFIG, device_diagnostics_config) \
|
EXTENSION(NV, DEVICE_DIAGNOSTICS_CONFIG, device_diagnostics_config) \
|
||||||
EXTENSION(NV, GEOMETRY_SHADER_PASSTHROUGH, geometry_shader_passthrough) \
|
EXTENSION(NV, GEOMETRY_SHADER_PASSTHROUGH, geometry_shader_passthrough) \
|
||||||
@@ -90,7 +94,17 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
|||||||
EXTENSION(NV, VIEWPORT_SWIZZLE, viewport_swizzle) \
|
EXTENSION(NV, VIEWPORT_SWIZZLE, viewport_swizzle) \
|
||||||
EXTENSION(EXT, DESCRIPTOR_INDEXING, descriptor_indexing) \
|
EXTENSION(EXT, DESCRIPTOR_INDEXING, descriptor_indexing) \
|
||||||
EXTENSION(EXT, FILTER_CUBIC, filter_cubic) \
|
EXTENSION(EXT, FILTER_CUBIC, filter_cubic) \
|
||||||
EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights)
|
EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights) \
|
||||||
|
EXTENSION(QCOM, RENDER_PASS_SHADER_RESOLVE, render_pass_shader_resolve) \
|
||||||
|
EXTENSION(KHR, MAINTENANCE_1, maintenance1) \
|
||||||
|
EXTENSION(KHR, MAINTENANCE_2, maintenance2) \
|
||||||
|
EXTENSION(KHR, MAINTENANCE_3, maintenance3) \
|
||||||
|
EXTENSION(KHR, MAINTENANCE_4, maintenance4) \
|
||||||
|
EXTENSION(KHR, MAINTENANCE_5, maintenance5) \
|
||||||
|
EXTENSION(KHR, MAINTENANCE_6, maintenance6) \
|
||||||
|
EXTENSION(KHR, MAINTENANCE_7, maintenance7) \
|
||||||
|
EXTENSION(KHR, MAINTENANCE_8, maintenance8) \
|
||||||
|
EXTENSION(KHR, MAINTENANCE_9, maintenance9)
|
||||||
|
|
||||||
// Define extensions which must be supported.
|
// Define extensions which must be supported.
|
||||||
#define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \
|
#define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \
|
||||||
@@ -455,6 +469,11 @@ public:
|
|||||||
return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2;
|
return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the device supports VK_KHR_incremental_present.
|
||||||
|
bool IsKhrIncrementalPresentSupported() const {
|
||||||
|
return extensions.incremental_present;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the device supports VK_EXT_primitive_topology_list_restart.
|
/// Returns true if the device supports VK_EXT_primitive_topology_list_restart.
|
||||||
bool IsTopologyListPrimitiveRestartSupported() const {
|
bool IsTopologyListPrimitiveRestartSupported() const {
|
||||||
return features.primitive_topology_list_restart.primitiveTopologyListRestart;
|
return features.primitive_topology_list_restart.primitiveTopologyListRestart;
|
||||||
@@ -564,6 +583,21 @@ public:
|
|||||||
return extensions.filter_cubic_weights;
|
return extensions.filter_cubic_weights;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the device supports VK_QCOM_render_pass_shader_resolve
|
||||||
|
bool IsQcomRenderPassShaderResolveSupported() const {
|
||||||
|
return extensions.render_pass_shader_resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if MSAA copy operations are supported via compute shader (upload/download)
|
||||||
|
/// Qualcomm uses render pass shader resolve instead, so this returns false for Qualcomm
|
||||||
|
bool CanUploadMSAA() const {
|
||||||
|
return IsStorageImageMultisampleSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanDownloadMSAA() const {
|
||||||
|
return CanUploadMSAA();
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the device supports VK_EXT_line_rasterization.
|
/// Returns true if the device supports VK_EXT_line_rasterization.
|
||||||
bool IsExtLineRasterizationSupported() const {
|
bool IsExtLineRasterizationSupported() const {
|
||||||
return extensions.line_rasterization;
|
return extensions.line_rasterization;
|
||||||
@@ -594,6 +628,11 @@ public:
|
|||||||
return extensions.shader_atomic_int64;
|
return extensions.shader_atomic_int64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the device supports VK_EXT_shader_atomic_float.
|
||||||
|
bool IsExtShaderAtomicFloatSupported() const {
|
||||||
|
return extensions.shader_atomic_float;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsExtConditionalRendering() const {
|
bool IsExtConditionalRendering() const {
|
||||||
return extensions.conditional_rendering;
|
return extensions.conditional_rendering;
|
||||||
}
|
}
|
||||||
@@ -817,7 +856,6 @@ private:
|
|||||||
VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor{};
|
VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor{};
|
||||||
VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{};
|
VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{};
|
||||||
VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{};
|
VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{};
|
||||||
|
|
||||||
VkPhysicalDeviceProperties properties{};
|
VkPhysicalDeviceProperties properties{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user