diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 0447dfe90d..f73fb233cb 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -403,6 +403,9 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct if (info.uses_sampled_1d) { ctx.AddCapability(spv::Capability::Sampled1D); } + if (info.uses_image_1d) { + ctx.AddCapability(spv::Capability::Image1D); + } if (info.uses_sparse_residency) { ctx.AddCapability(spv::Capability::SparseResidency); } 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 fb9d3dbbb2..f4d9de79d3 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 @@ -202,7 +202,8 @@ void EmitGetIndirectBranchVariable(EmitContext&) { } Id EmitGetCbufU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8 && + ctx.profile.support_uniform_and_storage_buffer_8bit) { const Id load{GetCbuf(ctx, ctx.U8, &UniformDefinitions::U8, sizeof(u8), binding, offset, ctx.load_const_func_u8)}; return ctx.OpUConvert(ctx.U32[1], load); @@ -219,7 +220,8 @@ Id EmitGetCbufU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& of } Id EmitGetCbufS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8 && + ctx.profile.support_uniform_and_storage_buffer_8bit) { const Id load{GetCbuf(ctx, ctx.S8, &UniformDefinitions::S8, sizeof(s8), binding, offset, ctx.load_const_func_u8)}; return ctx.OpSConvert(ctx.U32[1], load); @@ -236,7 +238,8 @@ Id EmitGetCbufS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& of } Id EmitGetCbufU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16 && + ctx.profile.support_uniform_and_storage_buffer_16bit) { const Id load{GetCbuf(ctx, ctx.U16, &UniformDefinitions::U16, sizeof(u16), binding, offset, ctx.load_const_func_u16)}; return ctx.OpUConvert(ctx.U32[1], load); @@ -253,7 +256,8 @@ Id EmitGetCbufU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& o } Id EmitGetCbufS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16 && + ctx.profile.support_uniform_and_storage_buffer_16bit) { const Id load{GetCbuf(ctx, ctx.S16, &UniformDefinitions::S16, sizeof(s16), binding, offset, ctx.load_const_func_u16)}; return ctx.OpSConvert(ctx.U32[1], load); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index df4da81356..0b3a1a7c08 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -260,6 +260,13 @@ bool IsTextureMsaa(EmitContext& ctx, const IR::TextureInstInfo& info) { return ctx.textures.at(info.descriptor_index).is_multisample; } +bool IsTextureInteger(EmitContext& ctx, const IR::TextureInstInfo& info) { + if (info.type == TextureType::Buffer) { + return false; + } + return ctx.textures.at(info.descriptor_index).is_integer; +} + Id Decorate(EmitContext& ctx, IR::Inst* inst, Id sample) { const auto info{inst->Flags()}; if (info.relaxed_precision != 0) { @@ -480,11 +487,14 @@ Id EmitBoundImageWrite(EmitContext&) { Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id bias_lc, const IR::Value& offset) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; + Id color; if (ctx.stage == Stage::Fragment) { const ImageOperands operands(ctx, info.has_bias != 0, false, info.has_lod_clamp != 0, bias_lc, offset); - return Emit(&EmitContext::OpImageSparseSampleImplicitLod, - &EmitContext::OpImageSampleImplicitLod, ctx, inst, ctx.F32[4], + color = Emit(&EmitContext::OpImageSparseSampleImplicitLod, + &EmitContext::OpImageSampleImplicitLod, ctx, inst, result_type, Texture(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); } else { // We can't use implicit lods on non-fragment stages on SPIR-V. Maxwell hardware behaves as @@ -492,26 +502,29 @@ Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& // derivatives const Id lod{ctx.Const(0.0f)}; const ImageOperands operands(ctx, false, true, info.has_lod_clamp != 0, lod, offset); - return Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], + color = Emit(&EmitContext::OpImageSparseSampleExplicitLod, + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); } + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id lod, const IR::Value& offset) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const ImageOperands operands(ctx, false, true, false, lod, offset); Id result = Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); #ifdef __ANDROID__ - if (Settings::values.fix_bloom_effects.GetValue()) { + if (!is_integer && Settings::values.fix_bloom_effects.GetValue()) { result = ctx.OpVectorTimesScalar(ctx.F32[4], result, ctx.Const(0.98f)); } #endif - return result; + return is_integer ? ctx.OpBitcast(ctx.F32[4], result) : result; } Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, @@ -547,30 +560,39 @@ Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Va Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, const IR::Value& offset, const IR::Value& offset2) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const ImageOperands operands(ctx, offset, offset2); if (ctx.profile.need_gather_subpixel_offset) { coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords); } - return Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst, - ctx.F32[4], Texture(ctx, info, index), coords, ctx.Const(info.gather_component), - operands.MaskOptional(), operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst, + result_type, Texture(ctx, info, index), coords, + ctx.Const(info.gather_component), operands.MaskOptional(), + operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, const IR::Value& offset, const IR::Value& offset2, Id dref) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const ImageOperands operands(ctx, offset, offset2); if (ctx.profile.need_gather_subpixel_offset) { coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords); } - return Emit(&EmitContext::OpImageSparseDrefGather, &EmitContext::OpImageDrefGather, ctx, inst, - ctx.F32[4], Texture(ctx, info, index), coords, dref, operands.MaskOptional(), - operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseDrefGather, &EmitContext::OpImageDrefGather, + ctx, inst, result_type, Texture(ctx, info, index), coords, dref, + operands.MaskOptional(), operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset, Id lod, Id ms) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; AddOffsetToCoordinates(ctx, info, coords, offset); if (info.type == TextureType::Buffer) { lod = Id{}; @@ -580,8 +602,10 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c lod = Id{}; } const ImageOperands operands(lod, ms); - return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4], - TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, + result_type, TextureImage(ctx, info, index), coords, + operands.MaskOptional(), operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod, @@ -626,14 +650,17 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id derivatives, const IR::Value& offset, Id lod_clamp) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const auto operands = info.num_derivatives == 3 ? ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, ctx.Def(offset), {}, lod_clamp) : ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, info.num_derivatives, offset, lod_clamp); - return Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], - Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseSampleExplicitLod, + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, + Texture(ctx, info, index), coords, operands.Mask(), operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 169e83d9fd..b1bed263a6 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -30,7 +30,7 @@ enum class Operation { Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) { const spv::ImageFormat format{spv::ImageFormat::Unknown}; - const Id type{ctx.F32[1]}; + const Id type{desc.is_integer ? ctx.U32[1] : ctx.F32[1]}; const bool depth{desc.is_depth}; const bool ms{desc.is_multisample}; switch (desc.type) { @@ -1126,7 +1126,7 @@ void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) { } IR::Type types{info.used_constant_buffer_types | info.used_indirect_cbuf_types}; if (True(types & IR::Type::U8)) { - if (profile.support_int8) { + if (profile.support_int8 && profile.support_uniform_and_storage_buffer_8bit) { DefineConstBuffers(*this, info, &UniformDefinitions::U8, binding, U8, 'u', sizeof(u8)); DefineConstBuffers(*this, info, &UniformDefinitions::S8, binding, S8, 's', sizeof(s8)); } else { @@ -1134,7 +1134,7 @@ void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) { } } if (True(types & IR::Type::U16)) { - if (profile.support_int16) { + if (profile.support_int16 && profile.support_uniform_and_storage_buffer_16bit) { DefineConstBuffers(*this, info, &UniformDefinitions::U16, binding, U16, 'u', sizeof(u16)); DefineConstBuffers(*this, info, &UniformDefinitions::S16, binding, S16, 's', @@ -1196,10 +1196,18 @@ void EmitContext::DefineConstantBufferIndirectFunctions(const Info& info) { IR::Type types{info.used_indirect_cbuf_types}; bool supports_aliasing = profile.support_descriptor_aliasing; if (supports_aliasing && True(types & IR::Type::U8)) { - load_const_func_u8 = make_accessor(U8, &UniformDefinitions::U8); + if (profile.support_int8 && profile.support_uniform_and_storage_buffer_8bit) { + load_const_func_u8 = make_accessor(U8, &UniformDefinitions::U8); + } else { + types |= IR::Type::U32; + } } if (supports_aliasing && True(types & IR::Type::U16)) { - load_const_func_u16 = make_accessor(U16, &UniformDefinitions::U16); + if (profile.support_int16 && profile.support_uniform_and_storage_buffer_16bit) { + load_const_func_u16 = make_accessor(U16, &UniformDefinitions::U16); + } else { + types |= IR::Type::U32; + } } if (supports_aliasing && True(types & IR::Type::F32)) { load_const_func_f32 = make_accessor(F32[1], &UniformDefinitions::F32); @@ -1375,6 +1383,7 @@ void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_in .image_type = image_type, .count = desc.count, .is_multisample = desc.is_multisample, + .is_integer = desc.is_integer, }); if (profile.supported_spirv >= 0x00010400) { interfaces.push_back(id); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 55385de0b0..504bffc0ed 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -42,6 +42,7 @@ struct TextureDefinition { Id image_type; u32 count; bool is_multisample; + bool is_integer; }; struct TextureBufferDefinition { diff --git a/src/shader_recompiler/frontend/ir/modifiers.h b/src/shader_recompiler/frontend/ir/modifiers.h index c20c2401f7..991c51b1d6 100644 --- a/src/shader_recompiler/frontend/ir/modifiers.h +++ b/src/shader_recompiler/frontend/ir/modifiers.h @@ -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 @@ -43,6 +46,10 @@ union TextureInstInfo { BitField<25, 2, u32> num_derivatives; BitField<27, 3, ImageFormat> image_format; BitField<30, 1, u32> ndv_is_active; + /// Only meaningful for the sampled-texture family (ImageSampleImplicitLod, ImageFetch, + /// ImageGather, etc.); unused/zero for storage-image opcodes, which carry their own + /// is_integer via ImageDescriptor/ImageBufferDescriptor instead. + BitField<31, 1, u32> is_integer; }; static_assert(sizeof(TextureInstInfo) <= sizeof(u32)); diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp index 2bfa3227a8..5089f3a101 100644 --- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp +++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.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 2021 yuzu Emulator Project @@ -569,6 +569,8 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ImageRead: { const auto flags{inst.Flags()}; info.uses_typeless_image_reads |= flags.image_format == ImageFormat::Typeless; + info.uses_image_1d |= + flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D; info.uses_sparse_residency |= inst.GetAssociatedPseudoOperation(IR::Opcode::GetSparseFromOp) != nullptr; break; @@ -577,6 +579,8 @@ void VisitUsages(Info& info, IR::Inst& inst) { const auto flags{inst.Flags()}; info.uses_typeless_image_writes |= flags.image_format == ImageFormat::Typeless; info.uses_image_buffers |= flags.type == TextureType::Buffer; + info.uses_image_1d |= + flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D; break; } case IR::Opcode::SubgroupEqMask: @@ -761,9 +765,13 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ImageAtomicAnd32: case IR::Opcode::ImageAtomicOr32: case IR::Opcode::ImageAtomicXor32: - case IR::Opcode::ImageAtomicExchange32: + case IR::Opcode::ImageAtomicExchange32: { + const auto flags{inst.Flags()}; info.uses_atomic_image_u32 = true; + info.uses_image_1d |= + flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D; break; + } default: break; } diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index 7194fd3b39..0d0144fcab 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -566,6 +566,7 @@ public: })}; // TODO: Read this from TIC texture_descriptors[index].is_multisample |= desc.is_multisample; + texture_descriptors[index].is_integer |= desc.is_integer; return index; } @@ -689,6 +690,21 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo program.info.image_descriptors, }; const u32 sampled_dynamic_cap = DynamicSampledTextureCap(program.info, host_info, DynamicSampledTextureArrayCount(to_replace)); + bool has_last_is_integer{false}; + u32 last_cbuf_index{}; + u32 last_cbuf_offset{}; + bool last_is_integer{false}; + const auto is_texture_pixel_format_integer{[&](const ConstBufferAddr& cbuf_addr) { + if (has_last_is_integer && last_cbuf_index == cbuf_addr.index && + last_cbuf_offset == cbuf_addr.offset) { + return last_is_integer; + } + last_is_integer = IsTexturePixelFormatIntegerCached(env, cbuf_addr); + last_cbuf_index = cbuf_addr.index; + last_cbuf_offset = cbuf_addr.offset; + has_last_is_integer = true; + return last_is_integer; + }}; for (TextureInst& texture_inst : to_replace) { // TODO: Handle arrays IR::Inst* const inst{texture_inst.inst}; @@ -753,7 +769,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo } const bool is_written{inst->GetOpcode() != IR::Opcode::ImageRead}; const bool is_read{inst->GetOpcode() != IR::Opcode::ImageWrite}; - const bool is_integer{IsTexturePixelFormatIntegerCached(env, cbuf)}; + const bool is_integer{is_texture_pixel_format_integer(cbuf)}; if (flags.type == TextureType::Buffer) { index = descriptors.Add(ImageBufferDescriptor{ .format = flags.image_format, @@ -795,10 +811,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo }); } else { count = std::min(count, sampled_dynamic_cap); + const bool is_integer{is_texture_pixel_format_integer(cbuf)}; index = descriptors.Add(TextureDescriptor{ .type = flags.type, .is_depth = flags.is_depth != 0, .is_multisample = is_multisample, + .is_integer = is_integer, .has_secondary = cbuf.has_secondary, .cbuf_index = cbuf.index, .cbuf_offset = cbuf.offset, @@ -809,6 +827,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo .count = count, .size_shift = size_shift, }); + flags.is_integer.Assign(is_integer ? 1 : 0); } break; } diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index bd7bc6ac7b..e95778a0de 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -15,7 +15,9 @@ struct Profile { bool unified_descriptor_binding{}; bool support_descriptor_aliasing{}; bool support_int8{}; + bool support_uniform_and_storage_buffer_8bit{}; bool support_int16{}; + bool support_uniform_and_storage_buffer_16bit{}; bool support_int64{}; bool support_vertex_instance_id{}; bool support_float_controls{}; diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index dfacc06802..0c11a0d72a 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -209,6 +209,7 @@ struct TextureDescriptor { TextureType type; bool is_depth; bool is_multisample; + bool is_integer; bool has_secondary; u32 cbuf_index; u32 cbuf_offset; diff --git a/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag index 484b99773c..78581cea52 100644 --- a/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag +++ b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,11 +8,11 @@ #extension GL_ARB_shader_stencil_export : require layout(binding = 0) uniform sampler2D depth_tex; -layout(binding = 1) uniform isampler2D stencil_tex; +layout(binding = 1) uniform usampler2D stencil_tex; layout(location = 0) in vec2 texcoord; void main() { gl_FragDepth = textureLod(depth_tex, texcoord, 0).r; - gl_FragStencilRefARB = textureLod(stencil_tex, texcoord, 0).r; + gl_FragStencilRefARB = int(textureLod(stencil_tex, texcoord, 0).r); } diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 12d1c2e372..0ced890929 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -39,6 +39,55 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = { POLYGON, // Patches }; +constexpr std::array TOPOLOGY_CLASS_REPRESENTATIVE_LUT = { + Maxwell::PrimitiveTopology::Points, // Points + Maxwell::PrimitiveTopology::Lines, // Lines + Maxwell::PrimitiveTopology::LineLoop, // LineLoop + Maxwell::PrimitiveTopology::LineStrip, // LineStrip + Maxwell::PrimitiveTopology::Triangles, // Triangles + Maxwell::PrimitiveTopology::Triangles, // TriangleStrip + Maxwell::PrimitiveTopology::Triangles, // TriangleFan + Maxwell::PrimitiveTopology::Triangles, // Quads + Maxwell::PrimitiveTopology::Triangles, // QuadStrip + Maxwell::PrimitiveTopology::Triangles, // Polygon + Maxwell::PrimitiveTopology::LinesAdjacency, // LinesAdjacency + Maxwell::PrimitiveTopology::LinesAdjacency, // LineStripAdjacency + Maxwell::PrimitiveTopology::TrianglesAdjacency, // TrianglesAdjacency + Maxwell::PrimitiveTopology::TrianglesAdjacency, // TriangleStripAdjacency + Maxwell::PrimitiveTopology::Patches, // Patches +}; + +bool IsDualSourceBlendFactor(Maxwell::Blend::Factor factor) { + using F = Maxwell::Blend::Factor; + switch (factor) { + case F::Source1Color_D3D: + case F::OneMinusSource1Color_D3D: + case F::Source1Alpha_D3D: + case F::OneMinusSource1Alpha_D3D: + case F::Source1Color_GL: + case F::OneMinusSource1Color_GL: + case F::Source1Alpha_GL: + case F::OneMinusSource1Alpha_GL: + return true; + default: + return false; + } +} + +bool ComputeAttachment0DualSourceBlend(const Maxwell& regs) { + if (!regs.blend.enable[0]) { + return false; + } + const auto uses_dual_source = [](const auto& blend) { + return IsDualSourceBlendFactor(blend.color_source) || + IsDualSourceBlendFactor(blend.color_dest) || + IsDualSourceBlendFactor(blend.alpha_source) || + IsDualSourceBlendFactor(blend.alpha_dest); + }; + return regs.blend_per_target_enabled ? uses_dual_source(regs.blend_per_target[0]) + : uses_dual_source(regs.blend); +} + void RefreshXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& regs) { std::ranges::transform(regs.transform_feedback.controls, state.layouts.begin(), [](const auto& layout) { @@ -65,6 +114,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe extended_dynamic_state_2_logic_op.Assign(features.has_extended_dynamic_state_2_logic_op ? 1 : 0); extended_dynamic_state_3_blend.Assign(features.has_extended_dynamic_state_3_blend ? 1 : 0); extended_dynamic_state_3_enables.Assign(features.has_extended_dynamic_state_3_enables ? 1 : 0); + color_write_enable_dynamic.Assign(features.has_color_write_enable ? 1 : 0); dynamic_vertex_input.Assign(features.has_dynamic_vertex_input ? 1 : 0); xfb_enabled.Assign(regs.transform_feedback_enabled != 0); ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0); @@ -74,8 +124,13 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe tessellation_clockwise.Assign(regs.tessellation.params.output_primitives.Value() == Maxwell::Tessellation::OutputPrimitives::Triangles_CW); patch_control_points_minus_one.Assign(regs.patch_vertices - 1); - topology.Assign(topology_); + const bool can_collapse_topology_class = + features.has_extended_dynamic_state && features.has_extended_dynamic_state_2; + topology.Assign(can_collapse_topology_class + ? TOPOLOGY_CLASS_REPRESENTATIVE_LUT[static_cast(topology_)] + : topology_); msaa_mode.Assign(regs.anti_alias_samples_mode); + attachment0_dual_source_blend.Assign(ComputeAttachment0DualSourceBlend(regs) ? 1 : 0); raw2 = 0; @@ -190,6 +245,15 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe maxwell3d.dirty.flags[Dirty::Blending] = false; for (size_t index = 0; index < attachments.size(); ++index) { attachments[index].Refresh(regs, index); + auto& attachment = attachments[index]; + if (color_write_enable_dynamic && attachment.mask_r == 0 && + attachment.mask_g == 0 && attachment.mask_b == 0 && + attachment.mask_a == 0) { + attachment.mask_r.Assign(1); + attachment.mask_g.Assign(1); + attachment.mask_b.Assign(1); + attachment.mask_a.Assign(1); + } } } } diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index c203e13880..36a4b4b7a2 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -33,6 +33,7 @@ struct DynamicFeatures { bool has_dynamic_state3_logic_op_enable; bool has_dynamic_state3_line_stipple_enable; bool has_dynamic_vertex_input; + bool has_color_write_enable; bool has_provoking_vertex; bool has_provoking_vertex_first_mode; bool has_provoking_vertex_last_mode; @@ -210,6 +211,9 @@ struct FixedPipelineState { BitField<12, 2, u32> tessellation_spacing; BitField<14, 1, u32> tessellation_clockwise; BitField<15, 5, u32> patch_control_points_minus_one; + BitField<20, 1, u32> color_write_enable_dynamic; + + BitField<21, 1, u32> attachment0_dual_source_blend; BitField<24, 4, Maxwell::PrimitiveTopology> topology; BitField<28, 4, Tegra::Texture::MsaaMode> msaa_mode; diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 77ef8bf7bc..6c18ade926 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -223,6 +223,10 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, 0, ETC2_RGB_SRGB) \ SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, 0, ETC2_RGBA_SRGB) \ SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, 0, ETC2_RGB_PTA_SRGB) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11_UNORM_BLOCK, 0, EAC_R11_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11_SNORM_BLOCK, 0, EAC_R11_SNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11G11_UNORM_BLOCK, 0, EAC_R11G11_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11G11_SNORM_BLOCK, 0, EAC_R11G11_SNORM) \ /* Depth formats */ \ SURFACE_FORMAT_ELEM(VK_FORMAT_D32_SFLOAT, usage_attachable, D32_FLOAT) \ SURFACE_FORMAT_ELEM(VK_FORMAT_D16_UNORM, usage_attachable, D16_UNORM) \ @@ -278,7 +282,17 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with } } else if (!device.IsOptimalEtc2Supported() && VideoCore::Surface::IsPixelFormatETC2(pixel_format)) { // Transcode on hardware that doesn't support ETC2 natively - tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32; + if (pixel_format == PixelFormat::EAC_R11_SNORM) { + tuple.format = VK_FORMAT_R8_SNORM; + } else if (pixel_format == PixelFormat::EAC_R11_UNORM) { + tuple.format = VK_FORMAT_R8_UNORM; + } else if (pixel_format == PixelFormat::EAC_R11G11_SNORM) { + tuple.format = VK_FORMAT_R8G8_SNORM; + } else if (pixel_format == PixelFormat::EAC_R11G11_UNORM) { + tuple.format = VK_FORMAT_R8G8_UNORM; + } else { + tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32; + } } bool const attachable = (tuple.usage & usage_attachable) != 0; bool const storage = (tuple.usage & usage_storage) != 0; diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index fce1f2e252..0523a040c8 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include @@ -15,6 +16,7 @@ #include "shader_recompiler/shader_info.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" +#include "video_core/surface.h" #include "video_core/texture_cache/types.h" #include "video_core/vulkan_common/vulkan_device.h" @@ -22,6 +24,29 @@ namespace Vulkan { using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS; +[[nodiscard]] inline std::optional PixelFormatFromImageFormat( + Shader::ImageFormat format) { + switch (format) { + case Shader::ImageFormat::Typeless: + return std::nullopt; + case Shader::ImageFormat::R8_UINT: + return PixelFormat::R8_UINT; + case Shader::ImageFormat::R8_SINT: + return PixelFormat::R8_SINT; + case Shader::ImageFormat::R16_UINT: + return PixelFormat::R16_UINT; + case Shader::ImageFormat::R16_SINT: + return PixelFormat::R16_SINT; + case Shader::ImageFormat::R32_UINT: + return PixelFormat::R32_UINT; + case Shader::ImageFormat::R32G32_UINT: + return PixelFormat::R32G32_UINT; + case Shader::ImageFormat::R32G32B32A32_UINT: + return PixelFormat::R32G32B32A32_UINT; + } + return std::nullopt; +} + [[nodiscard]] inline u32 NumDescriptorEntries(const Shader::Info& info) { return Shader::NumDescriptors(info.constant_buffer_descriptors) + Shader::NumDescriptors(info.storage_buffers_descriptors) + @@ -211,8 +236,12 @@ inline void PushImageDescriptors(TextureCache& texture_cache, const Sampler& sampler{texture_cache.GetSampler(sampler_id)}; const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && !image_view.SupportsAnisotropy()}; - const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() - : sampler.Handle()}; + VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() + : sampler.Handle()}; + if (sampler.HasLinearFiltering() && + VideoCore::Surface::IsPixelFormatInteger(image_view.format)) { + vk_sampler = sampler.HandleWithNearestFilter(); + } guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler); const bool element_rescaled{texture_cache.IsRescaling(image_view)}; is_rescaled |= element_rescaled; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 7bd0a260b3..bc28bb0111 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -84,7 +84,7 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo } // Anonymous namespace Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params) - : VideoCommon::BufferBase(null_params), tracker{4096} { + : VideoCommon::BufferBase(null_params), scheduler{&runtime.scheduler}, tracker{4096} { if (runtime.device.HasNullDescriptor()) { return; } @@ -95,12 +95,18 @@ Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_p Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) : VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device}, + scheduler{&runtime.scheduler}, buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())}, tracker{SizeBytes()} { if (runtime.device.HasDebuggingToolAttached()) { buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str()); } } +void Buffer::MarkUsage(u64 offset, u64 size) noexcept { + tracker.Track(offset, size); + last_usage_tick = scheduler->CurrentTick(); +} + VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format) { if (!device) { // Null buffer supported, return a null descriptor @@ -384,7 +390,9 @@ u32 BufferCacheRuntime::GetStorageBufferAlignment() const { void BufferCacheRuntime::TickFrame(Common::SlotVector& slot_buffers) noexcept { for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) { - it->ResetUsageTracking(); + if (scheduler.IsFree(it->LastUsageTick())) { + it->ResetUsageTracking(); + } } } @@ -556,7 +564,7 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset if (index >= device.GetMaxVertexInputBindings()) { return; } - if (device.IsExtExtendedDynamicStateSupported()) { + if (device.IsExtExtendedDynamicStateSupported() && !vertex_input_dynamic_state_active) { scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) { const VkDeviceSize vk_offset = buffer != VK_NULL_HANDLE ? offset : 0; const VkDeviceSize vk_size = buffer != VK_NULL_HANDLE ? size : VK_WHOLE_SIZE; @@ -596,7 +604,7 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bi if (binding_count == 0) { return; } - if (device.IsExtExtendedDynamicStateSupported()) { + if (device.IsExtExtendedDynamicStateSupported() && !vertex_input_dynamic_state_active) { scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) { cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data(), bindings_.strides.data()); }); diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index b73fcd162b..9cf0107e46 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -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 2019 yuzu Emulator Project @@ -43,14 +43,16 @@ public: return tracker.IsUsed(offset, size); } - void MarkUsage(u64 offset, u64 size) noexcept { - tracker.Track(offset, size); - } + void MarkUsage(u64 offset, u64 size) noexcept; void ResetUsageTracking() noexcept { tracker.Reset(); } + [[nodiscard]] u64 LastUsageTick() const noexcept { + return last_usage_tick; + } + operator VkBuffer() const noexcept { return *buffer; } @@ -64,9 +66,11 @@ private: }; const Device* device{}; + Scheduler* scheduler{}; vk::Buffer buffer; std::vector views; VideoCommon::UsageTracker tracker; + u64 last_usage_tick{}; bool is_null{}; }; @@ -127,6 +131,10 @@ public: void BindVertexBuffers(VideoCommon::HostBindings& bindings); + void SetVertexInputDynamicState(bool is_active) { + vertex_input_dynamic_state_active = is_active; + } + void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); @@ -163,7 +171,11 @@ public: private: void BindBuffer(VkBuffer buffer, u32 offset, u32 size) { - guest_descriptor_queue.AddBuffer(buffer, offset, size); + if (buffer == VK_NULL_HANDLE) { + guest_descriptor_queue.AddBuffer(buffer, 0, VK_WHOLE_SIZE); + } else { + guest_descriptor_queue.AddBuffer(buffer, offset, size); + } } void ReserveNullBuffer(); @@ -185,6 +197,8 @@ private: bool limit_dynamic_storage_buffers = false; u32 max_dynamic_storage_buffers = (std::numeric_limits::max)(); + + bool vertex_input_dynamic_state_active = false; }; struct BufferCacheParams { diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 13bd654a80..5525ce0e95 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -193,8 +193,14 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, is_written = desc.is_written; } ImageView& image_view = texture_cache.GetImageView(views[index].id); + PixelFormat format{image_view.format}; + if constexpr (is_image) { + if (const auto explicit_format{PixelFormatFromImageFormat(desc.format)}) { + format = *explicit_format; + } + } buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(), - image_view.BufferSize(), image_view.format, + image_view.BufferSize(), format, is_written, is_image); ++index; } diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 0c0174391c..ebc6da5a4b 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -426,8 +426,14 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { is_written = desc.is_written; } ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)}; + PixelFormat format{image_view.format}; + if constexpr (is_image) { + if (const auto explicit_format{PixelFormatFromImageFormat(desc.format)}) { + format = *explicit_format; + } + } buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(), - image_view.BufferSize(), image_view.format, + image_view.BufferSize(), format, is_written, is_image); ++index; ++texture_buffer_it; @@ -472,6 +478,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { } buffer_cache.UpdateGraphicsBuffers(is_indexed); + buffer_cache.runtime.SetVertexInputDynamicState(HasDynamicVertexInput()); buffer_cache.BindHostGeometryBuffers(is_indexed); guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries); @@ -865,18 +872,17 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_STENCIL_OP_EXT, + VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT, }; dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end()); - // VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT is part of EDS1 - // Only use it if VIDS is not active (VIDS replaces it with full vertex input control) + // VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT if (!key.state.dynamic_vertex_input) { dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT); } } - // VK_DYNAMIC_STATE_VERTEX_INPUT_EXT (VIDS) - Independent from EDS - // Provides full dynamic vertex input control, replaces VERTEX_INPUT_BINDING_STRIDE + // VK_DYNAMIC_STATE_VERTEX_INPUT_EXT if (key.state.dynamic_vertex_input) { dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT); } @@ -906,6 +912,11 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end()); } + // VK_EXT_color_write_enable fallback for fully on/off render targets when EDS3 blending is not available. + if (!key.state.extended_dynamic_state_3_blend && key.state.color_write_enable_dynamic) { + dynamic_states.push_back(VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT); + } + // EDS3 - Enables (composite: per-feature) if (key.state.extended_dynamic_state_3_enables) { if (device.SupportsDynamicState3DepthClampEnable()) { diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index b535097b36..be5a1144ce 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -130,6 +130,70 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, VkSemaphore wait_semaphore, u64 host_tick) { const VkSemaphore timeline_semaphore = *semaphore; + if (device.HasSynchronization2()) { + const std::array cmdbuffer_infos{{ + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *upload_cmdbuf, + .deviceMask = 0, + }, + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *cmdbuf, + .deviceMask = 0, + }, + }}; + + std::array signal_infos{{ + { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = timeline_semaphore, + .value = host_tick, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }, + {}, + }}; + u32 num_signal_semaphores = 1; + if (signal_semaphore) { + signal_infos[1] = VkSemaphoreSubmitInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = signal_semaphore, + .value = 0, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }; + num_signal_semaphores = 2; + } + + const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo wait_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = wait_semaphore, + .value = 0, + .stageMask = static_cast(wait_stage_mask), + .deviceIndex = 0, + }; + + const VkSubmitInfo2 submit_info2{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .pNext = nullptr, + .flags = 0, + .waitSemaphoreInfoCount = num_wait_semaphores, + .pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr, + .commandBufferInfoCount = static_cast(cmdbuffer_infos.size()), + .pCommandBufferInfos = cmdbuffer_infos.data(), + .signalSemaphoreInfoCount = num_signal_semaphores, + .pSignalSemaphoreInfos = signal_infos.data(), + }; + return device.GetGraphicsQueue().Submit2(submit_info2); + } + const u32 num_signal_semaphores = signal_semaphore ? 2 : 1; const std::array signal_values{host_tick, u64(0)}; const std::array signal_semaphores{timeline_semaphore, signal_semaphore}; @@ -172,6 +236,66 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf, VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, u64 host_tick) { + if (device.HasSynchronization2()) { + const std::array cmdbuffer_infos{{ + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *upload_cmdbuf, + .deviceMask = 0, + }, + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *cmdbuf, + .deviceMask = 0, + }, + }}; + + const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo signal_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = signal_semaphore, + .value = 0, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }; + + const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo wait_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = wait_semaphore, + .value = 0, + .stageMask = static_cast(wait_stage_mask), + .deviceIndex = 0, + }; + + const VkSubmitInfo2 submit_info2{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .pNext = nullptr, + .flags = 0, + .waitSemaphoreInfoCount = num_wait_semaphores, + .pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr, + .commandBufferInfoCount = static_cast(cmdbuffer_infos.size()), + .pCommandBufferInfos = cmdbuffer_infos.data(), + .signalSemaphoreInfoCount = num_signal_semaphores, + .pSignalSemaphoreInfos = num_signal_semaphores ? &signal_info : nullptr, + }; + + auto fence = GetFreeFence(); + auto result = device.GetGraphicsQueue().Submit2(submit_info2, *fence); + + if (result == VK_SUCCESS) { + std::scoped_lock lock{wait_mutex}; + wait_queue.emplace(host_tick, std::move(fence)); + wait_cv.notify_one(); + } + + return result; + } + const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 3d63d6eb23..2eea66802d 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -246,32 +246,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program key.state.UnpackComparisonOp(key.state.alpha_test_func.Value())); info.alpha_test_reference = std::bit_cast(key.state.alpha_test_ref); - // Check for dual source blending - const auto& blend0 = key.state.attachments[0]; - if (blend0.enable != 0) { - using F = Maxwell::Blend::Factor; - const auto src_rgb = blend0.SourceRGBFactor(); - const auto dst_rgb = blend0.DestRGBFactor(); - const auto src_a = blend0.SourceAlphaFactor(); - const auto dst_a = blend0.DestAlphaFactor(); - info.dual_source_blend = - src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D || - src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D || - src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL || - src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL || - dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D || - dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D || - dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL || - dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL || - src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D || - src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D || - src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL || - src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL || - dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D || - dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D || - dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL || - dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL; - } + info.dual_source_blend = key.state.attachment0_dual_source_blend != 0; if (device.IsMoltenVK()) { for (size_t i = 0; i < 8; ++i) { @@ -383,7 +358,11 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .unified_descriptor_binding = true, .support_descriptor_aliasing = device.IsDescriptorAliasingSupported(), .support_int8 = device.IsInt8Supported(), + .support_uniform_and_storage_buffer_8bit = + device.IsUniformAndStorageBuffer8BitAccessSupported(), .support_int16 = device.IsShaderInt16Supported(), + .support_uniform_and_storage_buffer_16bit = + device.IsUniformAndStorageBuffer16BitAccessSupported(), .support_int64 = device.IsShaderInt64Supported(), .support_vertex_instance_id = false, .support_float_controls = device.IsKhrShaderFloatControlsSupported(), @@ -502,6 +481,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, device.IsExtExtendedDynamicState3BlendingSupported(); dynamic_features.has_extended_dynamic_state_3_enables = device.IsExtExtendedDynamicState3EnablesSupported(); + dynamic_features.has_color_write_enable = + device.IsExtColorWriteEnableSupported(); dynamic_features.has_dynamic_state3_depth_clamp_enable = dynamic_features.has_extended_dynamic_state_3_enables && device.SupportsDynamicState3DepthClampEnable(); @@ -634,7 +615,10 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading dynamic_features.has_extended_dynamic_state_3_blend || (key.state.extended_dynamic_state_3_enables != 0) != dynamic_features.has_extended_dynamic_state_3_enables || - (key.state.dynamic_vertex_input != 0) != dynamic_features.has_dynamic_vertex_input) { + (key.state.color_write_enable_dynamic != 0) != + dynamic_features.has_color_write_enable || + (key.state.dynamic_vertex_input != 0) != + dynamic_features.has_dynamic_vertex_input) { return; } diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 991482d0b8..8d352f3b73 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -41,8 +41,8 @@ class SamplesQueryBank : public VideoCommon::BankBase { public: static constexpr size_t BANK_SIZE = 256; static constexpr size_t QUERY_SIZE = 8; - explicit SamplesQueryBank(const Device& device_, size_t index_) - : BankBase(BANK_SIZE), device{device_}, index{index_} { + explicit SamplesQueryBank(const Device& device_, Scheduler& scheduler_, size_t index_) + : BankBase(BANK_SIZE), device{device_}, scheduler{scheduler_}, index{index_} { const auto& dev = device.GetLogical(); query_pool = dev.CreateQueryPool({ .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, @@ -60,12 +60,28 @@ public: void Reset() override { ASSERT(references == 0); VideoCommon::BankBase::Reset(); - const auto& dev = device.GetLogical(); - dev.ResetQueryPool(*query_pool, 0, BANK_SIZE); + if (device.IsHostQueryResetSupported()) { + const auto& dev = device.GetLogical(); + dev.ResetQueryPool(*query_pool, 0, BANK_SIZE); + } else { + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([pool = *query_pool](vk::CommandBuffer cmdbuf) { + cmdbuf.ResetQueryPool(pool, 0, BANK_SIZE); + }); + } host_results.fill(0ULL); next_bank = 0; } + void AddReference(size_t how_many = 1) { + BankBase::AddReference(how_many); + last_used_tick = scheduler.CurrentTick(); + } + + [[nodiscard]] bool IsDead() const { + return BankBase::IsDead() && scheduler.IsFree(last_used_tick); + } + void Sync(size_t start, size_t size) { const auto& dev = device.GetLogical(); const VkResult query_result = dev.GetQueryResults( @@ -98,9 +114,11 @@ public: private: const Device& device; + Scheduler& scheduler; const size_t index; vk::QueryPool query_pool; std::array host_results; + u64 last_used_tick{}; }; using BaseStreamer = VideoCommon::SimpleStreamer; @@ -431,7 +449,7 @@ private: void ReserveBank() { current_bank_id = bank_pool.ReserveBank([this](std::deque& queue, size_t index) { - queue.emplace_back(device, index); + queue.emplace_back(device, scheduler, index); }); if (current_bank) { current_bank->next_bank = current_bank_id + 1; @@ -620,6 +638,15 @@ public: VideoCommon::BankBase::Reset(); } + void AddReference(size_t how_many = 1) { + BankBase::AddReference(how_many); + last_used_tick = scheduler.CurrentTick(); + } + + [[nodiscard]] bool IsDead() const { + return BankBase::IsDead() && scheduler.IsFree(last_used_tick); + } + void Sync(StagingBufferRef& stagging_buffer, size_t extra_offset, size_t start, size_t size) { scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([this, dst_buffer = stagging_buffer.buffer, extra_offset, start, @@ -645,6 +672,7 @@ private: Scheduler& scheduler; const size_t index; vk::Buffer buffer; + u64 last_used_tick{}; }; class PrimitivesSucceededStreamer; @@ -922,7 +950,7 @@ private: return; } has_flushed_end_pending = false; - + // Refresh buffer state before ending transform feedback to ensure counters_count is up-to-date. UpdateBuffers(); if (buffers_count == 0) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 863ebb72ec..26663d4b25 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -223,7 +223,10 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra scheduler.SetQueryCache(query_cache); } -RasterizerVulkan::~RasterizerVulkan() = default; +RasterizerVulkan::~RasterizerVulkan() { + scheduler.WaitWorker(); + scheduler.Finish(); +} template void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { @@ -1011,12 +1014,12 @@ void RasterizerVulkan::UpdateDynamicStates() { auto& regs = maxwell3d->regs; auto& flags = maxwell3d->dirty.flags; const auto topology = maxwell3d->draw_manager.draw_state.topology; - if (state_tracker.ChangePrimitiveTopology(topology)) { + const bool topology_changed = state_tracker.ChangePrimitiveTopology(topology); + if (topology_changed) { flags[Dirty::DepthBiasEnable] = true; flags[Dirty::PrimitiveRestartEnable] = true; } - // Core Dynamic States (Vulkan 1.0) - Always active regardless of dyna_state setting UpdateViewportsState(regs); UpdateScissorsState(regs); UpdateDepthBias(regs); @@ -1025,7 +1028,6 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateStencilFaces(regs); UpdateLineWidth(regs); - // EDS1: CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest if (device.IsExtExtendedDynamicStateSupported()) { UpdateCullMode(regs); UpdateDepthCompareOp(regs); @@ -1037,21 +1039,24 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateDepthWriteEnable(regs); UpdateStencilTestEnable(regs); } + if (topology_changed) { + scheduler.Record([topology_vk = MaxwellToVK::PrimitiveTopology(device, topology)]( + vk::CommandBuffer cmdbuf) { + cmdbuf.SetPrimitiveTopologyEXT(topology_vk); + }); + } } - // EDS2: PrimitiveRestart, RasterizerDiscard, DepthBias enable/disable if (device.IsExtExtendedDynamicState2Supported()) { UpdatePrimitiveRestartEnable(regs); UpdateRasterizerDiscardEnable(regs); UpdateDepthBiasEnable(regs); } - // EDS2 Extras: LogicOp operation selection if (device.IsExtExtendedDynamicState2ExtrasSupported()) { UpdateLogicOp(regs); } - // EDS3 Enables: LogicOpEnable, DepthClamp, LineStipple, ConservativeRaster if (device.IsExtExtendedDynamicState3EnablesSupported()) { using namespace Tegra::Engines; // AMD Workaround: LogicOp incompatible with float render targets @@ -1076,12 +1081,12 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateAlphaToOneEnable(regs); } - // EDS3 Blending: ColorBlendEnable, ColorBlendEquation, ColorWriteMask if (device.IsExtExtendedDynamicState3BlendingSupported()) { UpdateBlending(regs); + } else if (device.IsExtColorWriteEnableSupported()) { + UpdateColorWriteEnable(regs); } - // Vertex Input Dynamic State: Independent from EDS levels if (device.IsExtVertexInputDynamicStateSupported()) { if (auto* gp = pipeline_cache.CurrentGraphicsPipeline(); gp && gp->HasDynamicVertexInput()) { UpdateVertexInput(regs); @@ -1094,7 +1099,6 @@ void RasterizerVulkan::HandleTransformFeedback() { const auto& regs = maxwell3d->regs; if (!device.IsExtTransformFeedbackSupported()) { - // If the guest enabled transform feedback, warn once that the device lacks support. if (regs.transform_feedback_enabled != 0) { std::call_once(warn_unsupported, [&] { LOG_WARNING(Render_Vulkan, "Transform feedback requested by guest but VK_EXT_transform_feedback is unavailable; queries disabled"); @@ -1723,9 +1727,16 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) { if (state_tracker.TouchBlendEnable()) { std::array setup_enables{}; - std::ranges::transform( - regs.blend.enable, setup_enables.begin(), - [&](const auto& is_enabled) { return is_enabled != 0 ? VK_TRUE : VK_FALSE; }); + for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) { + bool is_integer = false; + if (regs.rt[index].format != Tegra::RenderTargetFormat::NONE) { + const auto format = + VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[index].format); + is_integer = IsPixelFormatInteger(format); + } + setup_enables[index] = + (!is_integer && regs.blend.enable[index] != 0) ? VK_TRUE : VK_FALSE; + } scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) { cmdbuf.SetColorBlendEnableEXT(0, setup_enables); }); @@ -1774,6 +1785,20 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) { } } +void RasterizerVulkan::UpdateColorWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchColorMask()) { + return; + } + std::array setup_enables{}; + for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) { + const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index]; + setup_enables[index] = (mask.R || mask.G || mask.B || mask.A) ? VK_TRUE : VK_FALSE; + } + scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) { + cmdbuf.SetColorWriteEnableEXT(setup_enables); + }); +} + void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) { if (!state_tracker.TouchStencilTestEnable()) { return; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 841933d31d..206bf1043a 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -191,6 +191,7 @@ private: void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateLogicOp(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdateColorWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 6a631f68a2..48268df708 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -344,7 +344,9 @@ void Scheduler::EndRenderPass() Record([num_images = num_renderpass_images, images = renderpass_images, - ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) { + ranges = renderpass_image_ranges, + has_transform_feedback = device.IsExtTransformFeedbackSupported()]( + vk::CommandBuffer cmdbuf) { std::array barriers; for (size_t i = 0; i < num_images; ++i) { const VkImageSubresourceRange& range = ranges[i]; @@ -384,6 +386,17 @@ void Scheduler::EndRenderPass() cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images)); + if (has_transform_feedback) { + static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT, + .dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, XFB_OUTPUT_BARRIER); + } }); state.renderpass = VkRenderPass{}; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 7804e33d46..6307f8b93e 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1299,7 +1299,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im case PixelFormat::R32G32_FLOAT: case PixelFormat::R32G32_SINT: case PixelFormat::R32_FLOAT: - if ((src_view.format == PixelFormat::D32_FLOAT) && Settings::values.fix_bloom_effects.GetValue()) { + if (src_view.format == PixelFormat::D32_FLOAT && + (dst_view.format == PixelFormat::B5G6R5_UNORM || + Settings::values.fix_bloom_effects.GetValue())) { const Region2D region{ .start = {0, 0}, .end = {static_cast(dst->RenderArea().width), @@ -2242,7 +2244,12 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, Shader::ImageFormat image_format) { if (image_handle) { if (image_format == Shader::ImageFormat::Typeless) { - return Handle(texture_type); + if (!typeless_storage_view) { + const auto& info = + MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT); + } + return *typeless_storage_view; } const bool is_signed = image_format == Shader::ImageFormat::R8_SINT || image_format == Shader::ImageFormat::R16_SINT; @@ -2297,12 +2304,15 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t const void* pnext = nullptr; if (has_custom_border_colors) { pnext = &border_ci; - // Log extension usage for custom border color if (GPU::Logging::IsActive()) { GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( "VK_EXT_custom_border_color", "Sampler::Sampler"); } } + if (device.IsExtBorderColorSwizzleSupported() && GPU::Logging::IsActive()) { + GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( + "VK_EXT_border_color_swizzle", "Sampler::Sampler"); + } const VkSamplerReductionModeCreateInfoEXT reduction_ci{ .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, .pNext = pnext, @@ -2316,20 +2326,28 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t // Some games have samplers with garbage. Sanitize them here. const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); - const auto create_sampler = [&](const f32 anisotropy) { + const VkFilter mag_filter{MaxwellToVK::Sampler::Filter(tsc.mag_filter)}; + const VkFilter min_filter{MaxwellToVK::Sampler::Filter(tsc.min_filter)}; + const VkSamplerMipmapMode mipmap_mode{MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter)}; + const bool has_linear_filtering{mag_filter == VK_FILTER_LINEAR || + min_filter == VK_FILTER_LINEAR || + mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR}; + + const auto create_sampler = [&](const f32 anisotropy, bool force_nearest) { return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = pnext, .flags = 0, - .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), - .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), - .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), + .magFilter = force_nearest ? VK_FILTER_NEAREST : mag_filter, + .minFilter = force_nearest ? VK_FILTER_NEAREST : min_filter, + .mipmapMode = force_nearest ? VK_SAMPLER_MIPMAP_MODE_NEAREST : mipmap_mode, .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), .mipLodBias = tsc.LodBias(), - .anisotropyEnable = static_cast(anisotropy > 1.0f ? VK_TRUE : VK_FALSE), - .maxAnisotropy = anisotropy, + .anisotropyEnable = + static_cast(!force_nearest && anisotropy > 1.0f ? VK_TRUE : VK_FALSE), + .maxAnisotropy = force_nearest ? 1.0f : anisotropy, .compareEnable = tsc.depth_compare_enabled, .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), @@ -2340,11 +2358,14 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t }); }; - sampler = create_sampler(max_anisotropy); + sampler = create_sampler(max_anisotropy, false); const f32 max_anisotropy_default = static_cast(1U << tsc.max_anisotropy); if (max_anisotropy > max_anisotropy_default) { - sampler_default_anisotropy = create_sampler(max_anisotropy_default); + sampler_default_anisotropy = create_sampler(max_anisotropy_default, false); + } + if (has_linear_filtering) { + sampler_nearest = create_sampler(1.0f, true); } } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 881135da1b..005ab94762 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -377,6 +377,7 @@ private: std::array image_views; std::optional storage_views; + vk::ImageView typeless_storage_view; vk::ImageView depth_view; vk::ImageView stencil_view; vk::ImageView color_view; @@ -405,9 +406,18 @@ public: return static_cast(sampler_default_anisotropy); } + [[nodiscard]] VkSampler HandleWithNearestFilter() const noexcept { + return *sampler_nearest; + } + + [[nodiscard]] bool HasLinearFiltering() const noexcept { + return static_cast(sampler_nearest); + } + private: vk::Sampler sampler; vk::Sampler sampler_default_anisotropy; + vk::Sampler sampler_nearest; }; struct TextureCacheParams { diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 69c1720943..4603870bfa 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -344,6 +344,10 @@ bool IsPixelFormatETC2(PixelFormat format) { case PixelFormat::ETC2_RGB_SRGB: case PixelFormat::ETC2_RGBA_SRGB: case PixelFormat::ETC2_RGB_PTA_SRGB: + case PixelFormat::EAC_R11_UNORM: + case PixelFormat::EAC_R11_SNORM: + case PixelFormat::EAC_R11G11_UNORM: + case PixelFormat::EAC_R11G11_SNORM: return true; default: return false; diff --git a/src/video_core/surface.h b/src/video_core/surface.h index 5611dbdffa..8e1973d3b9 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -119,6 +119,10 @@ namespace VideoCore::Surface { PIXEL_FORMAT_ELEM(ETC2_RGB_SRGB, 4, 4, 64) \ PIXEL_FORMAT_ELEM(ETC2_RGBA_SRGB, 4, 4, 128) \ PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_SRGB, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(EAC_R11_UNORM, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(EAC_R11_SNORM, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(EAC_R11G11_UNORM, 4, 4, 128) \ + PIXEL_FORMAT_ELEM(EAC_R11G11_SNORM, 4, 4, 128) \ /* Depth formats */ \ PIXEL_FORMAT_ELEM(D32_FLOAT, 1, 1, 32) \ PIXEL_FORMAT_ELEM(D16_UNORM, 1, 1, 16) \ diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index ea68a1c063..d6e019cddf 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -204,6 +204,15 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, return PixelFormat::ETC2_RGB_PTA_SRGB; case Hash(TextureFormat::ETC2_RGBA, UNORM, SRGB): return PixelFormat::ETC2_RGBA_SRGB; + /* EAC */ + case Hash(TextureFormat::EAC, UNORM): + return PixelFormat::EAC_R11_UNORM; + case Hash(TextureFormat::EAC, SNORM): + return PixelFormat::EAC_R11_SNORM; + case Hash(TextureFormat::EACX2, UNORM): + return PixelFormat::EAC_R11G11_UNORM; + case Hash(TextureFormat::EACX2, SNORM): + return PixelFormat::EAC_R11G11_SNORM; /* ASTC */ case Hash(TextureFormat::ASTC_2D_4X4, UNORM, LINEAR): return PixelFormat::ASTC_2D_4X4_UNORM; diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 7c2aa76c09..ab6395cef7 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -95,6 +95,12 @@ constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{ VK_FORMAT_UNDEFINED, }; +constexpr std::array B10G11R11_UFLOAT_PACK32{ + VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_A8B8G8R8_SRGB_PACK32, + VK_FORMAT_UNDEFINED, +}; + } // namespace Alternatives template @@ -127,6 +133,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) { return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT: return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data(); + case VK_FORMAT_B10G11R11_UFLOAT_PACK32: + return Alternatives::B10G11R11_UFLOAT_PACK32.data(); default: return nullptr; } @@ -492,18 +500,14 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR CollectToolingInfo(); if (is_qualcomm) { - LOG_WARNING(Render_Vulkan, - "Qualcomm drivers require scaled vertex format emulation"); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation"); must_emulate_scaled_formats = true; - LOG_WARNING(Render_Vulkan, - "Qualcomm drivers have broken provoking vertex"); - RemoveExtension(extensions.provoking_vertex, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); - LOG_WARNING(Render_Vulkan, - "Qualcomm drivers have slow push descriptor implementation"); - RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); - LOG_WARNING(Render_Vulkan, - "Disabling shader float controls and 64-bit integer features on Qualcomm proprietary drivers"); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); + RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, + VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); features.shader_atomic_int64.shaderBufferInt64Atomics = false; @@ -561,7 +565,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR features.shader_float16_int8.shaderFloat16 = false; } - // Mali/ NVIDIA proprietary drivers: Shader stencil export not supported // Use hardware depth/stencil blits instead when available if (!extensions.shader_stencil_export) { LOG_INFO(Render_Vulkan, @@ -657,14 +660,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR } const auto dyna_state = Settings::values.dyna_state.GetValue(); - - // Base dynamic states (VIEWPORT, SCISSOR, DEPTH_BIAS, etc.) are ALWAYS active in vk_graphics_pipeline.cpp - // This slider controls EXTENDED dynamic states with accumulative levels per Vulkan specs: - // Level 0 = Core Dynamic States only (Vulkan 1.0) - // Level 1 = Core + VK_EXT_extended_dynamic_state - // Level 2 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2 - // Level 3 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2 + VK_EXT_extended_dynamic_state3 - switch (dyna_state) { case Settings::ExtendedDynamicState::Disabled: // Level 0: Disable all extended dynamic state extensions @@ -699,8 +694,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR break; } - // VK_EXT_vertex_input_dynamic_state is independent from EDS - // It can be enabled even without extended_dynamic_state + // VK_EXT_vertex_input_dynamic_state if (!Settings::values.vertex_input_dynamic_state.GetValue()) { RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); } @@ -793,7 +787,6 @@ void Device::SaveShader(std::span spirv) const { } bool Device::ComputeIsOptimalAstcSupported() const { - // Verify hardware supports all ASTC formats with optimal tiling to avoid software conversion static constexpr std::array astc_formats = { VK_FORMAT_ASTC_4x4_UNORM_BLOCK, VK_FORMAT_ASTC_4x4_SRGB_BLOCK, VK_FORMAT_ASTC_5x4_UNORM_BLOCK, VK_FORMAT_ASTC_5x4_SRGB_BLOCK, @@ -1170,6 +1163,11 @@ bool Device::GetSuitability(bool requires_swapchain) { } void Device::RemoveUnsuitableExtensions() { + // VK_EXT_color_write_enable + extensions.color_write_enable = features.color_write_enable.colorWriteEnable; + RemoveExtensionFeatureIfUnsuitable(extensions.color_write_enable, features.color_write_enable, + VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); + // VK_EXT_custom_border_color if (extensions.custom_border_color) { extensions.custom_border_color = @@ -1179,6 +1177,17 @@ void Device::RemoveUnsuitableExtensions() { RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + // VK_EXT_border_color_swizzle + if (extensions.border_color_swizzle) { + extensions.border_color_swizzle = + extensions.custom_border_color && + features.border_color_swizzle.borderColorSwizzle && + features.border_color_swizzle.borderColorSwizzleFromImage; + } + RemoveExtensionFeatureIfUnsuitable(extensions.border_color_swizzle, + features.border_color_swizzle, + VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); + // VK_EXT_depth_bias_control extensions.depth_bias_control = features.depth_bias_control.depthBiasControl && @@ -1375,6 +1384,11 @@ void Device::RemoveUnsuitableExtensions() { // VK_KHR_maintenance8 extensions.maintenance8 = loaded_extensions.contains(VK_KHR_MAINTENANCE_8_EXTENSION_NAME); RemoveExtensionIfUnsuitable(extensions.maintenance8, VK_KHR_MAINTENANCE_8_EXTENSION_NAME); + + // VK_KHR_synchronization2 + extensions.synchronization2 = features.synchronization2.synchronization2; + RemoveExtensionFeatureIfUnsuitable(extensions.synchronization2, features.synchronization2, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); } void Device::SetupFamilies(VkSurfaceKHR surface) { diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 110d0c1199..cf039768fd 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -43,12 +43,15 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(EXT, ShaderDemoteToHelperInvocation, SHADER_DEMOTE_TO_HELPER_INVOCATION, \ shader_demote_to_helper_invocation) \ FEATURE(EXT, SubgroupSizeControl, SUBGROUP_SIZE_CONTROL, subgroup_size_control) \ - FEATURE(KHR, Maintenance4, MAINTENANCE_4, maintenance4) + FEATURE(KHR, Maintenance4, MAINTENANCE_4, maintenance4) \ + FEATURE(KHR, Synchronization2, SYNCHRONIZATION_2, synchronization2) #define FOR_EACH_VK_FEATURE_1_4(FEATURE) // Define all features which may be used by the implementation and require an extension here. #define FOR_EACH_VK_FEATURE_EXT(FEATURE) \ + FEATURE(EXT, BorderColorSwizzle, BORDER_COLOR_SWIZZLE, border_color_swizzle) \ + FEATURE(EXT, ColorWriteEnable, COLOR_WRITE_ENABLE, color_write_enable) \ FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \ FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \ FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \ @@ -180,6 +183,7 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE_NAME(robustness2, nullDescriptor) \ FEATURE_NAME(shader_float16_int8, shaderFloat16) \ FEATURE_NAME(shader_float16_int8, shaderInt8) \ + FEATURE_NAME(synchronization2, synchronization2) \ FEATURE_NAME(timeline_semaphore, timelineSemaphore) \ FEATURE_NAME(transform_feedback, transformFeedback) \ FEATURE_NAME(uniform_buffer_standard_layout, uniformBufferStandardLayout) \ @@ -383,6 +387,16 @@ FN_MAX_LIMIT_LIST return features.shader_float16_int8.shaderInt8; } + /// Returns true if the device allows 8-bit integer members in uniform/storage buffers. + bool IsUniformAndStorageBuffer8BitAccessSupported() const { + return features.bit8_storage.uniformAndStorageBuffer8BitAccess; + } + + /// Returns true if the device allows 16-bit integer members in uniform/storage buffers. + bool IsUniformAndStorageBuffer16BitAccessSupported() const { + return features.bit16_storage.uniformAndStorageBuffer16BitAccess; + } + /// Returns true if the device supports binding multisample images as storage images. bool IsStorageImageMultisampleSupported() const { return features.features.shaderStorageImageMultisample; @@ -509,7 +523,6 @@ FN_MAX_LIMIT_LIST } /// Returns true if the device supports VK_EXT_shader_stencil_export. - /// Note: Most Mali/NVIDIA drivers don't support this. Use hardware blits as fallback. bool IsExtShaderStencilExportSupported() const { return extensions.shader_stencil_export; } @@ -546,6 +559,11 @@ FN_MAX_LIMIT_LIST return extensions.subgroup_size_control; } + /// Returns true if vkResetQueryPool (host-side query reset) is supported. + bool IsHostQueryResetSupported() const { + return features.host_query_reset.hostQueryReset != VK_FALSE; + } + /// Returns true if the device supports VK_EXT_transform_feedback. bool IsExtTransformFeedbackSupported() const { return extensions.transform_feedback; @@ -582,6 +600,21 @@ FN_MAX_LIMIT_LIST return features.custom_border_color.customBorderColorWithoutFormat; } + /// Returns true if the device supports VK_EXT_color_write_enable. + bool IsExtColorWriteEnableSupported() const { + return extensions.color_write_enable; + } + + /// Returns true if the device supports VK_EXT_border_color_swizzle. + bool IsExtBorderColorSwizzleSupported() const { + return extensions.border_color_swizzle; + } + + /// Returns true if borderColorSwizzleFromImage is available. + bool IsBorderColorSwizzleFromImageSupported() const { + return features.border_color_swizzle.borderColorSwizzleFromImage; + } + /// Returns true if the device supports VK_EXT_extended_dynamic_state. bool IsExtExtendedDynamicStateSupported() const { return extensions.extended_dynamic_state; @@ -722,6 +755,11 @@ FN_MAX_LIMIT_LIST bool HasTimelineSemaphore() const; + /// Returns true if the device supports VK_KHR_synchronization2. + bool HasSynchronization2() const { + return extensions.synchronization2; + } + /// Returns the minimum supported version of SPIR-V. u32 SupportedSpirvVersion() const { if (instance_version >= VK_API_VERSION_1_3) { diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 3d082c8360..e5d5e93426 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -123,6 +123,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdEndDebugUtilsLabelEXT); X(vkCmdFillBuffer); X(vkCmdPipelineBarrier); + X(vkCmdPipelineBarrier2); X(vkCmdPushConstants); X(vkCmdPushDescriptorSetWithTemplateKHR); X(vkCmdSetBlendConstants); @@ -161,8 +162,10 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdSetStencilTestEnableEXT); X(vkCmdSetVertexInputEXT); X(vkCmdSetColorWriteMaskEXT); + X(vkCmdSetColorWriteEnableEXT); X(vkCmdSetColorBlendEnableEXT); X(vkCmdSetColorBlendEquationEXT); + X(vkCmdResetQueryPool); X(vkCmdResolveImage); X(vkCreateBuffer); X(vkCreateBufferView); @@ -226,6 +229,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkGetSemaphoreCounterValue); X(vkMapMemory); X(vkQueueSubmit); + X(vkQueueSubmit2); X(vkResetFences); X(vkResetQueryPool); X(vkSetDebugUtilsObjectNameEXT); @@ -252,6 +256,14 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { Proc(dld.vkCmdDrawIndirectCount, dld, "vkCmdDrawIndirectCountKHR", device); Proc(dld.vkCmdDrawIndexedIndirectCount, dld, "vkCmdDrawIndexedIndirectCountKHR", device); } + + // Synchronization2 is core in Vulkan 1.3, otherwise requires VK_KHR_synchronization2 + if (!dld.vkCmdPipelineBarrier2) { + Proc(dld.vkCmdPipelineBarrier2, dld, "vkCmdPipelineBarrier2KHR", device); + } + if (!dld.vkQueueSubmit2) { + Proc(dld.vkQueueSubmit2, dld, "vkQueueSubmit2KHR", device); + } #undef X } diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 64ee105f82..e50856533e 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -237,8 +238,10 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT{}; PFN_vkCmdFillBuffer vkCmdFillBuffer{}; PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier{}; + PFN_vkCmdPipelineBarrier2 vkCmdPipelineBarrier2{}; PFN_vkCmdPushConstants vkCmdPushConstants{}; PFN_vkCmdPushDescriptorSetWithTemplateKHR vkCmdPushDescriptorSetWithTemplateKHR{}; + PFN_vkCmdResetQueryPool vkCmdResetQueryPool{}; PFN_vkCmdResolveImage vkCmdResolveImage{}; PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{}; PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{}; @@ -275,6 +278,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCmdSetVertexInputEXT vkCmdSetVertexInputEXT{}; PFN_vkCmdSetViewport vkCmdSetViewport{}; PFN_vkCmdSetColorWriteMaskEXT vkCmdSetColorWriteMaskEXT{}; + PFN_vkCmdSetColorWriteEnableEXT vkCmdSetColorWriteEnableEXT{}; PFN_vkCmdSetColorBlendEnableEXT vkCmdSetColorBlendEnableEXT{}; PFN_vkCmdSetColorBlendEquationEXT vkCmdSetColorBlendEquationEXT{}; PFN_vkCmdWaitEvents vkCmdWaitEvents{}; @@ -340,6 +344,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue{}; PFN_vkMapMemory vkMapMemory{}; PFN_vkQueueSubmit vkQueueSubmit{}; + PFN_vkQueueSubmit2 vkQueueSubmit2{}; PFN_vkResetFences vkResetFences{}; PFN_vkResetQueryPool vkResetQueryPool{}; PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT{}; @@ -819,6 +824,12 @@ public: return dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence); } + /// Submits using VK_KHR_synchronization2 / Vulkan 1.3 vkQueueSubmit2. + VkResult Submit2(Span submit_infos, + VkFence fence = VK_NULL_HANDLE) const noexcept { + return dld->vkQueueSubmit2(queue, submit_infos.size(), submit_infos.data(), fence); + } + VkResult Present(const VkPresentInfoKHR& present_info) const noexcept { return dld->vkQueuePresentKHR(queue, &present_info); } @@ -1180,6 +1191,10 @@ public: dld->vkCmdEndQuery(handle, query_pool, query); } + void ResetQueryPool(VkQueryPool query_pool, u32 first, u32 count) const noexcept { + dld->vkCmdResetQueryPool(handle, query_pool, first, count); + } + void BindDescriptorSets(VkPipelineBindPoint bind_point, VkPipelineLayout layout, u32 first, Span sets, Span dynamic_offsets) const noexcept { dld->vkCmdBindDescriptorSets(handle, bind_point, layout, first, sets.size(), sets.data(), @@ -1287,6 +1302,72 @@ public: VkDependencyFlags dependency_flags, Span memory_barriers, Span buffer_barriers, Span image_barriers) const noexcept { + static constexpr u32 MaxBarriers = 16; + if (dld->vkCmdPipelineBarrier2 && memory_barriers.size() <= MaxBarriers && + buffer_barriers.size() <= MaxBarriers && image_barriers.size() <= MaxBarriers) { + const auto src_stage_mask2 = static_cast(src_stage_mask); + const auto dst_stage_mask2 = static_cast(dst_stage_mask); + + std::array memory_barriers2; + for (u32 i = 0; i < memory_barriers.size(); ++i) { + memory_barriers2[i] = VkMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(memory_barriers[i].srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(memory_barriers[i].dstAccessMask), + }; + } + std::array buffer_barriers2; + for (u32 i = 0; i < buffer_barriers.size(); ++i) { + const auto& barrier = buffer_barriers[i]; + buffer_barriers2[i] = VkBufferMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(barrier.srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(barrier.dstAccessMask), + .srcQueueFamilyIndex = barrier.srcQueueFamilyIndex, + .dstQueueFamilyIndex = barrier.dstQueueFamilyIndex, + .buffer = barrier.buffer, + .offset = barrier.offset, + .size = barrier.size, + }; + } + std::array image_barriers2; + for (u32 i = 0; i < image_barriers.size(); ++i) { + const auto& barrier = image_barriers[i]; + image_barriers2[i] = VkImageMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(barrier.srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(barrier.dstAccessMask), + .oldLayout = barrier.oldLayout, + .newLayout = barrier.newLayout, + .srcQueueFamilyIndex = barrier.srcQueueFamilyIndex, + .dstQueueFamilyIndex = barrier.dstQueueFamilyIndex, + .image = barrier.image, + .subresourceRange = barrier.subresourceRange, + }; + } + const VkDependencyInfo dependency_info{ + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pNext = nullptr, + .dependencyFlags = dependency_flags, + .memoryBarrierCount = memory_barriers.size(), + .pMemoryBarriers = memory_barriers2.data(), + .bufferMemoryBarrierCount = buffer_barriers.size(), + .pBufferMemoryBarriers = buffer_barriers2.data(), + .imageMemoryBarrierCount = image_barriers.size(), + .pImageMemoryBarriers = image_barriers2.data(), + }; + dld->vkCmdPipelineBarrier2(handle, &dependency_info); + return; + } dld->vkCmdPipelineBarrier(handle, src_stage_mask, dst_stage_mask, dependency_flags, memory_barriers.size(), memory_barriers.data(), buffer_barriers.size(), buffer_barriers.data(), @@ -1510,6 +1591,10 @@ public: dld->vkCmdSetColorWriteMaskEXT(handle, first, masks.size(), masks.data()); } + void SetColorWriteEnableEXT(Span enables) const noexcept { + dld->vkCmdSetColorWriteEnableEXT(handle, enables.size(), enables.data()); + } + void SetColorBlendEnableEXT(u32 first, Span enables) const noexcept { dld->vkCmdSetColorBlendEnableEXT(handle, first, enables.size(), enables.data()); }