diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 7ab2bd4adf..53453e6ab7 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -465,12 +465,22 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct ctx.AddCapability(spv::Capability::ImageGatherExtended); ctx.AddCapability(spv::Capability::ImageQuery); ctx.AddCapability(spv::Capability::SampledBuffer); - // TODO: this usage needs to be tracked properly - if (ctx.profile.support_sampled_image_array_nonuniform_indexing) { - if (ctx.profile.supported_spirv < 0x00010400) + if (!ctx.non_uniform_ids.empty()) { + if (ctx.profile.supported_spirv < 0x00010500) ctx.AddExtension("SPV_EXT_descriptor_indexing"); ctx.AddCapability(spv::Capability::ShaderNonUniform); - ctx.AddCapability(spv::Capability::SampledImageArrayNonUniformIndexing); + if (ctx.uses_nonuniform_sampled_image) { + ctx.AddCapability(spv::Capability::SampledImageArrayNonUniformIndexing); + } + if (ctx.uses_nonuniform_storage_image) { + ctx.AddCapability(spv::Capability::StorageImageArrayNonUniformIndexing); + } + if (ctx.uses_nonuniform_uniform_texel_buffer) { + ctx.AddCapability(spv::Capability::UniformTexelBufferArrayNonUniformIndexing); + } + if (ctx.uses_nonuniform_storage_texel_buffer) { + ctx.AddCapability(spv::Capability::StorageTexelBufferArrayNonUniformIndexing); + } } } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 0ce73f289b..d1c08c1131 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,7 +18,7 @@ Id SharedPointer(EmitContext& ctx, Id offset, u32 index_offset = 0) { if (index_offset > 0) { index = ctx.OpIAdd(ctx.U32[1], index, ctx.Const(index_offset)); } - return ctx.profile.support_explicit_workgroup_layout + return ctx.uses_explicit_workgroup_layout ? ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index) : ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index); } @@ -155,7 +158,7 @@ Id EmitSharedAtomicExchange32(EmitContext& ctx, Id offset, Id value) { } Id EmitSharedAtomicExchange64(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_int64_atomics && ctx.profile.support_explicit_workgroup_layout) { + if (ctx.profile.support_shared_int64_atomics && ctx.uses_explicit_workgroup_layout) { const Id shift_id{ctx.Const(3U)}; const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; const Id pointer{ diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index f4d9de79d3..28f9869e22 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -624,12 +624,14 @@ Id EmitRenderArea(EmitContext& ctx) { } Id EmitLoadLocal(EmitContext& ctx, Id word_offset) { - const Id pointer{ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset)}; + const Id pointer{ + ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset, ctx.Const(0U))}; return ctx.OpLoad(ctx.U32[1], pointer); } void EmitWriteLocal(EmitContext& ctx, Id word_offset, Id value) { - const Id pointer{ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset)}; + const Id pointer{ + ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset, ctx.Const(0U))}; ctx.OpStore(pointer, value); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 0b3a1a7c08..5d4983b9f7 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -15,8 +15,56 @@ namespace Shader::Backend::SPIRV { namespace { -[[nodiscard]] bool IsNonUniformDescriptor(EmitContext& ctx, const IR::Value& index) noexcept { - return ctx.profile.support_sampled_image_array_nonuniform_indexing && !index.IsImmediate(); +enum class NonUniformKind { + SampledImage, + StorageImage, + UniformTexelBuffer, + StorageTexelBuffer, +}; + +[[nodiscard]] bool IsNonUniformSupported(const Profile& profile, NonUniformKind kind) noexcept { + switch (kind) { + case NonUniformKind::SampledImage: + return profile.support_sampled_image_array_nonuniform_indexing; + case NonUniformKind::StorageImage: + return profile.support_storage_image_array_nonuniform_indexing; + case NonUniformKind::UniformTexelBuffer: + return profile.support_uniform_texel_buffer_array_nonuniform_indexing; + case NonUniformKind::StorageTexelBuffer: + return profile.support_storage_texel_buffer_array_nonuniform_indexing; + } + return false; +} + +void DecorateNonUniform(EmitContext& ctx, Id object) { + if (ctx.non_uniform_ids.contains(object.value)) { + return; + } + ctx.Decorate(object, spv::Decoration::NonUniform); + ctx.non_uniform_ids.insert(object.value); +} + +[[nodiscard]] bool MarkNonUniform(EmitContext& ctx, Id idx, const IR::Value& index, + NonUniformKind kind) { + if (index.IsImmediate() || !IsNonUniformSupported(ctx.profile, kind)) { + return false; + } + DecorateNonUniform(ctx, idx); + switch (kind) { + case NonUniformKind::SampledImage: + ctx.uses_nonuniform_sampled_image = true; + break; + case NonUniformKind::StorageImage: + ctx.uses_nonuniform_storage_image = true; + break; + case NonUniformKind::UniformTexelBuffer: + ctx.uses_nonuniform_uniform_texel_buffer = true; + break; + case NonUniformKind::StorageTexelBuffer: + ctx.uses_nonuniform_storage_texel_buffer = true; + break; + } + return true; } class ImageOperands { @@ -195,12 +243,13 @@ Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; if (def.count > 1) { auto const idx = index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index); - if (!ctx.non_uniform_ids.contains(idx.value) && IsNonUniformDescriptor(ctx, index)) { - ctx.Decorate(idx, spv::Decoration::NonUniform); - ctx.non_uniform_ids.insert(idx.value); - } + const bool non_uniform{MarkNonUniform(ctx, idx, index, NonUniformKind::SampledImage)}; const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx)}; const Id object{ctx.OpLoad(def.sampled_type, pointer)}; + if (non_uniform) { + DecorateNonUniform(ctx, pointer); + DecorateNonUniform(ctx, object); + } return object; } else { return ctx.OpLoad(def.sampled_type, def.id); @@ -212,21 +261,30 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind const TextureBufferDefinition& def{ctx.texture_buffers.at(info.descriptor_index)}; if (def.count > 1) { const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}; + const bool non_uniform{ + MarkNonUniform(ctx, idx, index, NonUniformKind::UniformTexelBuffer)}; const Id ptr{ctx.OpAccessChain(ctx.image_buffer_type, def.id, idx)}; - return ctx.OpLoad(ctx.image_buffer_type, ptr); + const Id object{ctx.OpLoad(ctx.image_buffer_type, ptr)}; + if (non_uniform) { + DecorateNonUniform(ctx, ptr); + DecorateNonUniform(ctx, object); + } + return object; } return ctx.OpLoad(ctx.image_buffer_type, def.id); } else { const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; if (def.count > 1) { auto const idx = index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index); - if (!ctx.non_uniform_ids.contains(idx.value) && IsNonUniformDescriptor(ctx, index)) { - ctx.Decorate(idx, spv::Decoration::NonUniform); - ctx.non_uniform_ids.insert(idx.value); - } + const bool non_uniform{MarkNonUniform(ctx, idx, index, NonUniformKind::SampledImage)}; const Id ptr = ctx.OpAccessChain(def.pointer_type, def.id, idx); const Id object = ctx.OpLoad(def.sampled_type, ptr); const Id image = ctx.OpImage(def.image_type, object); + if (non_uniform) { + DecorateNonUniform(ctx, ptr); + DecorateNonUniform(ctx, object); + DecorateNonUniform(ctx, image); + } return image; } return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, def.id)); @@ -238,16 +296,29 @@ std::pair Image(EmitContext& ctx, const IR::Value& index, IR::TextureI const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)}; if (def.count > 1) { const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}; + const bool non_uniform{ + MarkNonUniform(ctx, idx, index, NonUniformKind::StorageTexelBuffer)}; const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)}; - return {ctx.OpLoad(def.image_type, ptr), def.is_integer}; + const Id image{ctx.OpLoad(def.image_type, ptr)}; + if (non_uniform) { + DecorateNonUniform(ctx, ptr); + DecorateNonUniform(ctx, image); + } + return {image, def.is_integer}; } return {ctx.OpLoad(def.image_type, def.id), def.is_integer}; } else { const ImageDefinition def{ctx.images.at(info.descriptor_index)}; if (def.count > 1) { const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}; + const bool non_uniform{MarkNonUniform(ctx, idx, index, NonUniformKind::StorageImage)}; const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)}; - return {ctx.OpLoad(def.image_type, ptr), def.is_integer}; + const Id image{ctx.OpLoad(def.image_type, ptr)}; + if (non_uniform) { + DecorateNonUniform(ctx, ptr); + DecorateNonUniform(ctx, image); + } + return {image, def.is_integer}; } return {ctx.OpLoad(def.image_type, def.id), def.is_integer}; } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp index 4a5e7ef067..bef4421fed 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp @@ -159,7 +159,7 @@ void EmitWriteGlobal128(EmitContext& ctx, Id address, Id value) { } Id EmitLoadStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit && + if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit && ctx.profile.support_descriptor_aliasing) { return ctx.OpUConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.U8, ctx.storage_types.U8, @@ -171,7 +171,7 @@ Id EmitLoadStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value } Id EmitLoadStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit && + if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit && ctx.profile.support_descriptor_aliasing) { return ctx.OpSConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.S8, ctx.storage_types.S8, @@ -183,7 +183,7 @@ Id EmitLoadStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value } Id EmitLoadStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit && + if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit && ctx.profile.support_descriptor_aliasing) { return ctx.OpUConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.U16, ctx.storage_types.U16, @@ -195,7 +195,7 @@ Id EmitLoadStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Valu } Id EmitLoadStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit && + if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit && ctx.profile.support_descriptor_aliasing) { return ctx.OpSConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.S16, ctx.storage_types.S16, @@ -234,7 +234,8 @@ Id EmitLoadStorage128(EmitContext& ctx, const IR::Value& binding, const IR::Valu void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit) { + if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit && + ctx.profile.support_descriptor_aliasing) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U8, value), ctx.storage_types.U8, sizeof(u8), &StorageDefinitions::U8); } else { @@ -244,7 +245,8 @@ void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Va void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit) { + if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit && + ctx.profile.support_descriptor_aliasing) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S8, value), ctx.storage_types.S8, sizeof(s8), &StorageDefinitions::S8); } else { @@ -254,7 +256,8 @@ void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Va void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit) { + if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit && + ctx.profile.support_descriptor_aliasing) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U16, value), ctx.storage_types.U16, sizeof(u16), &StorageDefinitions::U16); } else { @@ -264,7 +267,8 @@ void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::V void EmitWriteStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit) { + if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit && + ctx.profile.support_descriptor_aliasing) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S16, value), ctx.storage_types.S16, sizeof(s16), &StorageDefinitions::S16); } else { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp index c3208b955a..dc398aa296 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp @@ -31,7 +31,7 @@ std::pair ExtractArgs(EmitContext& ctx, Id offset, u32 mask, u32 count) } // Anonymous namespace Id EmitLoadSharedU8(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{ ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer)); @@ -42,7 +42,7 @@ Id EmitLoadSharedU8(EmitContext& ctx, Id offset) { } Id EmitLoadSharedS8(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{ ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer)); @@ -53,7 +53,7 @@ Id EmitLoadSharedS8(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); } else { @@ -63,7 +63,7 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { } Id EmitLoadSharedS16(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); } else { @@ -73,7 +73,7 @@ Id EmitLoadSharedS16(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)}; return ctx.OpLoad(ctx.U32[1], pointer); } else { @@ -82,7 +82,7 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; return ctx.OpLoad(ctx.U32[2], pointer); } else { @@ -97,7 +97,7 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU128(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; return ctx.OpLoad(ctx.U32[4], pointer); } @@ -113,7 +113,7 @@ Id EmitLoadSharedU128(EmitContext& ctx, Id offset) { } void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{ ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; ctx.OpStore(pointer, ctx.OpUConvert(ctx.U8, value)); @@ -123,7 +123,7 @@ void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) { } void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value)); } else { @@ -133,7 +133,7 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { Id pointer{}; - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2); } else { const Id shift{ctx.Const(2U)}; @@ -144,7 +144,7 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { } void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; ctx.OpStore(pointer, value); return; @@ -159,7 +159,7 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { } void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; ctx.OpStore(pointer, value); return; diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 17ce4d744f..b562e82983 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -371,7 +371,7 @@ Id CasFunction(EmitContext& ctx, Operation operation, Id value_type) { Id CasLoop(EmitContext& ctx, Operation operation, Id array_pointer, Id element_pointer, Id value_type, Id memory_type, spv::Scope scope) { const bool is_shared{scope == spv::Scope::Workgroup}; - const bool is_struct{!is_shared || ctx.profile.support_explicit_workgroup_layout}; + const bool is_struct{!is_shared || ctx.uses_explicit_workgroup_layout}; const Id cas_func{CasFunction(ctx, operation, value_type)}; const Id zero{ctx.u32_zero_value}; const Id scope_id{ctx.Const(static_cast(scope))}; @@ -591,7 +591,8 @@ void EmitContext::DefineLocalMemory(const IR::Program& program) { return; } const u32 num_elements{Common::DivCeil(program.local_memory_size, 4U)}; - const Id type{TypeArray(U32[1], Const(num_elements))}; + const Id element_type{TypeStruct(U32[1])}; + const Id type{TypeArray(element_type, Const(num_elements))}; const Id pointer{TypePointer(spv::StorageClass::Private, type)}; local_memory = AddGlobalVariable(pointer, spv::StorageClass::Private); if (profile.supported_spirv >= 0x00010400) { @@ -600,6 +601,10 @@ void EmitContext::DefineLocalMemory(const IR::Program& program) { } void EmitContext::DefineSharedMemory(const IR::Program& program) { + uses_explicit_workgroup_layout = + profile.support_explicit_workgroup_layout && + (!program.info.uses_int8 || profile.support_workgroup_layout_8bit_access) && + (!program.info.uses_int16 || profile.support_workgroup_layout_16bit_access); if (program.shared_memory_size == 0) { return; } @@ -620,18 +625,18 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) { return std::make_tuple(variable, element_pointer, pointer); }}; - if (profile.support_explicit_workgroup_layout) { + if (uses_explicit_workgroup_layout) { AddExtension("SPV_KHR_workgroup_memory_explicit_layout"); AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR); - if (program.info.uses_int8) { + if (program.info.uses_int8 && profile.support_int8) { AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout8BitAccessKHR); std::tie(shared_memory_u8, shared_u8, std::ignore) = make(U8, 1); } - if (program.info.uses_int16) { + if (program.info.uses_int16 && profile.support_int16) { AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR); std::tie(shared_memory_u16, shared_u16, std::ignore) = make(U16, 2); } - if (program.info.uses_int64) { + if (program.info.uses_int64 && profile.support_int64) { std::tie(shared_memory_u64, shared_u64, std::ignore) = make(U64, 8); } std::tie(shared_memory_u32, shared_u32, shared_memory_u32_type) = make(U32[1], 4); @@ -1229,16 +1234,17 @@ void EmitContext::DefineStorageBuffers(const Info& info, u32& binding) { } AddExtension("SPV_KHR_storage_buffer_storage_class"); - const IR::Type used_types{profile.support_descriptor_aliasing ? info.used_storage_buffer_types - : IR::Type::U32}; - if (profile.support_int8 && profile.support_uniform_and_storage_buffer_8bit && + IR::Type used_types{profile.support_descriptor_aliasing ? info.used_storage_buffer_types + : IR::Type::U32}; + used_types |= IR::Type::U32; + if (profile.support_int8 && profile.support_storage_buffer_8bit && True(used_types & IR::Type::U8)) { DefineSsbos(*this, storage_types.U8, &StorageDefinitions::U8, info, binding, U8, sizeof(u8)); DefineSsbos(*this, storage_types.S8, &StorageDefinitions::S8, info, binding, S8, sizeof(u8)); } - if (profile.support_int16 && profile.support_uniform_and_storage_buffer_16bit && + if (profile.support_int16 && profile.support_storage_buffer_16bit && True(used_types & IR::Type::U16)) { DefineSsbos(*this, storage_types.U16, &StorageDefinitions::U16, info, binding, U16, sizeof(u16)); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 504bffc0ed..fb20966b67 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -311,6 +311,7 @@ public: Id local_memory{}; + bool uses_explicit_workgroup_layout{}; Id shared_memory_u8{}; Id shared_memory_u16{}; Id shared_memory_u32{}; @@ -371,6 +372,11 @@ public: // Sirit::Id doesn't play nice with *::set<> ankerl::unordered_dense::set non_uniform_ids; + bool uses_nonuniform_sampled_image{}; + bool uses_nonuniform_storage_image{}; + bool uses_nonuniform_uniform_texel_buffer{}; + bool uses_nonuniform_storage_texel_buffer{}; + private: void DefineCommonTypes(const Info& info); void DefineCommonConstants(); diff --git a/src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp b/src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp index 5db7a38add..9a05609857 100644 --- a/src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp +++ b/src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp b/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp index cdb58f46b3..dbca9643d8 100644 --- a/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp +++ b/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -181,6 +184,72 @@ void ShiftRightArithmetic64To32(IR::Block& block, IR::Inst& inst) { inst.ReplaceUsesWith(ir.CompositeConstruct(ret_lo, ret_hi)); } +void IAbs64To32(IR::Block& block, IR::Inst& inst) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + const auto [lo, hi]{Unpack(ir, inst.Arg(0))}; + + const IR::U32 neg_lo{ir.IAdd(ir.BitwiseNot(lo), ir.Imm32(1))}; + const IR::U32 carry{IR::U32{ir.Select(ir.GetCarryFromOp(neg_lo), ir.Imm32(1u), ir.Imm32(0u))}}; + const IR::U32 neg_hi{ir.IAdd(ir.BitwiseNot(hi), carry)}; + + const IR::U1 is_negative{ir.INotEqual(ir.BitwiseAnd(hi, ir.Imm32(0x80000000u)), ir.Imm32(0u))}; + const IR::U32 ret_lo{IR::U32{ir.Select(is_negative, neg_lo, lo)}}; + const IR::U32 ret_hi{IR::U32{ir.Select(is_negative, neg_hi, hi)}}; + inst.ReplaceUsesWith(ir.CompositeConstruct(ret_lo, ret_hi)); +} + +void SelectU64To32(IR::Block& block, IR::Inst& inst) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + const IR::U1 condition{inst.Arg(0)}; + const auto [true_lo, true_hi]{Unpack(ir, inst.Arg(1))}; + const auto [false_lo, false_hi]{Unpack(ir, inst.Arg(2))}; + + const IR::U32 ret_lo{IR::U32{ir.Select(condition, true_lo, false_lo)}}; + const IR::U32 ret_hi{IR::U32{ir.Select(condition, true_hi, false_hi)}}; + inst.ReplaceUsesWith(ir.CompositeConstruct(ret_lo, ret_hi)); +} + +void UndefU64To32(IR::Block& block, IR::Inst& inst) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + inst.ReplaceUsesWith(ir.CompositeConstruct(ir.Imm32(0u), ir.Imm32(0u))); +} + +void ConvertU64U32To32(IR::Block& block, IR::Inst& inst) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + inst.ReplaceUsesWith(ir.CompositeConstruct(IR::U32{inst.Arg(0)}, ir.Imm32(0u))); +} + +void ConvertU32U64To32(IR::Block& block, IR::Inst& inst) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + inst.ReplaceUsesWith(Unpack(ir, inst.Arg(0)).first); +} + +void IntToFloat64To32(IR::Block& block, IR::Inst& inst, bool is_signed, size_t dest_bitsize) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + const auto [lo, hi]{Unpack(ir, inst.Arg(0))}; + const IR::F32 low{ir.ConvertUToF(32, 32, lo)}; + const IR::F32 high{is_signed ? IR::F32{ir.ConvertSToF(32, 32, hi)} + : IR::F32{ir.ConvertUToF(32, 32, hi)}}; + const IR::F32 combined{ir.FPFma(high, ir.Imm32(4294967296.0f), low)}; + if (dest_bitsize == 32) { + inst.ReplaceUsesWith(combined); + } else { + inst.ReplaceUsesWith(ir.FPConvert(dest_bitsize, combined)); + } +} + +void FloatToInt64To32(IR::Block& block, IR::Inst& inst, bool is_signed, size_t src_bitsize) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + const IR::F32 value{src_bitsize == 32 ? IR::F32{inst.Arg(0)} + : IR::F32{ir.FPConvert(32, IR::F16F32F64{inst.Arg(0)})}}; + const IR::F32 high_f{ir.FPFloor(ir.FPMul(value, ir.Imm32(1.0f / 4294967296.0f)))}; + const IR::U32 hi{is_signed ? IR::U32{ir.ConvertFToS(32, high_f)} + : IR::U32{ir.ConvertFToU(32, high_f)}}; + const IR::F32 low_f{ir.FPFma(high_f, ir.FPNeg(ir.Imm32(4294967296.0f)), value)}; + const IR::U32 lo{IR::U32{ir.ConvertFToU(32, low_f)}}; + inst.ReplaceUsesWith(ir.CompositeConstruct(lo, hi)); +} + void Lower(IR::Block& block, IR::Inst& inst) { switch (inst.GetOpcode()) { case IR::Opcode::PackUint2x32: @@ -218,6 +287,62 @@ void Lower(IR::Block& block, IR::Inst& inst) { return inst.ReplaceOpcode(IR::Opcode::GlobalAtomicXor32x2); case IR::Opcode::GlobalAtomicExchange64: return inst.ReplaceOpcode(IR::Opcode::GlobalAtomicExchange32x2); + case IR::Opcode::StorageAtomicIAdd64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicIAdd32x2); + case IR::Opcode::StorageAtomicSMin64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicSMin32x2); + case IR::Opcode::StorageAtomicUMin64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicUMin32x2); + case IR::Opcode::StorageAtomicSMax64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicSMax32x2); + case IR::Opcode::StorageAtomicUMax64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicUMax32x2); + case IR::Opcode::StorageAtomicAnd64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicAnd32x2); + case IR::Opcode::StorageAtomicOr64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicOr32x2); + case IR::Opcode::StorageAtomicXor64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicXor32x2); + case IR::Opcode::StorageAtomicExchange64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicExchange32x2); + case IR::Opcode::BitCastU64F64: + return inst.ReplaceOpcode(IR::Opcode::UnpackDouble2x32); + case IR::Opcode::BitCastF64U64: + return inst.ReplaceOpcode(IR::Opcode::PackDouble2x32); + case IR::Opcode::UndefU64: + return UndefU64To32(block, inst); + case IR::Opcode::SelectU64: + return SelectU64To32(block, inst); + case IR::Opcode::IAbs64: + return IAbs64To32(block, inst); + case IR::Opcode::ConvertU64U32: + return ConvertU64U32To32(block, inst); + case IR::Opcode::ConvertU32U64: + return ConvertU32U64To32(block, inst); + case IR::Opcode::ConvertS64F16: + return FloatToInt64To32(block, inst, true, 16); + case IR::Opcode::ConvertS64F32: + return FloatToInt64To32(block, inst, true, 32); + case IR::Opcode::ConvertS64F64: + return FloatToInt64To32(block, inst, true, 64); + case IR::Opcode::ConvertU64F16: + return FloatToInt64To32(block, inst, false, 16); + case IR::Opcode::ConvertU64F32: + return FloatToInt64To32(block, inst, false, 32); + case IR::Opcode::ConvertU64F64: + return FloatToInt64To32(block, inst, false, 64); + case IR::Opcode::ConvertF16S64: + return IntToFloat64To32(block, inst, true, 16); + case IR::Opcode::ConvertF32S64: + return IntToFloat64To32(block, inst, true, 32); + case IR::Opcode::ConvertF64S64: + return IntToFloat64To32(block, inst, true, 64); + case IR::Opcode::ConvertF16U64: + return IntToFloat64To32(block, inst, false, 16); + case IR::Opcode::ConvertF32U64: + return IntToFloat64To32(block, inst, false, 32); + case IR::Opcode::ConvertF64U64: + return IntToFloat64To32(block, inst, false, 64); default: break; } diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 0197a5f1d7..eb7b2a02b2 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -18,8 +18,10 @@ struct Profile { bool support_descriptor_aliasing{}; bool support_int8{}; bool support_uniform_and_storage_buffer_8bit{}; + bool support_storage_buffer_8bit{}; bool support_int16{}; bool support_uniform_and_storage_buffer_16bit{}; + bool support_storage_buffer_16bit{}; bool support_int64{}; bool support_vertex_instance_id{}; bool support_float_controls{}; @@ -33,6 +35,8 @@ struct Profile { bool support_fp32_signed_zero_nan_preserve{}; bool support_fp64_signed_zero_nan_preserve{}; bool support_explicit_workgroup_layout{}; + bool support_workgroup_layout_8bit_access{}; + bool support_workgroup_layout_16bit_access{}; bool support_vote{}; u32 supported_subgroup_stages{0x7F}; bool support_viewport_index_layer_non_geometry{}; @@ -40,6 +44,7 @@ struct Profile { bool support_typeless_image_loads{}; bool support_demote_to_helper_invocation{}; bool support_int64_atomics{}; + bool support_shared_int64_atomics{}; bool support_derivative_control{}; bool support_geometry_shader_passthrough{}; bool support_native_ndc{}; @@ -54,6 +59,9 @@ struct Profile { bool support_multi_viewport{}; bool support_geometry_streams{}; bool support_sampled_image_array_nonuniform_indexing{}; + bool support_storage_image_array_nonuniform_indexing{}; + bool support_uniform_texel_buffer_array_nonuniform_indexing{}; + bool support_storage_texel_buffer_array_nonuniform_indexing{}; bool warp_size_potentially_larger_than_guest{}; diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index 9e8d76b104..0d342c463d 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -17,6 +17,9 @@ set(SHADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp ${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag ${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp + ${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag + ${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_msaa.frag + ${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag ${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp ${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp ${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag @@ -26,7 +29,9 @@ set(SHADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/convert_depth_to_float.frag ${CMAKE_CURRENT_SOURCE_DIR}/convert_float_to_depth.frag ${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.comp + ${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.frag ${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.comp + ${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.frag ${CMAKE_CURRENT_SOURCE_DIR}/convert_s8d24_to_abgr8.frag ${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert ${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag diff --git a/src/video_core/host_shaders/blit_color_msaa.frag b/src/video_core/host_shaders/blit_color_msaa.frag new file mode 100644 index 0000000000..b25d0342c3 --- /dev/null +++ b/src/video_core/host_shaders/blit_color_msaa.frag @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core + +layout(binding = 0) uniform sampler2DMS tex; + +layout(location = 0) in vec2 texcoord; +layout(location = 0) out vec4 color; + +void main() { + color = texelFetch(tex, ivec2(texcoord), gl_SampleID); +} diff --git a/src/video_core/host_shaders/blit_depth_msaa.frag b/src/video_core/host_shaders/blit_depth_msaa.frag new file mode 100644 index 0000000000..064289ad04 --- /dev/null +++ b/src/video_core/host_shaders/blit_depth_msaa.frag @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core + +layout(binding = 0) uniform sampler2DMS depth_tex; + +layout(location = 0) in vec2 texcoord; + +void main() { + gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), 0).r; +} diff --git a/src/video_core/host_shaders/blit_depth_stencil_msaa.frag b/src/video_core/host_shaders/blit_depth_stencil_msaa.frag new file mode 100644 index 0000000000..d4264f2d14 --- /dev/null +++ b/src/video_core/host_shaders/blit_depth_stencil_msaa.frag @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core +#extension GL_ARB_shader_stencil_export : require + +layout(binding = 0) uniform sampler2DMS depth_tex; +layout(binding = 1) uniform usampler2DMS stencil_tex; + +layout(location = 0) in vec2 texcoord; + +void main() { + gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), 0).r; + gl_FragStencilRefARB = int(texelFetch(stencil_tex, ivec2(texcoord), 0).r); +} diff --git a/src/video_core/host_shaders/convert_msaa_to_non_msaa.frag b/src/video_core/host_shaders/convert_msaa_to_non_msaa.frag new file mode 100644 index 0000000000..6c4aac3c82 --- /dev/null +++ b/src/video_core/host_shaders/convert_msaa_to_non_msaa.frag @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core + +layout(binding = 0) uniform sampler2DMS msaa_in; + +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; +}; + +layout(location = 0) out vec4 frag_color; + +void main() { + const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset; + const ivec2 msaa_coord = coord / scale; + const ivec2 sample_offset = coord % scale; + const int sample_id = sample_offset.x + scale.x * sample_offset.y; + frag_color = texelFetch(msaa_in, msaa_coord, sample_id); +} diff --git a/src/video_core/host_shaders/convert_non_msaa_to_msaa.frag b/src/video_core/host_shaders/convert_non_msaa_to_msaa.frag new file mode 100644 index 0000000000..19e23849d2 --- /dev/null +++ b/src/video_core/host_shaders/convert_non_msaa_to_msaa.frag @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core + +layout(binding = 0) uniform sampler2D img_in; + +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; +}; + +layout(location = 0) out vec4 frag_color; + +void main() { + const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset; + const ivec2 sample_offset = ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x); + const ivec2 coord = msaa_coord * scale + sample_offset + src_offset; + frag_color = texelFetch(img_in, coord, 0); +} diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index d99e528bc0..89083d29fb 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -8,14 +8,20 @@ #include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "common/div_ceil.h" #include "common/settings.h" #include "video_core/host_shaders/blit_color_float_frag_spv.h" +#include "video_core/host_shaders/blit_color_msaa_frag_spv.h" +#include "video_core/host_shaders/blit_depth_msaa_frag_spv.h" +#include "video_core/host_shaders/blit_depth_stencil_msaa_frag_spv.h" #include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h" #include "video_core/host_shaders/convert_abgr8_to_d32f_frag_spv.h" #include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h" #include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h" #include "video_core/host_shaders/convert_depth_to_float_frag_spv.h" #include "video_core/host_shaders/convert_float_to_depth_frag_spv.h" +#include "video_core/host_shaders/convert_msaa_to_non_msaa_frag_spv.h" +#include "video_core/host_shaders/convert_non_msaa_to_msaa_frag_spv.h" #include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h" #include "video_core/host_shaders/full_screen_triangle_vert_spv.h" #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" @@ -24,11 +30,13 @@ #include "video_core/host_shaders/vulkan_depthstencil_clear_frag_spv.h" #include "video_core/renderer_vulkan/blit_image.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" +#include "video_core/renderer_vulkan/vk_render_pass_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/renderer_vulkan/vk_state_tracker.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/surface.h" +#include "video_core/texture_cache/samples_helper.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -74,6 +82,12 @@ struct PushConstants { std::array tex_offset; }; +struct MSAACopyPushConstants { + std::array dst_offset; + std::array src_offset; + std::array scale; +}; + template inline constexpr VkDescriptorSetLayoutBinding TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING{ .binding = binding, @@ -241,6 +255,21 @@ constexpr VkPipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CRE .maxDepthBounds = 0.0f, }; +constexpr VkPipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_ONLY_STATE_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthTestEnable = VK_TRUE, + .depthWriteEnable = VK_TRUE, + .depthCompareOp = VK_COMPARE_OP_ALWAYS, + .depthBoundsTestEnable = VK_FALSE, + .stencilTestEnable = VK_FALSE, + .front = VkStencilOpState{}, + .back = VkStencilOpState{}, + .minDepthBounds = 0.0f, + .maxDepthBounds = 0.0f, +}; + template inline constexpr VkSamplerCreateInfo SAMPLER_CREATE_INFO{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, @@ -474,6 +503,46 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view) }); } +[[nodiscard]] VkSampleCountFlagBits SampleCountFlag(u32 num_samples) { + switch (num_samples) { + case 2: + return VK_SAMPLE_COUNT_2_BIT; + case 4: + return VK_SAMPLE_COUNT_4_BIT; + case 8: + return VK_SAMPLE_COUNT_8_BIT; + case 16: + return VK_SAMPLE_COUNT_16_BIT; + default: + return VK_SAMPLE_COUNT_1_BIT; + } +} + +[[nodiscard]] vk::ImageView MakeMSAACopyView(const vk::Device& device, VkImage image, + VkFormat format, u32 base_level) { + return device.CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components{ + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = base_level, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }); +} + void BeginRenderPass(vk::CommandBuffer& cmdbuf, const Framebuffer* framebuffer) { const VkRenderPass render_pass = framebuffer->RenderPass(); const VkFramebuffer framebuffer_handle = framebuffer->Handle(); @@ -514,11 +583,19 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, PUSH_CONSTANT_RANGE))), clear_color_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( nullptr, PUSH_CONSTANT_RANGE))), + msaa_copy_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( + one_texture_set_layout.address(), + PUSH_CONSTANT_RANGE))), full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)), blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)), + blit_color_msaa_frag(BuildShader(device, BLIT_COLOR_MSAA_FRAG_SPV)), blit_depth_stencil_frag(device.IsExtShaderStencilExportSupported() ? BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV) : vk::ShaderModule{}), + blit_depth_msaa_frag(BuildShader(device, BLIT_DEPTH_MSAA_FRAG_SPV)), + blit_depth_stencil_msaa_frag(device.IsExtShaderStencilExportSupported() + ? BuildShader(device, BLIT_DEPTH_STENCIL_MSAA_FRAG_SPV) + : vk::ShaderModule{}), clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)), clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)), clear_stencil_frag(BuildShader(device, VULKAN_DEPTHSTENCIL_CLEAR_FRAG_SPV)), @@ -531,6 +608,8 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)), convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)), convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)), + convert_msaa_to_non_msaa_frag(BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_FRAG_SPV)), + convert_non_msaa_to_msaa_frag(BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_FRAG_SPV)), linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO)), nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO)) {} @@ -591,6 +670,70 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView }); } +void BlitImageHelper::BlitColorMSAA(const Framebuffer* dst_framebuffer, + const ImageView& src_image_view, const Region2D& dst_region, + const Region2D& src_region) { + const BlitMSAAPipelineKey key{ + .renderpass = dst_framebuffer->RenderPass(), + .samples = dst_framebuffer->Samples(), + }; + const VkPipelineLayout layout = *one_texture_pipeline_layout; + const VkSampler sampler = *nearest_sampler; + const VkPipeline pipeline = FindOrEmplaceBlitColorMSAAPipeline(key); + const VkImageView src_view = src_image_view.Handle(Shader::TextureType::Color2D); + + RecordShaderReadBarrier(scheduler, src_image_view); + scheduler.RequestRenderpass(dst_framebuffer); + scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler, + src_view](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit(); + UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + BindBlitState(cmdbuf, layout, dst_region, src_region); + cmdbuf.Draw(3, 1, 0, 0); + }); + scheduler.InvalidateState(); +} + +void BlitImageHelper::ResolveDepthStencil(const Framebuffer* dst_framebuffer, + ImageView& src_image_view, const Region2D& dst_region, + const Region2D& src_region) { + const bool resolve_stencil = + dst_framebuffer->HasAspectStencilBit() && device.IsExtShaderStencilExportSupported(); + const VkPipeline pipeline = + FindOrEmplaceResolveDepthStencilPipeline(dst_framebuffer->RenderPass(), resolve_stencil); + const VkPipelineLayout layout = + resolve_stencil ? *two_textures_pipeline_layout : *one_texture_pipeline_layout; + const VkSampler sampler = *nearest_sampler; + const VkImageView src_depth_view = src_image_view.DepthView(); + const VkImageView src_stencil_view = + resolve_stencil ? src_image_view.StencilView() : VK_NULL_HANDLE; + + RecordShaderReadBarrier(scheduler, src_image_view); + scheduler.RequestRenderpass(dst_framebuffer); + scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler, src_depth_view, + src_stencil_view, resolve_stencil](vk::CommandBuffer cmdbuf) { + if (resolve_stencil) { + const VkDescriptorSet descriptor_set = two_textures_descriptor_allocator.Commit(); + UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_depth_view, + src_stencil_view); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + } else { + const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit(); + UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_depth_view); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + } + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + BindBlitState(cmdbuf, layout, dst_region, src_region); + cmdbuf.Draw(3, 1, 0, 0); + }); + scheduler.InvalidateState(); +} + void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer, ImageView& src_image_view, const Region2D& dst_region, const Region2D& src_region, @@ -739,6 +882,174 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool scheduler.InvalidateState(); } +void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat dst_format, VkImage src_image, + VideoCore::Surface::PixelFormat src_format, u32 num_samples, + std::span copies, + bool msaa_to_non_msaa) { + while (!msaa_copy_resources.empty() && scheduler.IsFree(msaa_copy_resources.front().tick)) { + msaa_copy_resources.pop_front(); + } + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast(num_samples)); + const s32 scale_x = 1 << samples_x; + const s32 scale_y = 1 << samples_y; + const VkSampleCountFlagBits samples = + msaa_to_non_msaa ? VK_SAMPLE_COUNT_1_BIT : SampleCountFlag(num_samples); + RenderPassKey renderpass_key{}; + renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid); + renderpass_key.color_formats[0] = dst_format; + renderpass_key.depth_format = VideoCore::Surface::PixelFormat::Invalid; + renderpass_key.samples = samples; + const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key); + const MSAACopyPipelineKey key{ + .renderpass = renderpass, + .samples = samples, + .msaa_to_non_msaa = msaa_to_non_msaa, + }; + const VkPipeline pipeline = FindOrEmplaceMSAACopyPipeline(key); + const VkPipelineLayout layout = *msaa_copy_pipeline_layout; + const VkSampler sampler = *nearest_sampler; + const VkFormat src_vk_format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src_format).format; + const VkFormat dst_vk_format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, dst_format).format; + for (const VideoCommon::ImageCopy& copy : copies) { + ASSERT(copy.src_subresource.base_layer == 0); + ASSERT(copy.src_subresource.num_layers == 1); + ASSERT(copy.dst_subresource.base_layer == 0); + ASSERT(copy.dst_subresource.num_layers == 1); + vk::ImageView src_view = + MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format, + static_cast(copy.src_subresource.base_level)); + vk::ImageView dst_view = + MakeMSAACopyView(device.GetLogical(), dst_image, dst_vk_format, + static_cast(copy.dst_subresource.base_level)); + const VkOffset2D dst_offset{copy.dst_offset.x, copy.dst_offset.y}; + const VkExtent2D dst_extent{copy.extent.width, copy.extent.height}; + const VkRect2D render_area{ + .offset = dst_offset, + .extent = dst_extent, + }; + vk::Framebuffer framebuffer = device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .renderPass = renderpass, + .attachmentCount = 1, + .pAttachments = dst_view.address(), + .width = static_cast(dst_offset.x) + dst_extent.width, + .height = static_cast(dst_offset.y) + dst_extent.height, + .layers = 1, + }); + const MSAACopyPushConstants push_constants{ + .dst_offset = {dst_offset.x, dst_offset.y}, + .src_offset = {copy.src_offset.x, copy.src_offset.y}, + .scale = {scale_x, scale_y}, + }; + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([this, pipeline, layout, sampler, renderpass, + framebuffer_handle = *framebuffer, src_view_handle = *src_view, + src = src_image, dst = dst_image, render_area, + push_constants](vk::CommandBuffer cmdbuf) { + constexpr VkImageSubresourceRange color_range{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }; + const std::array pre_barriers{ + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = src, + .subresourceRange = color_range, + }, + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst, + .subresourceRange = color_range, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, nullptr, nullptr, pre_barriers); + const VkRenderPassBeginInfo renderpass_bi{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = nullptr, + .renderPass = renderpass, + .framebuffer = framebuffer_handle, + .renderArea = render_area, + .clearValueCount = 0, + .pClearValues = nullptr, + }; + cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); + const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit(); + UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view_handle); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + const VkViewport viewport{ + .x = static_cast(render_area.offset.x), + .y = static_cast(render_area.offset.y), + .width = static_cast(render_area.extent.width), + .height = static_cast(render_area.extent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + cmdbuf.SetViewport(0, viewport); + cmdbuf.SetScissor(0, render_area); + cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, push_constants); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + const VkImageMemoryBarrier post_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst, + .subresourceRange = color_range, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, post_barrier); + }); + msaa_copy_resources.push_back(MSAACopyResources{ + .tick = scheduler.CurrentTick(), + .src_view = std::move(src_view), + .dst_view = std::move(dst_view), + .framebuffer = std::move(framebuffer), + }); + } + scheduler.InvalidateState(); +} + void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, const ImageView& src_image_view) { const VkPipelineLayout layout = *one_texture_pipeline_layout; @@ -905,7 +1216,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, - .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, .layout = *two_textures_pipeline_layout, .renderPass = key.renderpass, @@ -1025,14 +1336,63 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline( return *clear_stencil_pipelines.back(); } -void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { - if (pipeline) { - return; +VkPipeline BlitImageHelper::FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPipelineKey& key) { + const auto it = std::ranges::find(blit_msaa_color_keys, key); + if (it != blit_msaa_color_keys.end()) { + return *blit_msaa_color_pipelines[std::distance(blit_msaa_color_keys.begin(), it)]; } - VkShaderModule frag_shader = *convert_float_to_depth_frag; - const std::array stages = MakeStages(*full_screen_vert, frag_shader); + blit_msaa_color_keys.push_back(key); + const std::array stages = MakeStages(*full_screen_vert, *blit_color_msaa_frag); + const VkPipelineMultisampleStateCreateInfo multisample_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = key.samples, + .sampleShadingEnable = VK_TRUE, + .minSampleShading = 1.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ + blit_msaa_color_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = nullptr, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *one_texture_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + })); + return *blit_msaa_color_pipelines.back(); +} + +VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass, + bool resolve_stencil) { + auto& keys = resolve_stencil ? resolve_depth_stencil_keys : resolve_depth_keys; + auto& pipelines = resolve_stencil ? resolve_depth_stencil_pipelines : resolve_depth_pipelines; + const auto it = std::ranges::find(keys, renderpass); + if (it != keys.end()) { + return *pipelines[std::distance(keys.begin(), it)]; + } + keys.push_back(renderpass); + const std::array stages = + MakeStages(*full_screen_vert, + resolve_stencil ? *blit_depth_stencil_msaa_frag : *blit_depth_msaa_frag); + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); + pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -1044,25 +1404,41 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, - .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pDepthStencilState = resolve_stencil ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO + : &PIPELINE_DEPTH_ONLY_STATE_CREATE_INFO, .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, - .layout = *one_texture_pipeline_layout, + .layout = resolve_stencil ? *two_textures_pipeline_layout : *one_texture_pipeline_layout, .renderPass = renderpass, .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0, - }); + })); + return *pipelines.back(); } -void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { - if (pipeline) { - return; +VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key) { + const auto it = std::ranges::find(msaa_copy_keys, key); + if (it != msaa_copy_keys.end()) { + return *msaa_copy_pipelines[std::distance(msaa_copy_keys.begin(), it)]; } - VkShaderModule frag_shader = *convert_depth_to_float_frag; - const std::array stages = MakeStages(*full_screen_vert, frag_shader); + msaa_copy_keys.push_back(key); + const std::array stages = MakeStages(*clear_color_vert, key.msaa_to_non_msaa + ? *convert_msaa_to_non_msaa_frag + : *convert_non_msaa_to_msaa_frag); + const VkPipelineMultisampleStateCreateInfo multisample_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = key.samples, + .sampleShadingEnable = key.msaa_to_non_msaa ? VK_FALSE : VK_TRUE, + .minSampleShading = key.msaa_to_non_msaa ? 0.0f : 1.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ + msaa_copy_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -1073,16 +1449,25 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend .pTessellationState = nullptr, .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, - .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, - .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = nullptr, .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, - .layout = *one_texture_pipeline_layout, - .renderPass = renderpass, + .layout = *msaa_copy_pipeline_layout, + .renderPass = key.renderpass, .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0, - }); + })); + return *msaa_copy_pipelines.back(); +} + +void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { + ConvertPipeline(pipeline, renderpass, false); +} + +void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { + ConvertPipeline(pipeline, renderpass, true); } void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass, @@ -1106,7 +1491,8 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .pDepthStencilState = is_target_depth ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO : nullptr, - .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pColorBlendState = is_target_depth ? &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO + : &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, .layout = single_texture ? *one_texture_pipeline_layout : *two_textures_pipeline_layout, .renderPass = renderpass, diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h index 34f1b25f0f..67294d594b 100644 --- a/src/video_core/renderer_vulkan/blit_image.h +++ b/src/video_core/renderer_vulkan/blit_image.h @@ -6,8 +6,12 @@ #pragma once +#include +#include + #include "video_core/engines/fermi_2d.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/surface.h" #include "video_core/texture_cache/types.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -20,6 +24,7 @@ using VideoCommon::Region2D; class Device; class Framebuffer; class ImageView; +class RenderPassCache; class StateTracker; class Scheduler; @@ -40,6 +45,21 @@ struct BlitDepthStencilPipelineKey { u32 stencil_ref; }; +struct MSAACopyPipelineKey { + constexpr auto operator<=>(const MSAACopyPipelineKey&) const noexcept = default; + + VkRenderPass renderpass; + VkSampleCountFlagBits samples; + bool msaa_to_non_msaa; +}; + +struct BlitMSAAPipelineKey { + constexpr auto operator<=>(const BlitMSAAPipelineKey&) const noexcept = default; + + VkRenderPass renderpass; + VkSampleCountFlagBits samples; +}; + class BlitImageHelper { public: explicit BlitImageHelper(const Device& device, Scheduler& scheduler, @@ -55,6 +75,12 @@ public: VkImage src_image, VkSampler src_sampler, const Region2D& dst_region, const Region2D& src_region, const Extent3D& src_size); + void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, + const Region2D& dst_region, const Region2D& src_region); + + void ResolveDepthStencil(const Framebuffer* dst_framebuffer, ImageView& src_image_view, + const Region2D& dst_region, const Region2D& src_region); + void BlitDepthStencil(const Framebuffer* dst_framebuffer, ImageView& src_image_view, const Region2D& dst_region, const Region2D& src_region, Tegra::Engines::Fermi2D::Filter filter, @@ -84,6 +110,12 @@ public: void ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, f32 clear_depth, u8 stencil_mask, u32 stencil_ref, u32 stencil_compare_mask, const Region2D& dst_region); + + void CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat dst_format, VkImage src_image, + VideoCore::Surface::PixelFormat src_format, u32 num_samples, + std::span copies, bool msaa_to_non_msaa); + private: void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, const ImageView& src_image_view); @@ -98,6 +130,10 @@ private: [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key); [[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline( const BlitDepthStencilPipelineKey& key); + [[nodiscard]] VkPipeline FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key); + [[nodiscard]] VkPipeline FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPipelineKey& key); + [[nodiscard]] VkPipeline FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass, + bool resolve_stencil); void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth); @@ -125,9 +161,13 @@ private: vk::PipelineLayout one_texture_pipeline_layout; vk::PipelineLayout two_textures_pipeline_layout; vk::PipelineLayout clear_color_pipeline_layout; + vk::PipelineLayout msaa_copy_pipeline_layout; vk::ShaderModule full_screen_vert; vk::ShaderModule blit_color_to_color_frag; + vk::ShaderModule blit_color_msaa_frag; vk::ShaderModule blit_depth_stencil_frag; + vk::ShaderModule blit_depth_msaa_frag; + vk::ShaderModule blit_depth_stencil_msaa_frag; vk::ShaderModule clear_color_vert; vk::ShaderModule clear_color_frag; vk::ShaderModule clear_stencil_frag; @@ -138,6 +178,8 @@ private: vk::ShaderModule convert_d32f_to_abgr8_frag; vk::ShaderModule convert_d24s8_to_abgr8_frag; vk::ShaderModule convert_s8d24_to_abgr8_frag; + vk::ShaderModule convert_msaa_to_non_msaa_frag; + vk::ShaderModule convert_non_msaa_to_msaa_frag; vk::Sampler linear_sampler; vk::Sampler nearest_sampler; @@ -149,6 +191,21 @@ private: std::vector clear_color_pipelines; std::vector clear_stencil_keys; std::vector clear_stencil_pipelines; + std::vector msaa_copy_keys; + std::vector msaa_copy_pipelines; + std::vector blit_msaa_color_keys; + std::vector blit_msaa_color_pipelines; + std::vector resolve_depth_keys; + std::vector resolve_depth_pipelines; + std::vector resolve_depth_stencil_keys; + std::vector resolve_depth_stencil_pipelines; + struct MSAACopyResources { + u64 tick; + vk::ImageView src_view; + vk::ImageView dst_view; + vk::Framebuffer framebuffer; + }; + std::deque msaa_copy_resources; vk::Pipeline convert_d32_to_r32_pipeline; vk::Pipeline convert_r32_to_d32_pipeline; vk::Pipeline convert_d16_to_r16_pipeline; diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index edc0283d9a..80c434dc8f 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -17,8 +17,6 @@ #include "common/div_ceil.h" #include "common/vector_math.h" #include "video_core/host_shaders/astc_decoder_comp_spv.h" -#include "video_core/host_shaders/convert_msaa_to_non_msaa_comp_spv.h" -#include "video_core/host_shaders/convert_non_msaa_to_msaa_comp_spv.h" #include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h" #include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h" #include "video_core/host_shaders/resolve_conditional_render_comp_spv.h" @@ -140,33 +138,6 @@ constexpr DescriptorBankInfo ASTC_BANK_INFO{ .score = 2, }; -constexpr std::array MSAA_DESCRIPTOR_SET_BINDINGS{{ - { - .binding = 0, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .pImmutableSamplers = nullptr, - }, - { - .binding = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .pImmutableSamplers = nullptr, - }, -}}; - -constexpr DescriptorBankInfo MSAA_BANK_INFO{ - .uniform_buffers = 0, - .storage_buffers = 0, - .texture_buffers = 0, - .image_buffers = 0, - .textures = 0, - .images = 2, - .score = 2, -}; - constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{ .dstBinding = 0, .dstArrayElement = 0, @@ -185,15 +156,6 @@ constexpr VkDescriptorUpdateTemplateEntry QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLAT .stride = sizeof(DescriptorUpdateEntry), }; -constexpr VkDescriptorUpdateTemplateEntry MSAA_DESCRIPTOR_UPDATE_TEMPLATE{ - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 2, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .offset = 0, - .stride = sizeof(DescriptorUpdateEntry), -}; - constexpr std::array ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{ { @@ -910,100 +872,4 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk( }); } -MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_, - DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass(device_, scheduler_, descriptor_pool_, MSAA_DESCRIPTOR_SET_BINDINGS, - MSAA_DESCRIPTOR_UPDATE_TEMPLATE, MSAA_BANK_INFO, {}, - CONVERT_NON_MSAA_TO_MSAA_COMP_SPV), - scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, - compute_pass_descriptor_queue{compute_pass_descriptor_queue_} { - const auto make_msaa_pipeline = [this](size_t i, std::span code) { - modules[i] = device.GetLogical().CreateShaderModule({ - .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .codeSize = static_cast(code.size_bytes()), - .pCode = code.data(), - }); - pipelines[i] = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{ - .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .stage{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .stage = VK_SHADER_STAGE_COMPUTE_BIT, - .module = *modules[i], - .pName = "main", - .pSpecializationInfo = nullptr, - }, - .layout = *layout, - .basePipelineHandle = {}, - .basePipelineIndex = 0, - }); - }; - make_msaa_pipeline(0, CONVERT_NON_MSAA_TO_MSAA_COMP_SPV); - make_msaa_pipeline(1, CONVERT_MSAA_TO_NON_MSAA_COMP_SPV); -} - -MSAACopyPass::~MSAACopyPass() = default; - -void MSAACopyPass::CopyImage(Image& dst_image, Image& src_image, - std::span copies, - bool msaa_to_non_msaa) { - const VkPipeline msaa_pipeline = *pipelines[msaa_to_non_msaa ? 1 : 0]; - scheduler.RequestOutsideRenderPassOperationContext(); - for (const VideoCommon::ImageCopy& copy : copies) { - ASSERT(copy.src_subresource.base_layer == 0); - ASSERT(copy.src_subresource.num_layers == 1); - ASSERT(copy.dst_subresource.base_layer == 0); - ASSERT(copy.dst_subresource.num_layers == 1); - - compute_pass_descriptor_queue.Acquire(scheduler, 2); - compute_pass_descriptor_queue.AddImage( - src_image.StorageImageView(copy.src_subresource.base_level)); - compute_pass_descriptor_queue.AddImage( - dst_image.StorageImageView(copy.dst_subresource.base_level)); - const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; - - const Common::Vec3 num_dispatches = { - Common::DivCeil(copy.extent.width, 8U), - Common::DivCeil(copy.extent.height, 8U), - copy.extent.depth, - }; - - scheduler.Record([this, dst = dst_image.Handle(), msaa_pipeline, num_dispatches, - descriptor_data](vk::CommandBuffer cmdbuf) { - const VkDescriptorSet set = descriptor_allocator.Commit(); - device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); - cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, msaa_pipeline); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); - cmdbuf.Dispatch(num_dispatches.x, num_dispatches.y, num_dispatches.z); - const VkImageMemoryBarrier write_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - .oldLayout = VK_IMAGE_LAYOUT_GENERAL, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = dst, - .subresourceRange{ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier); - }); - } -} - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index cb213dae7e..3d30aa6bdc 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -164,23 +164,4 @@ private: ComputePassDescriptorQueue& compute_pass_descriptor_queue; }; - -class MSAACopyPass final : public ComputePass { -public: - explicit MSAACopyPass(const Device& device_, Scheduler& scheduler_, - DescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_); - ~MSAACopyPass(); - - void CopyImage(Image& dst_image, Image& src_image, - std::span copies, bool msaa_to_non_msaa); - -private: - Scheduler& scheduler; - StagingBufferPool& staging_buffer_pool; - ComputePassDescriptorQueue& compute_pass_descriptor_queue; - std::array modules; - std::array pipelines; -}; - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index c9704b7c6c..073e4b5079 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -48,6 +48,10 @@ public: void Configure(Tegra::Engines::KeplerCompute& kepler_compute, Tegra::MemoryManager& gpu_memory, Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache); + bool IsBound() const noexcept { + return static_cast(pipeline); + } + private: const Device& device; vk::PipelineCache& pipeline_cache; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 9c9a599f2e..757e0f3ccf 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -124,8 +124,8 @@ PixelFormat DecodeFormat(u8 encoded_format) { return PixelFormatFromRenderTargetFormat(format); } -RenderPassKey MakeRenderPassKey(const FixedPipelineState& state) { - RenderPassKey key; +RenderPassKey MakeRenderPassKey(const FixedPipelineState& state, const Device& device) { + RenderPassKey key{}; std::ranges::transform(state.color_formats, key.color_formats.begin(), DecodeFormat); if (state.depth_enabled != 0) { const auto depth_format{static_cast(state.depth_format.Value())}; @@ -134,6 +134,11 @@ RenderPassKey MakeRenderPassKey(const FixedPipelineState& state) { key.depth_format = PixelFormat::Invalid; } key.samples = MaxwellToVK::MsaaMode(state.msaa_mode); + const bool has_color = std::ranges::any_of(key.color_formats, [](PixelFormat format) { + return format != PixelFormat::Invalid; + }); + key.resolve_color = + key.samples != VK_SAMPLE_COUNT_1_BIT && has_color && device.IsTiler(); return key; } @@ -285,9 +290,20 @@ GraphicsPipeline::GraphicsPipeline( descriptor_update_template = builder.CreateTemplate(set_layout, *pipeline_layout, uses_push_descriptor); - const VkRenderPass render_pass{render_pass_cache.Get(MakeRenderPassKey(key.state))}; + const VkRenderPass render_pass{render_pass_cache.Get(MakeRenderPassKey(key.state, device))}; Validate(); - MakePipeline(render_pass); + try { + MakePipeline(render_pass); + } catch (const vk::Exception& exception) { + LOG_CRITICAL(Render_Vulkan, "Graphics pipeline build failed: {}", exception.what()); + std::scoped_lock lock{build_mutex}; + is_built = true; + build_condvar.notify_one(); + if (shader_notify) { + shader_notify->MarkShaderComplete(); + } + return; + } if (pipeline_statistics) { pipeline_statistics->Collect(device, *pipeline); } @@ -519,6 +535,9 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { texture_cache.UpdateRenderTargets(false); texture_cache.CheckFeedbackLoop(std::span{views.data(), views.size()}); + if (IsBuilt() && !pipeline) { + return false; + } ConfigureDraw(rescaling, render_area); return true; @@ -551,6 +570,9 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, uses_render_area = render_area.uses_render_area, render_area_data = render_area.words](vk::CommandBuffer cmdbuf) { if (bind_pipeline) { + if (!pipeline) { + return; + } cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); } cmdbuf.PushConstants(*pipeline_layout, VK_SHADER_STAGE_ALL_GRAPHICS, diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 6ca9fe4c71..0ff4e52c54 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -374,9 +374,11 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_int8 = device.IsInt8Supported(), .support_uniform_and_storage_buffer_8bit = device.IsUniformAndStorageBuffer8BitAccessSupported(), + .support_storage_buffer_8bit = device.IsStorageBuffer8BitAccessSupported(), .support_int16 = device.IsShaderInt16Supported(), .support_uniform_and_storage_buffer_16bit = device.IsUniformAndStorageBuffer16BitAccessSupported(), + .support_storage_buffer_16bit = device.IsStorageBuffer16BitAccessSupported(), .support_int64 = device.IsShaderInt64Supported(), .support_vertex_instance_id = false, .support_float_controls = device.IsKhrShaderFloatControlsSupported(), @@ -395,6 +397,10 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_fp64_signed_zero_nan_preserve = float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE, .support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(), + .support_workgroup_layout_8bit_access = + device.IsWorkgroupMemoryExplicitLayout8BitAccessSupported(), + .support_workgroup_layout_16bit_access = + device.IsWorkgroupMemoryExplicitLayout16BitAccessSupported(), .support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT), .supported_subgroup_stages = supported_subgroup_stages, .support_viewport_index_layer_non_geometry = @@ -404,6 +410,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_demote_to_helper_invocation = device.IsExtShaderDemoteToHelperInvocationSupported(), .support_int64_atomics = device.IsExtShaderAtomicInt64Supported(), + .support_shared_int64_atomics = device.IsSharedInt64AtomicsSupported(), .support_derivative_control = true, .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), .support_native_ndc = device.IsExtDepthClipControlSupported(), @@ -412,6 +419,12 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_geometry_streams = device.AreTransformFeedbackGeometryStreamsSupported(), .support_sampled_image_array_nonuniform_indexing = device.IsSampledImageArrayNonUniformIndexingSupported(), + .support_storage_image_array_nonuniform_indexing = + device.IsStorageImageArrayNonUniformIndexingSupported(), + .support_uniform_texel_buffer_array_nonuniform_indexing = + device.IsUniformTexelBufferArrayNonUniformIndexingSupported(), + .support_storage_texel_buffer_array_nonuniform_indexing = + device.IsStorageTexelBufferArrayNonUniformIndexingSupported(), .warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(), diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 26663d4b25..720f3b868c 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -410,7 +410,23 @@ void RasterizerVulkan::Clear(u32 layer_count) { texture_cache.UpdateRenderTargets(true); const Framebuffer* const framebuffer = texture_cache.GetFramebuffer(); const VkExtent2D render_area = framebuffer->RenderArea(); - scheduler.RequestRenderpass(framebuffer); + + constexpr bool ENABLE_DEFERRED_CLEAR = true; + const bool color_full_channels = regs.clear_surface.R && regs.clear_surface.G && + regs.clear_surface.B && regs.clear_surface.A; + const bool stencil_partial = use_stencil && framebuffer->HasAspectStencilBit() && + regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0; + const bool ds_used = use_depth || use_stencil; + const bool ds_deferrable = + !ds_used || ((!framebuffer->HasAspectDepthBit() || use_depth) && + (!framebuffer->HasAspectStencilBit() || use_stencil) && !stencil_partial); + const bool can_defer_clear = ENABLE_DEFERRED_CLEAR && !regs.clear_control.use_scissor && + regs.clear_surface.layer == 0 && + !scheduler.IsRenderPassActive() && + (!use_color || color_full_channels) && ds_deferrable; + if (!can_defer_clear) { + scheduler.RequestRenderpass(framebuffer); + } query_cache.NotifySegment(true); query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, maxwell3d->regs.zpass_pixel_count_enable); @@ -494,15 +510,19 @@ void RasterizerVulkan::Clear(u32 layer_count) { clear_value.color.int32[i] = s32(f32(s64(int_size - 1) << 1) * (regs.clear_color[i] - 0.5f)); } - if (regs.clear_surface.R && regs.clear_surface.G && regs.clear_surface.B && regs.clear_surface.A) { - scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) { - const VkClearAttachment attachment{ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .colorAttachment = color_attachment, - .clearValue = clear_value, - }; - cmdbuf.ClearAttachments(attachment, clear_rect); - }); + if (color_full_channels) { + if (can_defer_clear) { + scheduler.DeferColorClear(framebuffer, color_attachment, clear_value); + } else { + scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) { + const VkClearAttachment attachment{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .colorAttachment = color_attachment, + .clearValue = clear_value, + }; + cmdbuf.ClearAttachments(attachment, clear_rect); + }); + } } else { u8 color_mask = u8(regs.clear_surface.R | regs.clear_surface.G << 1 | regs.clear_surface.B << 2 | regs.clear_surface.A << 3); Region2D dst_region = { @@ -536,6 +556,11 @@ void RasterizerVulkan::Clear(u32 layer_count) { blit_image.ClearDepthStencil(framebuffer, use_depth, regs.clear_depth, u8(regs.stencil_front_mask), regs.clear_stencil, regs.stencil_front_func_mask, dst_region); + } else if (can_defer_clear) { + VkClearValue ds_value{}; + ds_value.depthStencil.depth = regs.clear_depth; + ds_value.depthStencil.stencil = regs.clear_stencil; + scheduler.DeferDepthStencilClear(framebuffer, ds_value); } else { scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { @@ -569,13 +594,20 @@ void RasterizerVulkan::DispatchCompute() { const auto [buffer, offset] = buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op); scheduler.RequestOutsideRenderPassOperationContext(); - scheduler.Record([indirect_buffer = buffer->Handle(), + scheduler.Record([pipeline, indirect_buffer = buffer->Handle(), indirect_offset = offset](vk::CommandBuffer cmdbuf) { + if (!pipeline->IsBound()) { + return; + } cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset); }); return; } const std::array dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z}; + const std::array max_dim{device.GetMaxComputeWorkGroupCount()}; + if (dim[0] > max_dim[0] || dim[1] > max_dim[1] || dim[2] > max_dim[2]) { + return; + } scheduler.RequestOutsideRenderPassOperationContext(); static constexpr VkMemoryBarrier READ_BARRIER{ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, @@ -585,7 +617,12 @@ void RasterizerVulkan::DispatchCompute() { }; scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, READ_BARRIER); }); - scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); + scheduler.Record([pipeline, dim](vk::CommandBuffer cmdbuf) { + if (!pipeline->IsBound()) { + return; + } + cmdbuf.Dispatch(dim[0], dim[1], dim[2]); + }); // Log compute dispatch if (GPU::Logging::IsActive() && @@ -1309,15 +1346,22 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs) } if (update_references) { [&]() { + bool changed; if (regs.stencil_two_side_enable) { - if (!state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref) && - !state_tracker.CheckStencilReferenceBack(regs.stencil_back_ref)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref); + const bool back_changed = + state_tracker.CheckStencilReferenceBack(regs.stencil_back_ref); + changed = front_changed || back_changed; } else { - if (!state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref); + const bool back_changed = + state_tracker.CheckStencilReferenceBack(regs.stencil_front_ref); + changed = front_changed || back_changed; + } + if (!changed) { + return; } scheduler.Record([front_ref = regs.stencil_front_ref, back_ref = regs.stencil_back_ref, two_sided = regs.stencil_two_side_enable](vk::CommandBuffer cmdbuf) { @@ -1334,15 +1378,22 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs) } if (update_write_mask) { [&]() { + bool changed; if (regs.stencil_two_side_enable) { - if (!state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask) && - !state_tracker.CheckStencilWriteMaskBack(regs.stencil_back_mask)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask); + const bool back_changed = + state_tracker.CheckStencilWriteMaskBack(regs.stencil_back_mask); + changed = front_changed || back_changed; } else { - if (!state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask); + const bool back_changed = + state_tracker.CheckStencilWriteMaskBack(regs.stencil_front_mask); + changed = front_changed || back_changed; + } + if (!changed) { + return; } scheduler.Record([front_write_mask = regs.stencil_front_mask, back_write_mask = regs.stencil_back_mask, @@ -1360,15 +1411,22 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs) } if (update_compare_masks) { [&]() { + bool changed; if (regs.stencil_two_side_enable) { - if (!state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask) && - !state_tracker.CheckStencilCompareMaskBack(regs.stencil_back_func_mask)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask); + const bool back_changed = + state_tracker.CheckStencilCompareMaskBack(regs.stencil_back_func_mask); + changed = front_changed || back_changed; } else { - if (!state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask); + const bool back_changed = + state_tracker.CheckStencilCompareMaskBack(regs.stencil_front_func_mask); + changed = front_changed || back_changed; + } + if (!changed) { + return; } scheduler.Record([front_test_mask = regs.stencil_front_func_mask, back_test_mask = regs.stencil_back_func_mask, diff --git a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp index 118b2a0832..66bade9a2e 100644 --- a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp @@ -44,7 +44,9 @@ using VideoCore::Surface::SurfaceType; } VkAttachmentDescription AttachmentDescription(const Device& device, PixelFormat format, - VkSampleCountFlagBits samples) { + VkSampleCountFlagBits samples, + VkAttachmentLoadOp load_op, + VkAttachmentStoreOp store_op) { using MaxwellToVK::SurfaceFormat; const SurfaceType surface_type = GetSurfaceType(format); @@ -55,12 +57,10 @@ using VideoCore::Surface::SurfaceType; .flags = {}, .format = SurfaceFormat(device, FormatType::Optimal, true, format).format, .samples = samples, - .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, - .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .stencilLoadOp = has_stencil ? VK_ATTACHMENT_LOAD_OP_LOAD - : VK_ATTACHMENT_LOAD_OP_DONT_CARE, - .stencilStoreOp = has_stencil ? VK_ATTACHMENT_STORE_OP_STORE - : VK_ATTACHMENT_STORE_OP_DONT_CARE, + .loadOp = load_op, + .storeOp = store_op, + .stencilLoadOp = has_stencil ? load_op : VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = has_stencil ? store_op : VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_GENERAL, .finalLayout = VK_IMAGE_LAYOUT_GENERAL, }; @@ -87,7 +87,14 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { .layout = VK_IMAGE_LAYOUT_GENERAL, }; if (is_valid) { - descriptions.push_back(AttachmentDescription(*device, format, key.samples)); + const VkAttachmentLoadOp load_op = (key.color_clear_mask & (1u << index)) != 0 + ? VK_ATTACHMENT_LOAD_OP_CLEAR + : VK_ATTACHMENT_LOAD_OP_LOAD; + const VkAttachmentStoreOp store_op = (key.color_discard_mask & (1u << index)) != 0 + ? VK_ATTACHMENT_STORE_OP_DONT_CARE + : VK_ATTACHMENT_STORE_OP_STORE; + descriptions.push_back( + AttachmentDescription(*device, format, key.samples, load_op, store_op)); num_attachments = static_cast(index + 1); ++num_colors; } @@ -99,7 +106,32 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { .attachment = num_colors, .layout = VK_IMAGE_LAYOUT_GENERAL, }; - descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples)); + const VkAttachmentLoadOp depth_load_op = key.depth_stencil_clear + ? VK_ATTACHMENT_LOAD_OP_CLEAR + : VK_ATTACHMENT_LOAD_OP_LOAD; + descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples, + depth_load_op, VK_ATTACHMENT_STORE_OP_STORE)); + } + std::array resolve_references{}; + const bool do_resolve_color = + key.resolve_color && key.samples != VK_SAMPLE_COUNT_1_BIT && num_colors > 0; + if (do_resolve_color) { + for (size_t index = 0; index < key.color_formats.size(); ++index) { + const PixelFormat format{key.color_formats[index]}; + const bool is_valid{format != PixelFormat::Invalid}; + resolve_references[index] = VkAttachmentReference{ + .attachment = is_valid ? static_cast(descriptions.size()) : VK_ATTACHMENT_UNUSED, + .layout = VK_IMAGE_LAYOUT_GENERAL, + }; + if (is_valid) { + VkAttachmentDescription resolve_desc = + AttachmentDescription(*device, format, VK_SAMPLE_COUNT_1_BIT, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, + VK_ATTACHMENT_STORE_OP_STORE); + resolve_desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + descriptions.push_back(resolve_desc); + } + } } const VkSubpassDescription subpass{ .flags = 0, @@ -108,7 +140,7 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { .pInputAttachments = nullptr, .colorAttachmentCount = num_attachments, .pColorAttachments = references.data(), - .pResolveAttachments = nullptr, + .pResolveAttachments = do_resolve_color ? resolve_references.data() : nullptr, .pDepthStencilAttachment = has_depth ? &depth_reference : nullptr, .preserveAttachmentCount = 0, .pPreserveAttachments = nullptr, diff --git a/src/video_core/renderer_vulkan/vk_render_pass_cache.h b/src/video_core/renderer_vulkan/vk_render_pass_cache.h index 5c7b3c2aed..75999a655e 100644 --- a/src/video_core/renderer_vulkan/vk_render_pass_cache.h +++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.h @@ -20,6 +20,10 @@ struct RenderPassKey { std::array color_formats; VideoCore::Surface::PixelFormat depth_format; VkSampleCountFlagBits samples; + bool resolve_color; + u32 color_clear_mask; + bool depth_stencil_clear; + u32 color_discard_mask; }; } // namespace Vulkan @@ -30,6 +34,10 @@ struct hash { [[nodiscard]] size_t operator()(const Vulkan::RenderPassKey& key) const noexcept { size_t value = static_cast(key.depth_format) << 48; value ^= static_cast(key.samples) << 52; + value ^= static_cast(key.resolve_color) << 63; + value ^= static_cast(key.color_clear_mask) << 54; + value ^= static_cast(key.depth_stencil_clear) << 62; + value ^= static_cast(key.color_discard_mask) << 24; for (size_t i = 0; i < key.color_formats.size(); ++i) { value ^= static_cast(key.color_formats[i]) << (i * 6); } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 334d82efff..6234b41978 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -93,30 +93,27 @@ void Scheduler::DispatchWork() { } } -void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) { - const VkRenderPass renderpass = framebuffer->RenderPass(); +void Scheduler::BeginRenderPassImpl(const Framebuffer* framebuffer, VkRenderPass renderpass, + const VkClearValue* clear_values, u32 clear_value_count) { const VkFramebuffer framebuffer_handle = framebuffer->Handle(); const VkExtent2D render_area = framebuffer->RenderArea(); - if (renderpass == state.renderpass && framebuffer_handle == state.framebuffer && - render_area.width == state.render_area.width && - render_area.height == state.render_area.height) { - return; - } - EndRenderPass(); state.renderpass = renderpass; state.framebuffer = framebuffer_handle; state.render_area = render_area; - // Log render pass begin - if (GPU::Logging::IsActive() && - Settings::values.gpu_log_vulkan_calls.GetValue()) { - const std::string render_pass_info = fmt::format( - "renderArea={}x{}, numImages={}", - render_area.width, render_area.height, framebuffer->NumImages()); + if (GPU::Logging::IsActive() && Settings::values.gpu_log_vulkan_calls.GetValue()) { + const std::string render_pass_info = + fmt::format("renderArea={}x{}, numImages={}", render_area.width, render_area.height, + framebuffer->NumImages()); GPU::Logging::GPULogger::GetInstance().LogRenderPassBegin(render_pass_info); } - Record([renderpass, framebuffer_handle, render_area](vk::CommandBuffer cmdbuf) { + std::array values{}; + for (u32 i = 0; i < clear_value_count && i < values.size(); ++i) { + values[i] = clear_values[i]; + } + Record([renderpass, framebuffer_handle, render_area, values, clear_value_count]( + vk::CommandBuffer cmdbuf) { const VkRenderPassBeginInfo renderpass_bi{ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .pNext = nullptr, @@ -127,8 +124,8 @@ void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) { .offset = {.x = 0, .y = 0}, .extent = render_area, }, - .clearValueCount = 0, - .pClearValues = nullptr, + .clearValueCount = clear_value_count, + .pClearValues = clear_value_count != 0 ? values.data() : nullptr, }; cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); }); @@ -137,6 +134,80 @@ void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) { renderpass_image_ranges = framebuffer->ImageRanges(); } +void Scheduler::RealizeDeferredClear() { + if (deferred_clear.framebuffer == nullptr) { + return; + } + const DeferredClear dc = deferred_clear; + deferred_clear = {}; + + std::array clear_values{}; + u32 count = 0; + const RenderPassKey& base = dc.framebuffer->RenderPassKeyBase(); + for (u32 slot = 0; slot < 8; ++slot) { + if (base.color_formats[slot] == VideoCore::Surface::PixelFormat::Invalid) { + continue; + } + clear_values[count++] = dc.color_values[slot]; + } + if (base.depth_format != VideoCore::Surface::PixelFormat::Invalid) { + clear_values[count++] = dc.depth_stencil_value; + } + const u32 color_discard_mask = + dc.framebuffer->DiscardsMsaaColor() ? dc.color_clear_mask : 0u; + const VkRenderPass renderpass = dc.framebuffer->RenderPassVariant( + dc.color_clear_mask, dc.depth_stencil, color_discard_mask); + EndRenderPass(); + BeginRenderPassImpl(dc.framebuffer, renderpass, clear_values.data(), count); +} + +bool Scheduler::DeferColorClear(const Framebuffer* framebuffer, u32 rt_slot, + const VkClearValue& value) { + if (IsRenderPassActive()) { + return false; + } + if (deferred_clear.framebuffer != nullptr && deferred_clear.framebuffer != framebuffer) { + RealizeDeferredClear(); + EndRenderPass(); + } + deferred_clear.framebuffer = framebuffer; + deferred_clear.color_clear_mask |= 1u << rt_slot; + deferred_clear.color_values[rt_slot] = value; + return true; +} + +bool Scheduler::DeferDepthStencilClear(const Framebuffer* framebuffer, const VkClearValue& value) { + if (IsRenderPassActive()) { + return false; + } + if (deferred_clear.framebuffer != nullptr && deferred_clear.framebuffer != framebuffer) { + RealizeDeferredClear(); + EndRenderPass(); + } + deferred_clear.framebuffer = framebuffer; + deferred_clear.depth_stencil = true; + deferred_clear.depth_stencil_value = value; + return true; +} + +void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) { + if (deferred_clear.framebuffer == framebuffer) { + RealizeDeferredClear(); + return; + } + const VkRenderPass renderpass = framebuffer->RenderPass(); + const VkFramebuffer framebuffer_handle = framebuffer->Handle(); + const VkExtent2D render_area = framebuffer->RenderArea(); + if (renderpass == state.renderpass && framebuffer_handle == state.framebuffer && + render_area.width == state.render_area.width && + render_area.height == state.render_area.height) { + return; + } + // Ends any active pass and realizes a deferred clear + EndRenderPass(); + BeginRenderPassImpl(framebuffer, renderpass, nullptr, 0); +} + void Scheduler::RequestOutsideRenderPassOperationContext() { EndRenderPass(); } @@ -308,6 +379,7 @@ void Scheduler::EndPendingOperations() { void Scheduler::EndRenderPass() { + RealizeDeferredClear(); if (!state.renderpass) { return; } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 0709c3a370..5c4af8c146 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -59,6 +59,12 @@ public: /// Requests to begin a renderpass. void RequestRenderpass(const Framebuffer* framebuffer); + /// Defers a full-attachment color clear so it becomes the next render pass. + bool DeferColorClear(const Framebuffer* framebuffer, u32 rt_slot, const VkClearValue& value); + + /// Defers a full depth/stencil clear so it becomes the next render pass. + bool DeferDepthStencilClear(const Framebuffer* framebuffer, const VkClearValue& value); + /// Requests the current execution context to be able to execute operations only allowed outside /// of a renderpass. void RequestOutsideRenderPassOperationContext(); @@ -251,6 +257,21 @@ private: bool needs_state_enable_refresh = false; }; + struct DeferredClear { + const Framebuffer* framebuffer = nullptr; + u32 color_clear_mask = 0; + std::array color_values{}; + bool depth_stencil = false; + VkClearValue depth_stencil_value{}; + }; + + /// Begins a render pass for the given framebuffer, optionally with clear values. + void BeginRenderPassImpl(const Framebuffer* framebuffer, VkRenderPass renderpass, + const VkClearValue* clear_values, u32 clear_value_count); + + /// If a deferred clear is pending. + void RealizeDeferredClear(); + void WorkerThread(std::stop_token stop_token); void AllocateWorkerCommandBuffer(); @@ -276,6 +297,8 @@ private: vk::CommandBuffer current_cmdbuf; vk::CommandBuffer current_upload_cmdbuf; + DeferredClear deferred_clear; + std::unique_ptr chunk; std::function on_submit; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 960dafb99d..44a4227a19 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -52,6 +52,9 @@ using VideoCore::Surface::IsPixelFormatInteger; using VideoCore::Surface::SurfaceType; namespace { +constexpr bool ENABLE_MSAA_RESOLVE_CONSUME = true; +constexpr bool ENABLE_MSAA_COLOR_DISCARD = true; + constexpr VkBorderColor ConvertBorderColor(const std::array& color) { if (color == std::array{0, 0, 0, 0}) { return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; @@ -909,9 +912,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue, memory_allocator); } - if (device.IsStorageImageMultisampleSupported()) { - msaa_copy_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue); - } if (!device.IsKhrImageFormatListSupported()) { return; } @@ -995,12 +995,84 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) { return *buffers[level]; } +VkImageView TextureCacheRuntime::GetOrCreateResolveShadow(VkImage msaa_image, VkFormat format, + VkExtent2D extent, u32 layers) { + ResolveShadow& shadow = resolve_shadows[msaa_image]; + if (shadow.image && shadow.format == format && shadow.extent.width == extent.width && + shadow.extent.height == extent.height && shadow.layers == layers) { + shadow.up_to_date = true; + return *shadow.view; + } + shadow.image = memory_allocator.CreateImage(VkImageCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = {extent.width, extent.height, 1}, + .mipLevels = 1, + .arrayLayers = layers, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }); + shadow.view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = *shadow.image, + .viewType = layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components{}, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = layers, + }, + }); + shadow.format = format; + shadow.extent = extent; + shadow.layers = layers; + shadow.up_to_date = true; + return *shadow.view; +} + +const TextureCacheRuntime::ResolveShadow* TextureCacheRuntime::GetValidResolveShadow( + VkImage msaa_image) const { + const auto it = resolve_shadows.find(msaa_image); + if (it == resolve_shadows.end() || !it->second.up_to_date || !it->second.image) { + return nullptr; + } + return &it->second; +} + +void TextureCacheRuntime::InvalidateResolveShadow(VkImage msaa_image) { + const auto it = resolve_shadows.find(msaa_image); + if (it != resolve_shadows.end()) { + it->second.up_to_date = false; + } +} + +void TextureCacheRuntime::EraseResolveShadow(VkImage msaa_image) { + resolve_shadows.erase(msaa_image); +} + void TextureCacheRuntime::BarrierFeedbackLoop() { scheduler.RequestOutsideRenderPassOperationContext(); } void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src, std::span copies) { + if (ENABLE_MSAA_RESOLVE_CONSUME) { + InvalidateResolveShadow(dst.Handle()); + } boost::container::small_vector vk_in_copies(copies.size()); boost::container::small_vector vk_out_copies(copies.size()); const VkImageAspectFlags src_aspect_mask = src.AspectMask(); @@ -1147,6 +1219,15 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst return; } ASSERT(src.format == dst.format); + if (is_src_msaa && !is_dst_msaa && + (aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { + if ((aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) == 0) { + UNIMPLEMENTED_MSG("Stencil-only MSAA resolve is not supported"); + return; + } + blit_image_helper.ResolveDepthStencil(dst_framebuffer, src, dst_region, src_region); + return; + } if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { const auto format = src.format; const auto can_blit_depth_stencil = [this, format] { @@ -1172,19 +1253,20 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst ASSERT(!(is_dst_msaa && !is_src_msaa)); ASSERT(operation == Fermi2D::Operation::SrcCopy); - const VkImage dst_image = dst.ImageHandle(); - const VkImage src_image = src.ImageHandle(); - const VkImageSubresourceLayers dst_layers = MakeSubresourceLayers(&dst); - const VkImageSubresourceLayers src_layers = MakeSubresourceLayers(&src); const bool is_msaa_to_msaa = is_src_msaa && is_dst_msaa; - - // NVIDIA 510+ and Intel crash on MSAA->MSAA blits (scaling operations) - // Fall back to 3D helpers for MSAA scaling + if (is_msaa_to_msaa && aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT) { + blit_image_helper.BlitColorMSAA(dst_framebuffer, src, dst_region, src_region); + return; + } if (is_msaa_to_msaa && device.CantBlitMSAA()) { - // This should be handled by NeedsScaleHelper() and use 3D helpers instead - UNIMPLEMENTED_MSG("MSAA to MSAA blit not supported on this driver"); + UNIMPLEMENTED_MSG("MSAA to MSAA depth-stencil blit is not supported on this driver"); return; } + + const VkImage dst_image = dst.ImageHandle(); + const VkImage src_image = src.ImageHandle(); + const VkImageSubresourceLayers dst_layers = MakeSubresourceLayers(&dst); + const VkImageSubresourceLayers src_layers = MakeSubresourceLayers(&src); const bool is_resolve = is_src_msaa && !is_dst_msaa; scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([filter, dst_region, src_region, dst_image, src_image, dst_layers, src_layers, @@ -1442,6 +1524,9 @@ bool TextureCacheRuntime::IsFormatScalable(PixelFormat format) { void TextureCacheRuntime::CopyImage(Image& dst, Image& src, std::span copies) { + if (ENABLE_MSAA_RESOLVE_CONSUME) { + InvalidateResolveShadow(dst.Handle()); + } // As per the size-compatible formats section of vulkan, copy manually via ReinterpretImage // these images that aren't size-compatible if (BytesPerBlock(src.info.format) != BytesPerBlock(dst.info.format)) { @@ -1555,10 +1640,117 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src, void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src, std::span copies) { const bool msaa_to_non_msaa = src.info.num_samples > 1 && dst.info.num_samples == 1; - if (msaa_copy_pass) { - return msaa_copy_pass->CopyImage(dst, src, copies, msaa_to_non_msaa); + const u32 num_samples = msaa_to_non_msaa ? src.info.num_samples : dst.info.num_samples; + if (dst.AspectMask() != VK_IMAGE_ASPECT_COLOR_BIT || + VideoCore::Surface::IsPixelFormatInteger(dst.info.format)) { + UNIMPLEMENTED_MSG("Copying images with different samples is not supported."); + return; + } + if (ENABLE_MSAA_RESOLVE_CONSUME && msaa_to_non_msaa && copies.size() == 1 && + src.info.format == dst.info.format) { + const VideoCommon::ImageCopy& copy = copies.front(); + const ResolveShadow* const shadow = GetValidResolveShadow(src.Handle()); + if (shadow != nullptr && copy.src_offset.x == 0 && copy.src_offset.y == 0 && + copy.src_subresource.base_level == 0 && + static_cast(copy.extent.width) <= shadow->extent.width && + static_cast(copy.extent.height) <= shadow->extent.height) { + const VkImage shadow_image = *shadow->image; + const VkImage dst_image = dst.Handle(); + const VkImageCopy region{ + .srcSubresource{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = static_cast(copy.src_subresource.base_layer), + .layerCount = static_cast(copy.src_subresource.num_layers), + }, + .srcOffset = {0, 0, 0}, + .dstSubresource{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = static_cast(copy.dst_subresource.base_level), + .baseArrayLayer = static_cast(copy.dst_subresource.base_layer), + .layerCount = static_cast(copy.dst_subresource.num_layers), + }, + .dstOffset = {copy.dst_offset.x, copy.dst_offset.y, copy.dst_offset.z}, + .extent = {copy.extent.width, copy.extent.height, 1}, + }; + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([shadow_image, dst_image, region](vk::CommandBuffer cmdbuf) { + const std::array pre_barriers{ + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = shadow_image, + .subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, + VK_REMAINING_ARRAY_LAYERS}, + }, + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst_image, + .subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, + VK_REMAINING_ARRAY_LAYERS}, + }, + }; + const std::array post_barriers{ + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = shadow_image, + .subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, + VK_REMAINING_ARRAY_LAYERS}, + }, + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst_image, + .subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, + VK_REMAINING_ARRAY_LAYERS}, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, nullptr, nullptr, + pre_barriers); + cmdbuf.CopyImage(shadow_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, region); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, nullptr, nullptr, + post_barriers); + }); + return; + } } - UNIMPLEMENTED_MSG("Copying images with different samples is not supported."); + blit_image_helper.CopyMSAA(render_pass_cache, dst.Handle(), dst.info.format, src.Handle(), + src.info.format, num_samples, copies, msaa_to_non_msaa); } u64 TextureCacheRuntime::GetDeviceLocalMemory() const { @@ -1577,7 +1769,11 @@ std::optional TextureCacheRuntime::GetSamplerHeapBudget() const { return device.GetSamplerHeapBudget(); } -void TextureCacheRuntime::TickFrame() {} +void TextureCacheRuntime::TickFrame() { + std::erase_if(pending_msaa_images, [this](const auto& pending) { + return scheduler.IsFree(pending.first); + }); +} Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_) @@ -1632,7 +1828,16 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBase{params} {} -Image::~Image() = default; +Image::~Image() { + if (ENABLE_MSAA_RESOLVE_CONSUME && runtime != nullptr) { + if (original_image) { + runtime->EraseResolveShadow(*original_image); + } + if (scaled_image) { + runtime->EraseResolveShadow(*scaled_image); + } + } +} void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) { using VideoCore::Surface::BytesPerBlock; @@ -1679,82 +1884,75 @@ void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) { void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset, std::span copies) { // TODO: Move this to another API + if (ENABLE_MSAA_RESOLVE_CONSUME && runtime != nullptr) { + runtime->InvalidateResolveShadow(Handle()); + } const bool is_rescaled = True(flags & ImageFlagBits::Rescaled); if (is_rescaled) { ScaleDown(true); } - // Handle MSAA upload if necessary - /* WARNING, TODO: This code uses some hacks, besides being fundamentally ugly - since tropic didn't want to touch it for a long time, so it needs a rewrite from someone - better than me at vulkan. */ - // CHANGE: Gate the MSAA path more strictly and only use it for color, when the pass and device - // support are available. Avoid running the MSAA path when prerequisites aren't met, - // preventing validation and runtime issues. const bool wants_msaa_upload = info.num_samples > 1 && (aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0 - && runtime->CanUploadMSAA() && runtime->msaa_copy_pass.has_value() - && runtime->device.IsStorageImageMultisampleSupported(); + && !VideoCore::Surface::IsPixelFormatInteger(info.format); if (wants_msaa_upload) { - // Create a temporary non-MSAA image to upload the data first ImageInfo temp_info = info; temp_info.num_samples = 1; - // CHANGE: Build a fresh VkImageCreateInfo with robust usage flags for the temp image. - // Using the target image's usage as-is could miss STORAGE/TRANSFER bits and trigger - // validation errors. VkImageCreateInfo image_ci = MakeImageCreateInfo(runtime->device, temp_info); - image_ci.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; - - // CHANGE: The previous stack-allocated wrapper was destroyed at function exit, - // which could destroy VkImage before the GPU used it. - auto temp_wrapper = std::make_shared(*runtime, temp_info, 0, 0); - temp_wrapper->original_image = runtime->memory_allocator.CreateImage(image_ci); - temp_wrapper->current_image = &Image::original_image; - temp_wrapper->aspect_mask = aspect_mask; - temp_wrapper->initialized = true; - - // Upload to the temporary non-MSAA image + image_ci.format = + MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format) + .format; + image_ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + vk::Image temp_image = runtime->memory_allocator.CreateImage(image_ci); + scheduler->RequestOutsideRenderPassOperationContext(); - auto vk_copies = TransformBufferImageCopies(copies, offset, temp_wrapper->aspect_mask); + auto vk_copies = TransformBufferImageCopies(copies, offset, aspect_mask); const VkBuffer src_buffer = buffer; - const VkImage temp_vk_image = *temp_wrapper->original_image; - const VkImageAspectFlags vk_aspect_mask = temp_wrapper->aspect_mask; + const VkImage temp_vk_image = *temp_image; + const VkImageAspectFlags vk_aspect_mask = aspect_mask; - scheduler->Record([src_buffer, temp_vk_image, vk_aspect_mask, vk_copies, - keep = temp_wrapper](vk::CommandBuffer cmdbuf) { + scheduler->Record([src_buffer, temp_vk_image, vk_aspect_mask, + vk_copies](vk::CommandBuffer cmdbuf) { CopyBufferToImage(cmdbuf, src_buffer, temp_vk_image, vk_aspect_mask, false, VideoCommon::FixSmallVectorADL(vk_copies)); }); - // Use MSAACopyPass to convert from non-MSAA to MSAA + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples); std::vector image_copies; image_copies.reserve(copies.size()); for (const auto& copy : copies) { VideoCommon::ImageCopy image_copy{}; - image_copy.src_offset = {0, 0, 0}; // Use zero offset for source - image_copy.dst_offset = copy.image_offset; + image_copy.src_offset = {0, 0, 0}; + image_copy.dst_offset = {copy.image_offset.x >> samples_x, + copy.image_offset.y >> samples_y, copy.image_offset.z}; image_copy.src_subresource = copy.image_subresource; image_copy.dst_subresource = copy.image_subresource; - image_copy.extent = copy.image_extent; + image_copy.extent = {copy.image_extent.width >> samples_x, + copy.image_extent.height >> samples_y, copy.image_extent.depth}; image_copies.push_back(image_copy); } - runtime->msaa_copy_pass->CopyImage(*this, *temp_wrapper, image_copies, - /*msaa_to_non_msaa=*/false); - std::exchange(initialized, true); + runtime->blit_image_helper.CopyMSAA(runtime->render_pass_cache, Handle(), info.format, + temp_vk_image, info.format, info.num_samples, + image_copies, false); + initialized = true; + runtime->pending_msaa_images.emplace_back(scheduler->CurrentTick(), std::move(temp_image)); - const u64 tick = scheduler->Flush(); - scheduler->Wait(tick); + if (is_rescaled) { + ScaleUp(); + } + return; + } + if (info.num_samples > 1) { + LOG_WARNING(Render_Vulkan, "MSAA upload not implemented for format {}", info.format); if (is_rescaled) { ScaleUp(); } return; } - // Regular non-MSAA upload (original behavior preserved) scheduler->RequestOutsideRenderPassOperationContext(); auto vk_copies = TransformBufferImageCopies(copies, offset, aspect_mask); const VkBuffer src_buffer = buffer; @@ -1794,22 +1992,45 @@ void Image::DownloadMemory(std::span buffers_span, std::span o ScaleDown(); } - // RE-USE MSAA UPLOAD CODE BUT NOW FOR DOWNLOAD - if (info.num_samples > 1 && runtime->msaa_copy_pass) { - // TODO: Depth/stencil formats need special handling - if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT) { + if (info.num_samples > 1) { + if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT && + !VideoCore::Surface::IsPixelFormatInteger(info.format)) { ImageInfo temp_info = info; temp_info.num_samples = 1; VkImageCreateInfo image_ci = MakeImageCreateInfo(runtime->device, temp_info); - image_ci.usage = original_image.UsageFlags(); + image_ci.format = + MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format) + .format; + image_ci.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; vk::Image temp_image = runtime->memory_allocator.CreateImage(image_ci); + const VkImage temp_vk_image = *temp_image; - Image temp_wrapper(*runtime, temp_info, 0, 0); - temp_wrapper.original_image = std::move(temp_image); - temp_wrapper.current_image = &Image::original_image; - temp_wrapper.aspect_mask = aspect_mask; - temp_wrapper.initialized = true; + scheduler->RequestOutsideRenderPassOperationContext(); + scheduler->Record([temp_vk_image](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier init_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = temp_vk_image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, + init_barrier); + }); std::vector image_copies; for (const auto& copy : copies) { @@ -1822,7 +2043,9 @@ void Image::DownloadMemory(std::span buffers_span, std::span o image_copies.push_back(image_copy); } - runtime->msaa_copy_pass->CopyImage(temp_wrapper, *this, image_copies, true); + runtime->blit_image_helper.CopyMSAA(runtime->render_pass_cache, temp_vk_image, + info.format, Handle(), info.format, + info.num_samples, image_copies, true); boost::container::small_vector buffers_vector{}; boost::container::small_vector, 8> @@ -1834,7 +2057,7 @@ void Image::DownloadMemory(std::span buffers_span, std::span o } scheduler->RequestOutsideRenderPassOperationContext(); - scheduler->Record([buffers = std::move(buffers_vector), image = *temp_wrapper.original_image, + scheduler->Record([buffers = std::move(buffers_vector), image = temp_vk_image, aspect_mask_ = aspect_mask, vk_copies](vk::CommandBuffer cmdbuf) { const VkImageMemoryBarrier read_barrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, @@ -1889,6 +2112,8 @@ void Image::DownloadMemory(std::span buffers_span, std::span o cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, memory_write_barrier, nullptr, image_write_barrier); }); + runtime->pending_msaa_images.emplace_back(scheduler->CurrentTick(), + std::move(temp_image)); return; } } else { @@ -2074,10 +2299,11 @@ bool Image::BlitScaleHelper(bool scale_up) { blit_view = std::make_unique(*runtime, view_info, NULL_IMAGE_ID, *this); } - const u32 src_width = scale_up ? info.size.width : scaled_width; - const u32 src_height = scale_up ? info.size.height : scaled_height; - const u32 dst_width = scale_up ? scaled_width : info.size.width; - const u32 dst_height = scale_up ? scaled_height : info.size.height; + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples); + const u32 src_width = (scale_up ? info.size.width : scaled_width) >> samples_x; + const u32 src_height = (scale_up ? info.size.height : scaled_height) >> samples_y; + const u32 dst_width = (scale_up ? scaled_width : info.size.width) >> samples_x; + const u32 dst_height = (scale_up ? scaled_height : info.size.height) >> samples_y; const Region2D src_region{ .start = {0, 0}, .end = {s32(src_width), s32(src_height)}, @@ -2087,17 +2313,23 @@ bool Image::BlitScaleHelper(bool scale_up) { .end = {s32(dst_width), s32(dst_height)}, }; const VkExtent2D extent{ - .width = (std::max)(scaled_width, info.size.width), - .height = (std::max)(scaled_height, info.size.height), + .width = (std::max)(scaled_width, info.size.width) >> samples_x, + .height = (std::max)(scaled_height, info.size.height) >> samples_y, }; auto* view_ptr = blit_view.get(); if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT) { if (!blit_framebuffer) blit_framebuffer.emplace(*runtime, view_ptr, nullptr, extent, scale_up); - runtime->blit_image_helper.BlitColor(&*blit_framebuffer, *blit_view, - dst_region, src_region, operation, BLIT_OPERATION); - } else if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { + if (info.num_samples > 1) { + runtime->blit_image_helper.BlitColorMSAA(&*blit_framebuffer, *blit_view, + dst_region, src_region); + } else { + runtime->blit_image_helper.BlitColor(&*blit_framebuffer, *blit_view, + dst_region, src_region, operation, BLIT_OPERATION); + } + } else if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) && + info.num_samples == 1) { if (!blit_framebuffer) blit_framebuffer.emplace(*runtime, nullptr, view_ptr, extent, scale_up); runtime->blit_image_helper.BlitDepthStencil(&*blit_framebuffer, *blit_view, @@ -2113,7 +2345,8 @@ bool Image::BlitScaleHelper(bool scale_up) { bool Image::NeedsScaleHelper() const { const auto& device = runtime->device; - const bool needs_msaa_helper = info.num_samples > 1 && device.CantBlitMSAA(); + const bool needs_msaa_helper = info.num_samples > 1 && + (device.CantBlitMSAA() || aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT); if (needs_msaa_helper) { return true; } @@ -2515,11 +2748,76 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, renderpass_key.depth_format = PixelFormat::Invalid; } renderpass_key.samples = samples; + const bool do_resolve_color = + samples != VK_SAMPLE_COUNT_1_BIT && num_colors > 0 && runtime.device.IsTiler(); + renderpass_key.resolve_color = do_resolve_color; + + discard_msaa_color = + ENABLE_MSAA_RESOLVE_CONSUME && ENABLE_MSAA_COLOR_DISCARD && do_resolve_color; renderpass = runtime.render_pass_cache.Get(renderpass_key); + render_pass_key = renderpass_key; + render_pass_cache = &runtime.render_pass_cache; render_area.width = (std::min)(render_area.width, width); render_area.height = (std::min)(render_area.height, height); + if (do_resolve_color) { + const u32 layers = static_cast((std::max)(num_layers, 1)); + for (size_t index = 0; index < NUM_RT; ++index) { + const PixelFormat format = renderpass_key.color_formats[index]; + if (format == PixelFormat::Invalid) { + continue; + } + const VkFormat vk_format = + MaxwellToVK::SurfaceFormat(runtime.device, FormatType::Optimal, true, format).format; + if (ENABLE_MSAA_RESOLVE_CONSUME) { + const VkImage msaa_image = images[rt_map[index]]; + attachments.push_back(runtime.GetOrCreateResolveShadow(msaa_image, vk_format, + render_area, layers)); + continue; + } + VkImageCreateInfo resolve_ci{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = vk_format, + .extent = {render_area.width, render_area.height, 1}, + .mipLevels = 1, + .arrayLayers = layers, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + vk::Image resolve_image = runtime.memory_allocator.CreateImage(resolve_ci); + vk::ImageView resolve_view = + runtime.device.GetLogical().CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = *resolve_image, + .viewType = layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D, + .format = vk_format, + .components{}, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = layers, + }, + }); + attachments.push_back(*resolve_view); + resolve_images.push_back(std::move(resolve_image)); + resolve_image_views.push_back(std::move(resolve_view)); + } + } + num_color_buffers = static_cast(num_colors); framebuffer = runtime.device.GetLogical().CreateFramebuffer({ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, @@ -2534,6 +2832,18 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, }); } +VkRenderPass Framebuffer::RenderPassVariant(u32 color_clear_mask, bool depth_stencil_clear, + u32 color_discard_mask) const { + if (color_clear_mask == 0 && !depth_stencil_clear && color_discard_mask == 0) { + return renderpass; + } + RenderPassKey key = render_pass_key; + key.color_clear_mask = color_clear_mask; + key.depth_stencil_clear = depth_stencil_clear; + key.color_discard_mask = color_discard_mask; + return render_pass_cache->Get(key); +} + void TextureCacheRuntime::AccelerateImageUpload( Image& image, const StagingBufferRef& map, std::span swizzles, diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 13f51555a3..99f2978cca 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -12,6 +12,7 @@ #include "shader_recompiler/shader_info.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" +#include "video_core/renderer_vulkan/vk_render_pass_cache.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/texture_cache/image_view_base.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h" @@ -87,7 +88,7 @@ public: } bool CanUploadMSAA() const noexcept { - return msaa_copy_pass.operator bool(); + return true; } void AccelerateImageUpload(Image&, const StagingBufferRef&, @@ -110,6 +111,24 @@ public: [[nodiscard]] VkBuffer GetTemporaryBuffer(size_t needed_size); + struct ResolveShadow { + vk::Image image; + vk::ImageView view; + VkFormat format = VK_FORMAT_UNDEFINED; + VkExtent2D extent{}; + u32 layers = 0; + bool up_to_date = false; + }; + + [[nodiscard]] VkImageView GetOrCreateResolveShadow(VkImage msaa_image, VkFormat format, + VkExtent2D extent, u32 layers); + + [[nodiscard]] const ResolveShadow* GetValidResolveShadow(VkImage msaa_image) const; + + void InvalidateResolveShadow(VkImage msaa_image); + + void EraseResolveShadow(VkImage msaa_image); + std::span ViewFormats(PixelFormat format) { return view_formats[static_cast(format)]; } @@ -130,12 +149,13 @@ public: std::optional astc_decoder_pass; std::optional bl3d_unswizzle_pass; - std::optional msaa_copy_pass; const Settings::ResolutionScalingInfo& resolution; std::array, VideoCore::Surface::MaxPixelFormat> view_formats; static constexpr size_t indexing_slots = 8 * sizeof(size_t); std::array buffers{}; + std::vector> pending_msaa_images; + ankerl::unordered_dense::map resolve_shadows; }; class Framebuffer { @@ -166,6 +186,13 @@ public: return renderpass; } + [[nodiscard]] const RenderPassKey& RenderPassKeyBase() const noexcept { + return render_pass_key; + } + + [[nodiscard]] VkRenderPass RenderPassVariant(u32 color_clear_mask, bool depth_stencil_clear, + u32 color_discard_mask) const; + [[nodiscard]] VkExtent2D RenderArea() const noexcept { return render_area; } @@ -206,6 +233,18 @@ public: return is_rescaled; } + [[nodiscard]] bool HasResolveColor() const noexcept { + return !resolve_images.empty(); + } + + [[nodiscard]] VkImage ResolveColorImage(size_t index) const noexcept { + return index < resolve_images.size() ? *resolve_images[index] : VK_NULL_HANDLE; + } + + [[nodiscard]] bool DiscardsMsaaColor() const noexcept { + return discard_msaa_color; + } + private: vk::Framebuffer framebuffer; VkRenderPass renderpass{}; @@ -219,6 +258,11 @@ private: bool has_depth{}; bool has_stencil{}; bool is_rescaled{}; + std::vector resolve_images; + std::vector resolve_image_views; + RenderPassKey render_pass_key{}; + RenderPassCache* render_pass_cache{nullptr}; + bool discard_msaa_color{}; }; class Image : public VideoCommon::ImageBase { diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index ef02972209..8b59eb6841 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 8bddca0c70..8fd9b6e69f 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -504,8 +504,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR CollectToolingInfo(); if (is_qualcomm) { - LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation"); must_emulate_scaled_formats = true; + LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation."); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken custom border color."); RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); @@ -523,11 +523,10 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR features.shader_atomic_int64.shaderBufferInt64Atomics = false; features.shader_atomic_int64.shaderSharedInt64Atomics = false; features.features.shaderInt64 = false; - LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken storage buffer access."); - features.bit8_storage.storageBuffer8BitAccess = false; - features.bit8_storage.uniformAndStorageBuffer8BitAccess = false; - features.bit16_storage.storageBuffer16BitAccess = false; - features.bit16_storage.uniformAndStorageBuffer16BitAccess = false; + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken workgroup memory explicit layout."); + RemoveExtensionFeature(extensions.workgroup_memory_explicit_layout, + features.workgroup_memory_explicit_layout, + VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); #if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) // BCn patching only safe on Android 9+ (API 28+). Older versions crash on driver load. @@ -1356,10 +1355,7 @@ void Device::RemoveUnsuitableExtensions() { // VK_KHR_workgroup_memory_explicit_layout extensions.workgroup_memory_explicit_layout = - features.features.shaderInt16 && features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout && - features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess && - features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess && features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout; RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout, features.workgroup_memory_explicit_layout, diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index db92df70a6..c449f60324 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -305,6 +305,19 @@ public: return properties.driver.driverID; } + /// Returns true for tile-based deferred renderers. + bool IsTiler() const { + switch (GetDriverID()) { + case VK_DRIVER_ID_QUALCOMM_PROPRIETARY: + case VK_DRIVER_ID_ARM_PROPRIETARY: + case VK_DRIVER_ID_SAMSUNG_PROPRIETARY: + case VK_DRIVER_ID_MESA_TURNIP: + return true; + default: + return false; + } + } + bool ShouldBoostClocks() const; /// Returns uniform buffer alignment requirement. @@ -322,6 +335,11 @@ public: return properties.properties.limits.maxStorageBufferRange; } + std::array GetMaxComputeWorkGroupCount() const { + const auto& count = properties.properties.limits.maxComputeWorkGroupCount; + return {count[0], count[1], count[2]}; + } + /// Returns the maximum size for push constants. VkDeviceSize GetMaxPushConstantsSize() const { return properties.properties.limits.maxPushConstantsSize; @@ -374,6 +392,18 @@ FN_MAX_LIMIT_LIST return features.descriptor_indexing.shaderSampledImageArrayNonUniformIndexing; } + bool IsStorageImageArrayNonUniformIndexingSupported() const { + return features.descriptor_indexing.shaderStorageImageArrayNonUniformIndexing; + } + + bool IsUniformTexelBufferArrayNonUniformIndexingSupported() const { + return features.descriptor_indexing.shaderUniformTexelBufferArrayNonUniformIndexing; + } + + bool IsStorageTexelBufferArrayNonUniformIndexingSupported() const { + return features.descriptor_indexing.shaderStorageTexelBufferArrayNonUniformIndexing; + } + /// Returns true if the device supports float64 natively. bool IsFloat64Supported() const { return features.features.shaderFloat64; @@ -513,6 +543,18 @@ FN_MAX_LIMIT_LIST return extensions.workgroup_memory_explicit_layout; } + bool IsWorkgroupMemoryExplicitLayout8BitAccessSupported() const { + return extensions.workgroup_memory_explicit_layout && + features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess && + features.shader_float16_int8.shaderInt8; + } + + bool IsWorkgroupMemoryExplicitLayout16BitAccessSupported() const { + return extensions.workgroup_memory_explicit_layout && + features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess && + features.features.shaderInt16; + } + /// Returns true if the device supports VK_KHR_image_format_list. bool IsKhrImageFormatListSupported() const { return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2; @@ -765,6 +807,11 @@ FN_MAX_LIMIT_LIST return extensions.shader_atomic_int64; } + bool IsSharedInt64AtomicsSupported() const { + return extensions.shader_atomic_int64 && + features.shader_atomic_int64.shaderSharedInt64Atomics; + } + bool IsExtConditionalRendering() const { return extensions.conditional_rendering; }