aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCamilleLaVey <camillelavey99@gmail.com>2026-07-09 04:22:43 +0200
committercrueter <crueter@eden-emu.dev>2026-07-09 04:22:43 +0200
commit8225151a4469a13ac602215dbeb2ce9a3702f38b (patch)
treeca17c9a7f88e3bde78edc6bead17c103289757ca /src
parent41762940d6bd6350901f12662248b95b91b51e6c (diff)
[vulkan] 3rd Vulkan Global Maintenance (#4189)
Following the philosophy on the previous Vulkan maintenance PR's, this one is reviewing the video_core to resolve VUID's related to the missing handling/ safechecks (UBO's case on missing StorageBufferAccess 8/16 bits on QCOM drivers, resulting on bad compute pipeline compiled), wiring topology representative to EDS2 configuration (following #4117), refactored the sampled image access via new memeber function for type "typeless" which adds a handling on the integer mistmatch (uint to sint, float to uint) + fixing bugs on current texture sampling (int) including the depth/stencil path resolve, maintenance to previous changes on query cache (resolving bugs from #3853), reduced the amount of binding BindVertexBufferEXT2 when a new tick/ frame is presented (with this being a reason for performance reduction on games where vertex polutes the stage, like BOTW/TOTK where grass is draw with vertex); implemented color border swizzle and color write enable for future improvements on EDS3, added initial implementation on Synchronization2 starting a path to ensure an access to VK 1.3 features safely and other smaller fixes along the road. Special Thanks: -> Mr. Smolio Gidolard (@gidoly) Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4189
Diffstat (limited to 'src')
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv.cpp3
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp12
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image.cpp63
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp19
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h1
-rw-r--r--src/shader_recompiler/frontend/ir/modifiers.h7
-rw-r--r--src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp12
-rw-r--r--src/shader_recompiler/ir_opt/texture_pass.cpp21
-rw-r--r--src/shader_recompiler/profile.h2
-rw-r--r--src/shader_recompiler/shader_info.h1
-rw-r--r--src/video_core/host_shaders/vulkan_blit_depth_stencil.frag7
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.cpp66
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.h4
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp16
-rw-r--r--src/video_core/renderer_vulkan/pipeline_helper.h33
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp16
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h24
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.cpp8
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp21
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.cpp124
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp38
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp40
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp51
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h1
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp15
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp43
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h10
-rw-r--r--src/video_core/surface.cpp4
-rw-r--r--src/video_core/surface.h4
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp9
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp58
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h42
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp12
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h85
34 files changed, 739 insertions, 133 deletions
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<IR::TextureInstInfo>()};
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<IR::TextureInstInfo>()};
+ 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<IR::TextureInstInfo>()};
+ 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<IR::TextureInstInfo>()};
+ 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<IR::TextureInstInfo>()};
+ 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<IR::TextureInstInfo>()};
+ 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<IR::TextureInstInfo>()};
+ 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<IR::TextureInstInfo>()};
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<IR::TextureInstInfo>()};
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<IR::TextureInstInfo>()};
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<size_t>(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 <cstddef>
+#include <optional>
#include <boost/container/small_vector.hpp>
@@ -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<PixelFormat> 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<Buffer>& 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<Buffer>& 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<BufferView> views;
VideoCommon::UsageTracker tracker;
+ u64 last_usage_tick{};
bool is_null{};
};
@@ -127,6 +131,10 @@ public:
void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& 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<Buffer>& 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<u32>::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<VkCommandBufferSubmitInfo, 2> 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<VkSemaphoreSubmitInfo, 2> 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<VkPipelineStageFlags2>(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<u32>(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<VkCommandBufferSubmitInfo, 2> 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<VkPipelineStageFlags2>(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<u32>(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<const Shader::IR::Program> program
key.state.UnpackComparisonOp(key.state.alpha_test_func.Value()));
info.alpha_test_reference = std::bit_cast<float>(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<u64, BANK_SIZE> host_results;
+ u64 last_used_tick{};
};
using BaseStreamer = VideoCommon::SimpleStreamer<VideoCommon::HostQueryBase>;
@@ -431,7 +449,7 @@ private:
void ReserveBank() {
current_bank_id =
bank_pool.ReserveBank([this](std::deque<SamplesQueryBank>& 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 <typename Func>
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<VkBool32, Maxwell::NumRenderTargets> 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<VkBool32, Maxwell::NumRenderTargets> 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<VkImageMemoryBarrier, 9> 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<s32>(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<VkBool32>(anisotropy > 1.0f ? VK_TRUE : VK_FALSE),
- .maxAnisotropy = anisotropy,
+ .anisotropyEnable =
+ static_cast<VkBool32>(!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<f32>(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<vk::ImageView, Shader::NUM_TEXTURE_TYPES> image_views;
std::optional<StorageViews> 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<bool>(sampler_default_anisotropy);
}
+ [[nodiscard]] VkSampler HandleWithNearestFilter() const noexcept {
+ return *sampler_nearest;
+ }
+
+ [[nodiscard]] bool HasLinearFiltering() const noexcept {
+ return static_cast<bool>(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 <typename T>
@@ -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<const u32> spirv) const {
}
bool Device::ComputeIsOptimalAstcSupported() const {
- // Verify hardware supports all ASTC formats with optimal tiling to avoid software conversion
static constexpr std::array<VkFormat, 28> 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 <array>
#include <exception>
#include <limits>
#include <memory>
@@ -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<VkSubmitInfo2> 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<VkDescriptorSet> sets, Span<u32> dynamic_offsets) const noexcept {
dld->vkCmdBindDescriptorSets(handle, bind_point, layout, first, sets.size(), sets.data(),
@@ -1287,6 +1302,72 @@ public:
VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers,
Span<VkBufferMemoryBarrier> buffer_barriers,
Span<VkImageMemoryBarrier> 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<VkPipelineStageFlags2>(src_stage_mask);
+ const auto dst_stage_mask2 = static_cast<VkPipelineStageFlags2>(dst_stage_mask);
+
+ std::array<VkMemoryBarrier2, MaxBarriers> 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<VkAccessFlags2>(memory_barriers[i].srcAccessMask),
+ .dstStageMask = dst_stage_mask2,
+ .dstAccessMask = static_cast<VkAccessFlags2>(memory_barriers[i].dstAccessMask),
+ };
+ }
+ std::array<VkBufferMemoryBarrier2, MaxBarriers> 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<VkAccessFlags2>(barrier.srcAccessMask),
+ .dstStageMask = dst_stage_mask2,
+ .dstAccessMask = static_cast<VkAccessFlags2>(barrier.dstAccessMask),
+ .srcQueueFamilyIndex = barrier.srcQueueFamilyIndex,
+ .dstQueueFamilyIndex = barrier.dstQueueFamilyIndex,
+ .buffer = barrier.buffer,
+ .offset = barrier.offset,
+ .size = barrier.size,
+ };
+ }
+ std::array<VkImageMemoryBarrier2, MaxBarriers> 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<VkAccessFlags2>(barrier.srcAccessMask),
+ .dstStageMask = dst_stage_mask2,
+ .dstAccessMask = static_cast<VkAccessFlags2>(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<VkBool32> enables) const noexcept {
+ dld->vkCmdSetColorWriteEnableEXT(handle, enables.size(), enables.data());
+ }
+
void SetColorBlendEnableEXT(u32 first, Span<VkBool32> enables) const noexcept {
dld->vkCmdSetColorBlendEnableEXT(handle, first, enables.size(), enables.data());
}