Fix mvk dynamic state & limit (#361)
* Fix MoltenVK rendering compatibility Gate unsupported dynamic state and compute subgroup requests, cap push descriptors, use canonical uniform views, match integer fragment outputs, and honor tiled-cache barriers. Disable blending on integer targets in static and dynamic state, including pipeline transitions. Redirect unused duplicate attachments to cached transient scratch targets with lazy-memory preference and a normal-memory fallback. Isolate masked color and stencil clears by target and layer, preserve component masks, and match integer output types and MSAA samples. Cache duplicate groups and diagnostics, and avoid empty helper render passes. Preserve genuine duplicate writers and warn rather than discard output. Respect advertised compute subgroup stages on all drivers and scalarize wide global buffer accesses when descriptor aliases are unavailable. Destroy framebuffer owners in dependency order during move assignment. Adapt Eden 9237eebc4f, a42ae226c8, 873acb9a42, 3795e4e4fc, 884d2ac228, 643f11d972 and 39841b35da. Preserve the Ryubing duplicate-attachment MIT notice. * Apply integer render target fixes on all drivers Typed fragment outputs and disabled blending on integer targets are Vulkan rules, not MoltenVK ones. Refresh the dynamic blend enable when a render target format changes. Skip the alpha test when the first color output is an integer. * Enable portability subset and fix MoltenVK vertex and SSBO bindings Enable VK_KHR_portability_subset when the device exposes it, as the Vulkan specification requires, and query its features. Keep static vertex attribute formats within their binding stride on MoltenVK and on hosts without vertexAttributeAccessBeyondStride. Trailing components are dropped only when the vertex shader never reads them; otherwise the original format is kept. Strides of enabled streams join the pipeline key only in this mode, and cached pipelines built without it are skipped on load. Make storage buffer bindings visible to every graphics stage using descriptors on MoltenVK, which can otherwise lose their resource usage across stages (KhronosGroup/MoltenVK#1870). The layout is widened only while the per-stage storage buffer and resource limits still hold. Adapt the Ryubing vertex attribute format reduction and storage buffer stage workaround for MoltenVK. * Emit signed texel offsets for texture gather The IR carries gather offsets as unsigned vectors and they were passed to OpImageGather unchanged. SPIRV-Cross then emits a uint2 offset, for which Metal has no gather overload, so the shader fails to compile under MoltenVK and pipeline creation throws VK_ERROR_INITIALIZATION_FAILED. Emit the offsets as signed integers, including the ConstOffsets array.
This commit is contained in:
parent
f2b3bf6c4a
commit
e950199f03
|
|
@ -231,6 +231,18 @@
|
|||
<Command>call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
|
||||
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="..\yuzu_video_core\host_shaders\vulkan_color_clear_sint.frag">
|
||||
<FileType>Document</FileType>
|
||||
<Message>Compiling %(Filename)%(Extension)</Message>
|
||||
<Command>call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
|
||||
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="..\yuzu_video_core\host_shaders\vulkan_color_clear_uint.frag">
|
||||
<FileType>Document</FileType>
|
||||
<Message>Compiling %(Filename)%(Extension)</Message>
|
||||
<Command>call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
|
||||
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="..\yuzu_video_core\host_shaders\vulkan_depthstencil_clear.frag">
|
||||
<FileType>Document</FileType>
|
||||
<Message>Compiling %(Filename)%(Extension)</Message>
|
||||
|
|
|
|||
|
|
@ -160,6 +160,12 @@
|
|||
<CustomBuild Include="..\yuzu_video_core\host_shaders\vulkan_color_clear.frag">
|
||||
<Filter>vulkan.frag</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="..\yuzu_video_core\host_shaders\vulkan_color_clear_sint.frag">
|
||||
<Filter>vulkan.frag</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="..\yuzu_video_core\host_shaders\vulkan_color_clear_uint.frag">
|
||||
<Filter>vulkan.frag</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="..\yuzu_video_core\host_shaders\vulkan_blit_depth_stencil.frag">
|
||||
<Filter>vulkan.frag</Filter>
|
||||
</CustomBuild>
|
||||
|
|
|
|||
|
|
@ -159,13 +159,14 @@ Id GetCbufU32x4(EmitContext& ctx, const IR::Value& binding, const IR::Value& off
|
|||
ctx.load_const_func_u32x4);
|
||||
}
|
||||
|
||||
Id GetCbufElement(EmitContext& ctx, Id vector, const IR::Value& offset, u32 index_offset) {
|
||||
Id GetCbufElement(EmitContext& ctx, Id vector, const IR::Value& offset, u32 index_offset,
|
||||
u32 element_mask = 3u) {
|
||||
if (offset.IsImmediate()) {
|
||||
const u32 element{(offset.U32() / 4) % 4 + index_offset};
|
||||
const u32 element{((offset.U32() / 4) & element_mask) + index_offset};
|
||||
return ctx.OpCompositeExtract(ctx.U32[1], vector, element);
|
||||
}
|
||||
const Id shift{ctx.OpShiftRightLogical(ctx.U32[1], ctx.Def(offset), ctx.Const(2u))};
|
||||
Id element{ctx.OpBitwiseAnd(ctx.U32[1], shift, ctx.Const(3u))};
|
||||
Id element{ctx.OpBitwiseAnd(ctx.U32[1], shift, ctx.Const(element_mask))};
|
||||
if (index_offset > 0) {
|
||||
element = ctx.OpIAdd(ctx.U32[1], element, ctx.Const(index_offset));
|
||||
}
|
||||
|
|
@ -206,13 +207,13 @@ 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.SupportsUniformBufferDescriptorAliasing() && ctx.profile.support_int8) {
|
||||
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);
|
||||
}
|
||||
Id element{};
|
||||
if (ctx.profile.support_descriptor_aliasing) {
|
||||
if (ctx.profile.SupportsUniformBufferDescriptorAliasing()) {
|
||||
element = GetCbufU32(ctx, binding, offset);
|
||||
} else {
|
||||
const Id vector{GetCbufU32x4(ctx, binding, offset)};
|
||||
|
|
@ -223,13 +224,13 @@ 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.SupportsUniformBufferDescriptorAliasing() && ctx.profile.support_int8) {
|
||||
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);
|
||||
}
|
||||
Id element{};
|
||||
if (ctx.profile.support_descriptor_aliasing) {
|
||||
if (ctx.profile.SupportsUniformBufferDescriptorAliasing()) {
|
||||
element = GetCbufU32(ctx, binding, offset);
|
||||
} else {
|
||||
const Id vector{GetCbufU32x4(ctx, binding, offset)};
|
||||
|
|
@ -240,13 +241,13 @@ 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.SupportsUniformBufferDescriptorAliasing() && ctx.profile.support_int16) {
|
||||
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);
|
||||
}
|
||||
Id element{};
|
||||
if (ctx.profile.support_descriptor_aliasing) {
|
||||
if (ctx.profile.SupportsUniformBufferDescriptorAliasing()) {
|
||||
element = GetCbufU32(ctx, binding, offset);
|
||||
} else {
|
||||
const Id vector{GetCbufU32x4(ctx, binding, offset)};
|
||||
|
|
@ -257,13 +258,13 @@ 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.SupportsUniformBufferDescriptorAliasing() && ctx.profile.support_int16) {
|
||||
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);
|
||||
}
|
||||
Id element{};
|
||||
if (ctx.profile.support_descriptor_aliasing) {
|
||||
if (ctx.profile.SupportsUniformBufferDescriptorAliasing()) {
|
||||
element = GetCbufU32(ctx, binding, offset);
|
||||
} else {
|
||||
const Id vector{GetCbufU32x4(ctx, binding, offset)};
|
||||
|
|
@ -274,7 +275,7 @@ Id EmitGetCbufS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& o
|
|||
}
|
||||
|
||||
Id EmitGetCbufU32(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
|
||||
if (ctx.profile.support_descriptor_aliasing) {
|
||||
if (ctx.profile.SupportsUniformBufferDescriptorAliasing()) {
|
||||
return GetCbufU32(ctx, binding, offset);
|
||||
} else {
|
||||
const Id vector{GetCbufU32x4(ctx, binding, offset)};
|
||||
|
|
@ -283,7 +284,7 @@ Id EmitGetCbufU32(EmitContext& ctx, const IR::Value& binding, const IR::Value& o
|
|||
}
|
||||
|
||||
Id EmitGetCbufF32(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
|
||||
if (ctx.profile.support_descriptor_aliasing) {
|
||||
if (ctx.profile.SupportsUniformBufferDescriptorAliasing()) {
|
||||
return GetCbuf(ctx, ctx.F32[1], &UniformDefinitions::F32, sizeof(f32), binding, offset,
|
||||
ctx.load_const_func_f32);
|
||||
} else {
|
||||
|
|
@ -293,13 +294,15 @@ Id EmitGetCbufF32(EmitContext& ctx, const IR::Value& binding, const IR::Value& o
|
|||
}
|
||||
|
||||
Id EmitGetCbufU32x2(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
|
||||
if (ctx.profile.support_descriptor_aliasing) {
|
||||
if (ctx.profile.SupportsUniformBufferDescriptorAliasing()) {
|
||||
return GetCbuf(ctx, ctx.U32[2], &UniformDefinitions::U32x2, sizeof(u32[2]), binding, offset,
|
||||
ctx.load_const_func_u32x2);
|
||||
} else {
|
||||
const Id vector{GetCbufU32x4(ctx, binding, offset)};
|
||||
return ctx.OpCompositeConstruct(ctx.U32[2], GetCbufElement(ctx, vector, offset, 0u),
|
||||
GetCbufElement(ctx, vector, offset, 1u));
|
||||
// Match the aliased U32x2 view's eight-byte alignment. The pair must
|
||||
// start at component zero or two, never past the end of this U32x4.
|
||||
return ctx.OpCompositeConstruct(ctx.U32[2], GetCbufElement(ctx, vector, offset, 0u, 2u),
|
||||
GetCbufElement(ctx, vector, offset, 1u, 2u));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -480,7 +483,20 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) {
|
|||
|
||||
void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) {
|
||||
const Id component_id{ctx.Const(component)};
|
||||
const Id pointer{ctx.OpAccessChain(ctx.output_f32, ctx.frag_color.at(index), component_id)};
|
||||
Id pointer_type{ctx.output_f32};
|
||||
switch (ctx.runtime_info.color_output_types.at(index)) {
|
||||
case AttributeType::UnsignedInt:
|
||||
pointer_type = ctx.output_u32;
|
||||
value = ctx.OpBitcast(ctx.U32[1], value);
|
||||
break;
|
||||
case AttributeType::SignedInt:
|
||||
pointer_type = ctx.TypePointer(spv::StorageClass::Output, ctx.S32[1]);
|
||||
value = ctx.OpBitcast(ctx.S32[1], value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const Id pointer{ctx.OpAccessChain(pointer_type, ctx.frag_color.at(index), component_id)};
|
||||
ctx.OpStore(pointer, value);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public:
|
|||
if (offset.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
Add(spv::ImageOperandsMask::Offset, ctx.Def(offset));
|
||||
Add(spv::ImageOperandsMask::Offset, SignedOffset(ctx, offset));
|
||||
return;
|
||||
}
|
||||
const std::array values{offset.InstRecursive(), offset2.InstRecursive()};
|
||||
|
|
@ -51,12 +51,14 @@ public:
|
|||
if (opcode != values[1]->GetOpcode() || opcode != IR::Opcode::CompositeConstructU32x4) {
|
||||
throw LogicError("Invalid PTP arguments");
|
||||
}
|
||||
auto read{[&](unsigned int a, unsigned int b) { return values[a]->Arg(b).U32(); }};
|
||||
auto read{[&](unsigned int a, unsigned int b) {
|
||||
return static_cast<s32>(values[a]->Arg(b).U32());
|
||||
}};
|
||||
|
||||
const Id offsets{ctx.ConstantComposite(
|
||||
ctx.TypeArray(ctx.U32[2], ctx.Const(4U)), ctx.Const(read(0, 0), read(0, 1)),
|
||||
ctx.Const(read(0, 2), read(0, 3)), ctx.Const(read(1, 0), read(1, 1)),
|
||||
ctx.Const(read(1, 2), read(1, 3)))};
|
||||
ctx.TypeArray(ctx.S32[2], ctx.Const(4U)), ctx.SConst(read(0, 0), read(0, 1)),
|
||||
ctx.SConst(read(0, 2), read(0, 3)), ctx.SConst(read(1, 0), read(1, 1)),
|
||||
ctx.SConst(read(1, 2), read(1, 3)))};
|
||||
Add(spv::ImageOperandsMask::ConstOffsets, offsets);
|
||||
}
|
||||
|
||||
|
|
@ -130,6 +132,46 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
// The IR carries offsets as unsigned vectors. Texel offsets are signed, and
|
||||
// Metal has no gather overload taking an unsigned one, so MoltenVK fails to
|
||||
// compile the shader.
|
||||
static Id SignedOffset(EmitContext& ctx, const IR::Value& offset) {
|
||||
if (offset.IsImmediate()) {
|
||||
return ctx.SConst(static_cast<s32>(offset.U32()));
|
||||
}
|
||||
IR::Inst* const inst{offset.InstRecursive()};
|
||||
if (inst->AreAllArgsImmediates()) {
|
||||
switch (inst->GetOpcode()) {
|
||||
case IR::Opcode::CompositeConstructU32x2:
|
||||
return ctx.SConst(static_cast<s32>(inst->Arg(0).U32()),
|
||||
static_cast<s32>(inst->Arg(1).U32()));
|
||||
case IR::Opcode::CompositeConstructU32x3:
|
||||
return ctx.SConst(static_cast<s32>(inst->Arg(0).U32()),
|
||||
static_cast<s32>(inst->Arg(1).U32()),
|
||||
static_cast<s32>(inst->Arg(2).U32()));
|
||||
case IR::Opcode::CompositeConstructU32x4:
|
||||
return ctx.SConst(static_cast<s32>(inst->Arg(0).U32()),
|
||||
static_cast<s32>(inst->Arg(1).U32()),
|
||||
static_cast<s32>(inst->Arg(2).U32()),
|
||||
static_cast<s32>(inst->Arg(3).U32()));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (offset.Type()) {
|
||||
case IR::Type::U32:
|
||||
return ctx.OpBitcast(ctx.S32[1], ctx.Def(offset));
|
||||
case IR::Type::U32x2:
|
||||
return ctx.OpBitcast(ctx.S32[2], ctx.Def(offset));
|
||||
case IR::Type::U32x3:
|
||||
return ctx.OpBitcast(ctx.S32[3], ctx.Def(offset));
|
||||
case IR::Type::U32x4:
|
||||
return ctx.OpBitcast(ctx.S32[4], ctx.Def(offset));
|
||||
default:
|
||||
return ctx.Def(offset);
|
||||
}
|
||||
}
|
||||
|
||||
void AddOffset(EmitContext& ctx, const IR::Value& offset, bool runtime_offset_allowed) {
|
||||
if (offset.IsEmpty()) {
|
||||
return;
|
||||
|
|
@ -164,7 +206,7 @@ private:
|
|||
}
|
||||
}
|
||||
if (runtime_offset_allowed) {
|
||||
Add(spv::ImageOperandsMask::Offset, ctx.Def(offset));
|
||||
Add(spv::ImageOperandsMask::Offset, SignedOffset(ctx, offset));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,11 @@ void AlphaTest(EmitContext& ctx) {
|
|||
if (!Sirit::ValidId(ctx.frag_color[0])) {
|
||||
return;
|
||||
}
|
||||
// The alpha test does not apply to integer color outputs.
|
||||
const AttributeType output_type{ctx.runtime_info.color_output_types[0]};
|
||||
if (output_type == AttributeType::SignedInt || output_type == AttributeType::UnsignedInt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Id type{ctx.F32[1]};
|
||||
const Id rt0_color{ctx.OpLoad(ctx.F32[4], ctx.frag_color[0])};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "yuzu_common/common_types.h"
|
||||
#include "yuzu_common/div_ceil.h"
|
||||
#include "yuzu_shader_recompiler/backend/spirv/emit_spirv.h"
|
||||
#include "yuzu_shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||
#include "yuzu_shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
|
|
@ -934,8 +935,9 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
|
|||
}
|
||||
using DefPtr = Id StorageDefinitions::*;
|
||||
const Id zero{u32_zero_value};
|
||||
const bool supports_uniform_aliasing{profile.SupportsUniformBufferDescriptorAliasing()};
|
||||
const auto define_body{[&](DefPtr ssbo_member, Id addr, Id element_pointer, u32 shift,
|
||||
auto&& callback) {
|
||||
u32 scalar_components, auto&& callback) {
|
||||
AddLabel();
|
||||
const size_t num_buffers{info.storage_buffers_descriptors.size()};
|
||||
for (size_t index = 0; index < num_buffers; ++index) {
|
||||
|
|
@ -943,64 +945,130 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
|
|||
continue;
|
||||
}
|
||||
const auto& ssbo{info.storage_buffers_descriptors[index]};
|
||||
const Id ssbo_addr_cbuf_offset{Const(ssbo.cbuf_offset / 8)};
|
||||
const Id ssbo_size_cbuf_offset{Const(ssbo.cbuf_offset / 4 + 2)};
|
||||
const Id ssbo_addr_pointer{OpAccessChain(
|
||||
uniform_types.U32x2, cbufs[ssbo.cbuf_index].U32x2, zero, ssbo_addr_cbuf_offset)};
|
||||
const Id ssbo_size_pointer{OpAccessChain(uniform_types.U32, cbufs[ssbo.cbuf_index].U32,
|
||||
zero, ssbo_size_cbuf_offset)};
|
||||
|
||||
Id unaligned_addr{};
|
||||
Id ssbo_size_pointer{};
|
||||
Id ssbo_size_word{};
|
||||
if (!supports_uniform_aliasing) {
|
||||
// The canonical uniform view has no U32/U32x2 descriptor aliases.
|
||||
const IR::Value cbuf_index{ssbo.cbuf_index};
|
||||
unaligned_addr = OpBitcast(
|
||||
U64, EmitGetCbufU32x2(*this, cbuf_index, IR::Value{ssbo.cbuf_offset}));
|
||||
ssbo_size_word =
|
||||
EmitGetCbufU32(*this, cbuf_index, IR::Value{ssbo.cbuf_offset + 8});
|
||||
} else {
|
||||
const Id ssbo_addr_cbuf_offset{Const(ssbo.cbuf_offset / 8)};
|
||||
const Id ssbo_size_cbuf_offset{Const(ssbo.cbuf_offset / 4 + 2)};
|
||||
const Id ssbo_addr_pointer{OpAccessChain(
|
||||
uniform_types.U32x2, cbufs[ssbo.cbuf_index].U32x2, zero, ssbo_addr_cbuf_offset)};
|
||||
ssbo_size_pointer = OpAccessChain(uniform_types.U32, cbufs[ssbo.cbuf_index].U32,
|
||||
zero, ssbo_size_cbuf_offset);
|
||||
unaligned_addr = OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer));
|
||||
}
|
||||
const u64 ssbo_align_mask{~(profile.min_ssbo_alignment - 1U)};
|
||||
const Id unaligned_addr{OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer))};
|
||||
const Id ssbo_addr{OpBitwiseAnd(U64, unaligned_addr, Constant(U64, ssbo_align_mask))};
|
||||
const Id ssbo_size{OpUConvert(U64, OpLoad(U32[1], ssbo_size_pointer))};
|
||||
const Id ssbo_end{OpIAdd(U64, ssbo_addr, ssbo_size)};
|
||||
const Id cond{OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, ssbo_addr),
|
||||
OpULessThan(U1, addr, ssbo_end))};
|
||||
const Id ssbo_size{OpUConvert(U64, !supports_uniform_aliasing
|
||||
? ssbo_size_word
|
||||
: OpLoad(U32[1], ssbo_size_pointer))};
|
||||
// Validate the complete access against the guest range, not the host
|
||||
// binding's alignment padding. Subtraction avoids overflowing its end.
|
||||
const Id guest_offset{OpISub(U64, addr, unaligned_addr)};
|
||||
const Id offset_in_range{
|
||||
OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, unaligned_addr),
|
||||
OpULessThanEqual(U1, guest_offset, ssbo_size))};
|
||||
const Id access_size{Constant(U64, u64{1} << shift)};
|
||||
const Id host_offset{OpISub(U64, addr, ssbo_addr)};
|
||||
// Storage buffer byte offsets are 32-bit; do not truncate a large offset.
|
||||
const Id max_host_offset{Constant(U64, u64{0x1'0000'0000} - (u64{1} << shift))};
|
||||
const Id cond{
|
||||
OpLogicalAnd(
|
||||
U1, offset_in_range,
|
||||
OpLogicalAnd(U1, OpULessThanEqual(U1, host_offset, max_host_offset),
|
||||
OpULessThanEqual(U1, access_size,
|
||||
OpISub(U64, ssbo_size, guest_offset))))};
|
||||
const Id then_label{OpLabel()};
|
||||
const Id else_label{OpLabel()};
|
||||
OpSelectionMerge(else_label, spv::SelectionControlMask::MaskNone);
|
||||
OpBranchConditional(cond, then_label, else_label);
|
||||
AddLabel(then_label);
|
||||
const Id ssbo_id{ssbos[index].*ssbo_member};
|
||||
const Id ssbo_offset{OpUConvert(U32[1], OpISub(U64, addr, ssbo_addr))};
|
||||
const Id ssbo_index{OpShiftRightLogical(U32[1], ssbo_offset, Const(shift))};
|
||||
const Id ssbo_offset{OpUConvert(U32[1], host_offset)};
|
||||
Id ssbo_index{OpShiftRightLogical(U32[1], ssbo_offset, Const(shift))};
|
||||
if (scalar_components > 1) {
|
||||
// Keep the vector view's alignment when indexing the canonical U32 view.
|
||||
ssbo_index = OpShiftLeftLogical(
|
||||
U32[1], ssbo_index,
|
||||
Const(static_cast<u32>(std::countr_zero(scalar_components))));
|
||||
}
|
||||
const Id ssbo_pointer{OpAccessChain(element_pointer, ssbo_id, zero, ssbo_index)};
|
||||
callback(ssbo_pointer);
|
||||
callback(ssbo_pointer, ssbo_id, ssbo_index);
|
||||
AddLabel(else_label);
|
||||
}
|
||||
}};
|
||||
const auto define_load{[&](DefPtr ssbo_member, Id element_pointer, Id type, u32 shift) {
|
||||
const auto define_load{[&](DefPtr ssbo_member, Id element_pointer, Id type, u32 shift,
|
||||
u32 scalar_components) {
|
||||
const Id function_type{TypeFunction(type, U64)};
|
||||
const Id func_id{OpFunction(type, spv::FunctionControlMask::MaskNone, function_type)};
|
||||
const Id addr{OpFunctionParameter(U64)};
|
||||
define_body(ssbo_member, addr, element_pointer, shift,
|
||||
[&](Id ssbo_pointer) { OpReturnValue(OpLoad(type, ssbo_pointer)); });
|
||||
define_body(ssbo_member, addr, element_pointer, shift, scalar_components,
|
||||
[&](Id ssbo_pointer, Id ssbo_id, Id ssbo_index) {
|
||||
if (scalar_components == 1) {
|
||||
OpReturnValue(OpLoad(type, ssbo_pointer));
|
||||
return;
|
||||
}
|
||||
std::array<Id, 4> components;
|
||||
components[0] = OpLoad(U32[1], ssbo_pointer);
|
||||
for (u32 i = 1; i < scalar_components; ++i) {
|
||||
const Id index{OpIAdd(U32[1], ssbo_index, Const(i))};
|
||||
const Id pointer{OpAccessChain(element_pointer, ssbo_id, zero, index)};
|
||||
components[i] = OpLoad(U32[1], pointer);
|
||||
}
|
||||
OpReturnValue(OpCompositeConstruct(
|
||||
type, std::span{components.data(), scalar_components}));
|
||||
});
|
||||
OpReturnValue(ConstantNull(type));
|
||||
OpFunctionEnd();
|
||||
return func_id;
|
||||
}};
|
||||
const auto define_write{[&](DefPtr ssbo_member, Id element_pointer, Id type, u32 shift) {
|
||||
const auto define_write{[&](DefPtr ssbo_member, Id element_pointer, Id type, u32 shift,
|
||||
u32 scalar_components) {
|
||||
const Id function_type{TypeFunction(void_id, U64, type)};
|
||||
const Id func_id{OpFunction(void_id, spv::FunctionControlMask::MaskNone, function_type)};
|
||||
const Id addr{OpFunctionParameter(U64)};
|
||||
const Id data{OpFunctionParameter(type)};
|
||||
define_body(ssbo_member, addr, element_pointer, shift, [&](Id ssbo_pointer) {
|
||||
OpStore(ssbo_pointer, data);
|
||||
OpReturn();
|
||||
});
|
||||
define_body(ssbo_member, addr, element_pointer, shift, scalar_components,
|
||||
[&](Id ssbo_pointer, Id ssbo_id, Id ssbo_index) {
|
||||
if (scalar_components == 1) {
|
||||
OpStore(ssbo_pointer, data);
|
||||
} else {
|
||||
OpStore(ssbo_pointer, OpCompositeExtract(U32[1], data, 0U));
|
||||
for (u32 i = 1; i < scalar_components; ++i) {
|
||||
const Id index{OpIAdd(U32[1], ssbo_index, Const(i))};
|
||||
const Id pointer{
|
||||
OpAccessChain(element_pointer, ssbo_id, zero, index)};
|
||||
OpStore(pointer, OpCompositeExtract(U32[1], data, i));
|
||||
}
|
||||
}
|
||||
OpReturn();
|
||||
});
|
||||
OpReturn();
|
||||
OpFunctionEnd();
|
||||
return func_id;
|
||||
}};
|
||||
const auto define{
|
||||
[&](DefPtr ssbo_member, const StorageTypeDefinition& type_def, Id type, size_t size) {
|
||||
const Id element_type{type_def.element};
|
||||
const u32 shift{static_cast<u32>(std::countr_zero(size))};
|
||||
const Id load_func{define_load(ssbo_member, element_type, type, shift)};
|
||||
const Id write_func{define_write(ssbo_member, element_type, type, shift)};
|
||||
return std::make_pair(load_func, write_func);
|
||||
}};
|
||||
const auto define{[&](DefPtr ssbo_member, const StorageTypeDefinition& type_def, Id type,
|
||||
size_t size) {
|
||||
Id element_type{type_def.element};
|
||||
const u32 shift{static_cast<u32>(std::countr_zero(size))};
|
||||
u32 scalar_components{1};
|
||||
if (!profile.support_descriptor_aliasing && size > sizeof(u32)) {
|
||||
ssbo_member = &StorageDefinitions::U32;
|
||||
element_type = storage_types.U32.element;
|
||||
scalar_components = static_cast<u32>(size / sizeof(u32));
|
||||
}
|
||||
const Id load_func{define_load(ssbo_member, element_type, type, shift, scalar_components)};
|
||||
const Id write_func{
|
||||
define_write(ssbo_member, element_type, type, shift, scalar_components)};
|
||||
return std::make_pair(load_func, write_func);
|
||||
}};
|
||||
std::tie(load_global_func_u32, write_global_func_u32) =
|
||||
define(&StorageDefinitions::U32, storage_types.U32, U32[1], sizeof(u32));
|
||||
std::tie(load_global_func_u32x2, write_global_func_u32x2) =
|
||||
|
|
@ -1110,7 +1178,7 @@ void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) {
|
|||
if (info.constant_buffer_descriptors.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!profile.support_descriptor_aliasing) {
|
||||
if (!profile.SupportsUniformBufferDescriptorAliasing()) {
|
||||
DefineConstBuffers(*this, info, &UniformDefinitions::U32x4, binding, U32[4], 'u',
|
||||
sizeof(u32[4]));
|
||||
for (const ConstantBufferDescriptor& desc : info.constant_buffer_descriptors) {
|
||||
|
|
@ -1188,7 +1256,7 @@ void EmitContext::DefineConstantBufferIndirectFunctions(const Info& info) {
|
|||
return func;
|
||||
}};
|
||||
IR::Type types{info.used_indirect_cbuf_types};
|
||||
bool supports_aliasing = profile.support_descriptor_aliasing;
|
||||
bool supports_aliasing = profile.SupportsUniformBufferDescriptorAliasing();
|
||||
if (supports_aliasing && True(types & IR::Type::U8)) {
|
||||
load_const_func_u8 = make_accessor(U8, &UniformDefinitions::U8);
|
||||
}
|
||||
|
|
@ -1655,7 +1723,8 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
|
|||
if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors) {
|
||||
continue;
|
||||
}
|
||||
frag_color[index] = DefineOutput(*this, F32[4], std::nullopt);
|
||||
const Id type{GetAttributeType(*this, runtime_info.color_output_types[index])};
|
||||
frag_color[index] = DefineOutput(*this, type, std::nullopt);
|
||||
Decorate(frag_color[index], spv::Decoration::Location, index);
|
||||
Name(frag_color[index], fmt::format("frag_color{}", index));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ struct Profile {
|
|||
u32 supported_spirv{0x00010000};
|
||||
bool unified_descriptor_binding{};
|
||||
bool support_descriptor_aliasing{};
|
||||
// MoltenVK needs one canonical uniform view; storage-buffer aliases remain available.
|
||||
bool disable_uniform_buffer_descriptor_aliasing{};
|
||||
bool support_int8{};
|
||||
bool support_int16{};
|
||||
bool support_int64{};
|
||||
|
|
@ -90,6 +92,10 @@ struct Profile {
|
|||
u64 min_ssbo_alignment{};
|
||||
|
||||
u32 max_user_clip_distances{};
|
||||
|
||||
bool SupportsUniformBufferDescriptorAliasing() const {
|
||||
return support_descriptor_aliasing && !disable_uniform_buffer_descriptor_aliasing;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
|
|
|||
|
|
@ -104,6 +104,10 @@ struct RuntimeInfo {
|
|||
/// Transform feedback state for each varying
|
||||
std::array<TransformFeedbackVarying, 256> xfb_varyings{};
|
||||
u32 xfb_count{0};
|
||||
|
||||
// Vulkan requires fragment outputs to match the attachment scalar type.
|
||||
// Float remains the default for other backends.
|
||||
std::array<AttributeType, 8> color_output_types{};
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
|
|
|||
|
|
@ -308,6 +308,7 @@ add_library(video_core STATIC
|
|||
vulkan_common/vulkan_debug_callback.h
|
||||
vulkan_common/vulkan_device.cpp
|
||||
vulkan_common/vulkan_device.h
|
||||
vulkan_common/moltenvk_workarounds.h
|
||||
vulkan_common/vulkan_instance.cpp
|
||||
vulkan_common/vulkan_instance.h
|
||||
vulkan_common/vulkan_library.cpp
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ set(SHADER_FILES
|
|||
smaa_neighborhood_blending.frag
|
||||
vulkan_blit_depth_stencil.frag
|
||||
vulkan_color_clear.frag
|
||||
vulkan_color_clear_sint.frag
|
||||
vulkan_color_clear_uint.frag
|
||||
vulkan_color_clear.vert
|
||||
vulkan_depthstencil_clear.frag
|
||||
vulkan_fidelityfx_fsr.vert
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#version 460 core
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
ivec4 clear_color;
|
||||
};
|
||||
|
||||
layout(location = 0) out ivec4 color;
|
||||
|
||||
void main() {
|
||||
color = clear_color;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#version 460 core
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
uvec4 clear_color;
|
||||
};
|
||||
|
||||
layout(location = 0) out uvec4 color;
|
||||
|
||||
void main() {
|
||||
color = clear_color;
|
||||
}
|
||||
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
#include "yuzu_video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
|
||||
#include "yuzu_common/settings.h"
|
||||
#include "video_settings.h"
|
||||
#include "yuzu_common/settings.h"
|
||||
#include "yuzu_video_core/host_shaders/blit_color_float_frag_spv.h"
|
||||
#include "yuzu_video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
|
||||
#include "yuzu_video_core/host_shaders/convert_abgr8_to_d32f_frag_spv.h"
|
||||
|
|
@ -18,6 +18,8 @@
|
|||
#include "yuzu_video_core/host_shaders/full_screen_triangle_vert_spv.h"
|
||||
#include "yuzu_video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
|
||||
#include "yuzu_video_core/host_shaders/vulkan_color_clear_frag_spv.h"
|
||||
#include "yuzu_video_core/host_shaders/vulkan_color_clear_sint_frag_spv.h"
|
||||
#include "yuzu_video_core/host_shaders/vulkan_color_clear_uint_frag_spv.h"
|
||||
#include "yuzu_video_core/host_shaders/vulkan_color_clear_vert_spv.h"
|
||||
#include "yuzu_video_core/host_shaders/vulkan_depthstencil_clear_frag_spv.h"
|
||||
#include "yuzu_video_core/renderer_vulkan/blit_image.h"
|
||||
|
|
@ -361,36 +363,33 @@ VkExtent2D GetConversionExtent(const ImageView& src_image_view) {
|
|||
};
|
||||
}
|
||||
|
||||
void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout target_layout,
|
||||
VkImageLayout source_layout = VK_IMAGE_LAYOUT_GENERAL) {
|
||||
constexpr VkFlags flags{VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT};
|
||||
void SynchronizeBlitSource(vk::CommandBuffer& cmdbuf, VkImage image) {
|
||||
// Cached images and sampled descriptors use GENERAL. Make prior writes visible
|
||||
// across the source image, including views of nonzero mip levels or layers.
|
||||
const VkImageMemoryBarrier barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = flags,
|
||||
.dstAccessMask = flags,
|
||||
.oldLayout = source_layout,
|
||||
.newLayout = target_layout,
|
||||
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
.subresourceRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
},
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
0, barrier);
|
||||
}
|
||||
|
||||
void BeginRenderPass(vk::CommandBuffer& cmdbuf, const Framebuffer* framebuffer) {
|
||||
const VkRenderPass render_pass = framebuffer->RenderPass();
|
||||
const VkFramebuffer framebuffer_handle = framebuffer->Handle();
|
||||
const VkExtent2D render_area = framebuffer->RenderArea();
|
||||
void BeginRenderPass(vk::CommandBuffer& cmdbuf, VkRenderPass render_pass,
|
||||
VkFramebuffer framebuffer_handle, VkExtent2D render_area) {
|
||||
const VkRenderPassBeginInfo renderpass_bi{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
.pNext = nullptr,
|
||||
|
|
@ -450,14 +449,19 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView
|
|||
Tegra::Engines::Fermi2D::Filter filter,
|
||||
Tegra::Engines::Fermi2D::Operation operation) {
|
||||
const bool is_linear = filter == Tegra::Engines::Fermi2D::Filter::Bilinear;
|
||||
const u32 color_scratch_mask = dst_framebuffer->ColorScratchMask(1U);
|
||||
const bool is_moltenvk = device.GetDriverID() == VK_DRIVER_ID_MOLTENVK;
|
||||
const BlitImagePipelineKey key{
|
||||
.renderpass = dst_framebuffer->RenderPass(),
|
||||
.renderpass = dst_framebuffer->RenderPassVariant(color_scratch_mask),
|
||||
.operation = operation,
|
||||
.samples = is_moltenvk ? dst_framebuffer->Samples() : VK_SAMPLE_COUNT_1_BIT,
|
||||
.color_attachment_count =
|
||||
static_cast<u8>(is_moltenvk ? dst_framebuffer->NumColorAttachments() : 1),
|
||||
};
|
||||
const VkPipelineLayout layout = *one_texture_pipeline_layout;
|
||||
const VkSampler sampler = is_linear ? *linear_sampler : *nearest_sampler;
|
||||
const VkPipeline pipeline = FindOrEmplaceColorPipeline(key);
|
||||
scheduler.RequestRenderpass(dst_framebuffer);
|
||||
scheduler.RequestRenderpass(dst_framebuffer, color_scratch_mask);
|
||||
scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler,
|
||||
src_view](vk::CommandBuffer cmdbuf) {
|
||||
// TODO: Barriers
|
||||
|
|
@ -476,17 +480,26 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView
|
|||
VkImage src_image, VkSampler src_sampler,
|
||||
const Region2D& dst_region, const Region2D& src_region,
|
||||
const Extent3D& src_size) {
|
||||
const u32 color_scratch_mask = dst_framebuffer->ColorScratchMask(1U);
|
||||
const bool is_moltenvk = device.GetDriverID() == VK_DRIVER_ID_MOLTENVK;
|
||||
const BlitImagePipelineKey key{
|
||||
.renderpass = dst_framebuffer->RenderPass(),
|
||||
.renderpass = dst_framebuffer->RenderPassVariant(color_scratch_mask),
|
||||
.operation = Tegra::Engines::Fermi2D::Operation::SrcCopy,
|
||||
.samples = is_moltenvk ? dst_framebuffer->Samples() : VK_SAMPLE_COUNT_1_BIT,
|
||||
.color_attachment_count =
|
||||
static_cast<u8>(is_moltenvk ? dst_framebuffer->NumColorAttachments() : 1),
|
||||
};
|
||||
const VkPipelineLayout layout = *one_texture_pipeline_layout;
|
||||
const VkPipeline pipeline = FindOrEmplaceColorPipeline(key);
|
||||
const VkRenderPass render_pass = key.renderpass;
|
||||
const VkFramebuffer framebuffer_handle = dst_framebuffer->HandleVariant(color_scratch_mask);
|
||||
const VkExtent2D render_area = dst_framebuffer->RenderArea();
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, dst_framebuffer, src_image_view, src_image, src_sampler, dst_region,
|
||||
src_region, src_size, pipeline, layout](vk::CommandBuffer cmdbuf) {
|
||||
TransitionImageLayout(cmdbuf, src_image, VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL);
|
||||
BeginRenderPass(cmdbuf, dst_framebuffer);
|
||||
scheduler.Record([this, render_pass, framebuffer_handle, render_area, src_image_view, src_image,
|
||||
src_sampler, dst_region, src_region, src_size, pipeline,
|
||||
layout](vk::CommandBuffer cmdbuf) {
|
||||
SynchronizeBlitSource(cmdbuf, src_image);
|
||||
BeginRenderPass(cmdbuf, render_pass, framebuffer_handle, render_area);
|
||||
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
|
||||
UpdateOneTextureDescriptorSet(device, descriptor_set, src_sampler, src_image_view);
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
|
|
@ -496,6 +509,7 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView
|
|||
cmdbuf.Draw(3, 1, 0, 0);
|
||||
cmdbuf.EndRenderPass();
|
||||
});
|
||||
scheduler.InvalidateState();
|
||||
}
|
||||
|
||||
void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer,
|
||||
|
|
@ -614,6 +628,33 @@ void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_ma
|
|||
scheduler.InvalidateState();
|
||||
}
|
||||
|
||||
void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask,
|
||||
const VkClearColorValue& clear_color,
|
||||
VideoCore::Surface::PixelFormat format,
|
||||
const Region2D& dst_region) {
|
||||
const bool integer_format = VideoCore::Surface::IsPixelFormatInteger(format);
|
||||
const u8 color_type =
|
||||
integer_format ? (VideoCore::Surface::IsPixelFormatSignedInteger(format) ? 1 : 2) : 0;
|
||||
const BlitImagePipelineKey key{
|
||||
.renderpass = dst_framebuffer->RenderPass(),
|
||||
.operation = Tegra::Engines::Fermi2D::Operation::BlendPremult,
|
||||
.samples = dst_framebuffer->Samples(),
|
||||
.clear_color_mask = color_mask,
|
||||
.clear_color_type = color_type,
|
||||
.masked_clear = true,
|
||||
};
|
||||
const VkPipeline pipeline = FindOrEmplaceClearColorPipeline(key);
|
||||
const VkPipelineLayout layout = *clear_color_pipeline_layout;
|
||||
scheduler.RequestRenderpass(dst_framebuffer);
|
||||
scheduler.Record([pipeline, layout, clear_color, dst_region](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
BindBlitState(cmdbuf, dst_region);
|
||||
cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_color);
|
||||
cmdbuf.Draw(3, 1, 0, 0);
|
||||
});
|
||||
scheduler.InvalidateState();
|
||||
}
|
||||
|
||||
void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear,
|
||||
f32 clear_depth, u8 stencil_mask, u32 stencil_ref,
|
||||
u32 stencil_compare_mask, const Region2D& dst_region) {
|
||||
|
|
@ -623,6 +664,8 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
|
|||
.stencil_mask = stencil_mask,
|
||||
.stencil_compare_mask = stencil_compare_mask,
|
||||
.stencil_ref = stencil_ref,
|
||||
.samples = device.GetDriverID() == VK_DRIVER_ID_MOLTENVK ? dst_framebuffer->Samples()
|
||||
: VK_SAMPLE_COUNT_1_BIT,
|
||||
};
|
||||
const VkPipeline pipeline = FindOrEmplaceClearStencilPipeline(key);
|
||||
const VkPipelineLayout layout = *clear_color_pipeline_layout;
|
||||
|
|
@ -747,6 +790,14 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe
|
|||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
|
||||
};
|
||||
std::array<VkPipelineColorBlendAttachmentState, NUM_RT> blend_attachments{};
|
||||
blend_attachments[0] = blend_attachment;
|
||||
auto multisample = PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||
multisample.rasterizationSamples = key.samples;
|
||||
auto depth_stencil = PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
||||
depth_stencil.depthTestEnable = VK_FALSE;
|
||||
depth_stencil.depthWriteEnable = VK_FALSE;
|
||||
// Only location 0 is written. Keep the remaining subpass slots explicitly disabled.
|
||||
// TODO: programmable blending
|
||||
const VkPipelineColorBlendStateCreateInfo color_blend_create_info{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||
|
|
@ -754,8 +805,8 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe
|
|||
.flags = 0,
|
||||
.logicOpEnable = VK_FALSE,
|
||||
.logicOp = VK_LOGIC_OP_CLEAR,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = &blend_attachment,
|
||||
.attachmentCount = key.color_attachment_count,
|
||||
.pAttachments = blend_attachments.data(),
|
||||
.blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
|
||||
};
|
||||
blit_color_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
|
||||
|
|
@ -769,8 +820,9 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe
|
|||
.pTessellationState = nullptr,
|
||||
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||
.pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.pDepthStencilState = nullptr,
|
||||
.pMultisampleState = &multisample,
|
||||
.pDepthStencilState =
|
||||
device.GetDriverID() == VK_DRIVER_ID_MOLTENVK ? &depth_stencil : nullptr,
|
||||
.pColorBlendState = &color_blend_create_info,
|
||||
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
.layout = *one_texture_pipeline_layout,
|
||||
|
|
@ -819,17 +871,30 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel
|
|||
return *clear_color_pipelines[std::distance(clear_color_keys.begin(), it)];
|
||||
}
|
||||
clear_color_keys.push_back(key);
|
||||
const std::array stages = MakeStages(*clear_color_vert, *clear_color_frag);
|
||||
vk::ShaderModule* fragment = &clear_color_frag;
|
||||
if (key.clear_color_type == 1) {
|
||||
if (!clear_color_sint_frag) {
|
||||
clear_color_sint_frag = BuildShader(device, VULKAN_COLOR_CLEAR_SINT_FRAG_SPV);
|
||||
}
|
||||
fragment = &clear_color_sint_frag;
|
||||
} else if (key.clear_color_type == 2) {
|
||||
if (!clear_color_uint_frag) {
|
||||
clear_color_uint_frag = BuildShader(device, VULKAN_COLOR_CLEAR_UINT_FRAG_SPV);
|
||||
}
|
||||
fragment = &clear_color_uint_frag;
|
||||
}
|
||||
const std::array stages = MakeStages(*clear_color_vert, **fragment);
|
||||
auto multisample = PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||
multisample.rasterizationSamples = key.samples;
|
||||
const VkPipelineColorBlendAttachmentState color_blend_attachment_state{
|
||||
.blendEnable = VK_TRUE,
|
||||
.blendEnable = !key.masked_clear,
|
||||
.srcColorBlendFactor = VK_BLEND_FACTOR_CONSTANT_COLOR,
|
||||
.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR,
|
||||
.colorBlendOp = VK_BLEND_OP_ADD,
|
||||
.srcAlphaBlendFactor = VK_BLEND_FACTOR_CONSTANT_ALPHA,
|
||||
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA,
|
||||
.alphaBlendOp = VK_BLEND_OP_ADD,
|
||||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
|
||||
.colorWriteMask = key.clear_color_mask,
|
||||
};
|
||||
const VkPipelineColorBlendStateCreateInfo color_blend_state_generic_create_info{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||
|
|
@ -852,7 +917,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel
|
|||
.pTessellationState = nullptr,
|
||||
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||
.pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.pMultisampleState = &multisample,
|
||||
.pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||
.pColorBlendState = &color_blend_state_generic_create_info,
|
||||
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
|
|
@ -873,6 +938,8 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
|
|||
}
|
||||
clear_stencil_keys.push_back(key);
|
||||
const std::array stages = MakeStages(*clear_color_vert, *clear_stencil_frag);
|
||||
auto multisample = PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||
multisample.rasterizationSamples = key.samples;
|
||||
const auto stencil = VkStencilOpState{
|
||||
.failOp = VK_STENCIL_OP_KEEP,
|
||||
.passOp = VK_STENCIL_OP_REPLACE,
|
||||
|
|
@ -907,7 +974,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
|
|||
.pTessellationState = nullptr,
|
||||
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||
.pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.pMultisampleState = &multisample,
|
||||
.pDepthStencilState = &depth_stencil_ci,
|
||||
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO,
|
||||
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "yuzu_video_core/engines/fermi_2d.h"
|
||||
#include "yuzu_video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "yuzu_video_core/surface.h"
|
||||
#include "yuzu_video_core/texture_cache/types.h"
|
||||
#include "yuzu_video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
|
|
@ -25,6 +26,12 @@ struct BlitImagePipelineKey {
|
|||
|
||||
VkRenderPass renderpass;
|
||||
Tegra::Engines::Fermi2D::Operation operation;
|
||||
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
|
||||
u8 color_attachment_count{1};
|
||||
u8 clear_color_mask{0xf};
|
||||
// 0: float, 1: signed integer, 2: unsigned integer.
|
||||
u8 clear_color_type{};
|
||||
bool masked_clear{};
|
||||
};
|
||||
|
||||
struct BlitDepthStencilPipelineKey {
|
||||
|
|
@ -35,6 +42,7 @@ struct BlitDepthStencilPipelineKey {
|
|||
u8 stencil_mask;
|
||||
u32 stencil_compare_mask;
|
||||
u32 stencil_ref;
|
||||
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
|
||||
};
|
||||
|
||||
class BlitImageHelper {
|
||||
|
|
@ -78,6 +86,10 @@ public:
|
|||
void ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask,
|
||||
const std::array<f32, 4>& clear_color, const Region2D& dst_region);
|
||||
|
||||
void ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask,
|
||||
const VkClearColorValue& clear_color, VideoCore::Surface::PixelFormat format,
|
||||
const Region2D& dst_region);
|
||||
|
||||
void ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, f32 clear_depth,
|
||||
u8 stencil_mask, u32 stencil_ref, u32 stencil_compare_mask,
|
||||
const Region2D& dst_region);
|
||||
|
|
@ -128,6 +140,8 @@ private:
|
|||
vk::ShaderModule blit_depth_stencil_frag;
|
||||
vk::ShaderModule clear_color_vert;
|
||||
vk::ShaderModule clear_color_frag;
|
||||
vk::ShaderModule clear_color_sint_frag;
|
||||
vk::ShaderModule clear_color_uint_frag;
|
||||
vk::ShaderModule clear_stencil_frag;
|
||||
vk::ShaderModule convert_depth_to_float_frag;
|
||||
vk::ShaderModule convert_float_to_depth_frag;
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
|||
msaa_mode.Assign(regs.anti_alias_samples_mode);
|
||||
|
||||
raw2 = 0;
|
||||
vertex_stride_workaround.Assign(features.has_vertex_stride_workaround &&
|
||||
!features.has_dynamic_vertex_input ? 1 : 0);
|
||||
|
||||
const auto test_func =
|
||||
regs.alpha_test_enabled != 0 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always_GL;
|
||||
|
|
@ -86,8 +88,16 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
|||
alpha_to_one_enabled.Assign(regs.anti_alias_alpha_control.alpha_to_one != 0 ? 1 : 0);
|
||||
app_stage.Assign(maxwell3d.engine_state);
|
||||
|
||||
bool color_formats_changed = false;
|
||||
for (size_t i = 0; i < regs.rt.size(); ++i) {
|
||||
color_formats[i] = static_cast<u8>(regs.rt[i].format);
|
||||
const u8 format = static_cast<u8>(regs.rt[i].format);
|
||||
color_formats_changed |= color_formats[i] != format;
|
||||
color_formats[i] = format;
|
||||
}
|
||||
if (extended_dynamic_state_3_blend && color_formats_changed) {
|
||||
// The dynamic blend enable depends on the render target formats.
|
||||
maxwell3d.dirty.flags[Dirty::Blending] = true;
|
||||
maxwell3d.dirty.flags[Dirty::BlendEnable] = true;
|
||||
}
|
||||
alpha_test_ref = Common::BitCast<u32>(regs.alpha_test_ref);
|
||||
point_size = Common::BitCast<u32>(regs.point_size);
|
||||
|
|
@ -146,6 +156,12 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
|||
std::ranges::transform(regs.vertex_streams, vertex_strides.begin(), [](const auto& array) {
|
||||
return static_cast<u16>(array.stride.Value());
|
||||
});
|
||||
} else if (vertex_stride_workaround) {
|
||||
// Strides stay dynamic, but they select the vertex attribute formats. Disabled streams
|
||||
// are zeroed so their stale strides do not create redundant pipelines.
|
||||
std::ranges::transform(regs.vertex_streams, vertex_strides.begin(), [](const auto& array) {
|
||||
return static_cast<u16>(array.enable != 0 ? array.stride.Value() : 0);
|
||||
});
|
||||
}
|
||||
if (!extended_dynamic_state_2_extra) {
|
||||
dynamic_state.Refresh2(regs, topology_, extended_dynamic_state_2);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ struct DynamicFeatures {
|
|||
bool has_extended_dynamic_state_3_blend;
|
||||
bool has_extended_dynamic_state_3_enables;
|
||||
bool has_dynamic_vertex_input;
|
||||
bool has_vertex_stride_workaround;
|
||||
};
|
||||
|
||||
struct FixedPipelineState {
|
||||
|
|
@ -213,6 +214,7 @@ struct FixedPipelineState {
|
|||
BitField<15, 1, u32> alpha_to_coverage_enabled;
|
||||
BitField<16, 1, u32> alpha_to_one_enabled;
|
||||
BitField<17, 3, Tegra::Engines::Maxwell3D::EngineHint> app_stage;
|
||||
BitField<20, 1, u32> vertex_stride_workaround;
|
||||
};
|
||||
std::array<u8, Maxwell::NumRenderTargets> color_formats;
|
||||
|
||||
|
|
@ -256,7 +258,7 @@ struct FixedPipelineState {
|
|||
// Exclude dynamic state
|
||||
return offsetof(FixedPipelineState, attributes);
|
||||
}
|
||||
if (extended_dynamic_state) {
|
||||
if (extended_dynamic_state && !vertex_stride_workaround) {
|
||||
// Exclude dynamic state
|
||||
return offsetof(FixedPipelineState, vertex_strides);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -367,6 +367,125 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device,
|
|||
return {};
|
||||
}
|
||||
|
||||
u32 VertexFormatSize(VkFormat format) noexcept {
|
||||
switch (format) {
|
||||
case VK_FORMAT_R8_UNORM:
|
||||
case VK_FORMAT_R8_SNORM:
|
||||
case VK_FORMAT_R8_UINT:
|
||||
case VK_FORMAT_R8_SINT:
|
||||
case VK_FORMAT_R8_USCALED:
|
||||
case VK_FORMAT_R8_SSCALED:
|
||||
return 1;
|
||||
case VK_FORMAT_R8G8_UNORM:
|
||||
case VK_FORMAT_R8G8_SNORM:
|
||||
case VK_FORMAT_R8G8_UINT:
|
||||
case VK_FORMAT_R8G8_SINT:
|
||||
case VK_FORMAT_R8G8_USCALED:
|
||||
case VK_FORMAT_R8G8_SSCALED:
|
||||
case VK_FORMAT_R16_UNORM:
|
||||
case VK_FORMAT_R16_SNORM:
|
||||
case VK_FORMAT_R16_UINT:
|
||||
case VK_FORMAT_R16_SINT:
|
||||
case VK_FORMAT_R16_USCALED:
|
||||
case VK_FORMAT_R16_SSCALED:
|
||||
case VK_FORMAT_R16_SFLOAT:
|
||||
return 2;
|
||||
case VK_FORMAT_R8G8B8_UNORM:
|
||||
case VK_FORMAT_R8G8B8_SNORM:
|
||||
case VK_FORMAT_R8G8B8_UINT:
|
||||
case VK_FORMAT_R8G8B8_SINT:
|
||||
case VK_FORMAT_R8G8B8_USCALED:
|
||||
case VK_FORMAT_R8G8B8_SSCALED:
|
||||
return 3;
|
||||
case VK_FORMAT_R8G8B8A8_UNORM:
|
||||
case VK_FORMAT_R8G8B8A8_SNORM:
|
||||
case VK_FORMAT_R8G8B8A8_UINT:
|
||||
case VK_FORMAT_R8G8B8A8_SINT:
|
||||
case VK_FORMAT_R8G8B8A8_USCALED:
|
||||
case VK_FORMAT_R8G8B8A8_SSCALED:
|
||||
case VK_FORMAT_R16G16_UNORM:
|
||||
case VK_FORMAT_R16G16_SNORM:
|
||||
case VK_FORMAT_R16G16_UINT:
|
||||
case VK_FORMAT_R16G16_SINT:
|
||||
case VK_FORMAT_R16G16_USCALED:
|
||||
case VK_FORMAT_R16G16_SSCALED:
|
||||
case VK_FORMAT_R16G16_SFLOAT:
|
||||
case VK_FORMAT_R32_UINT:
|
||||
case VK_FORMAT_R32_SINT:
|
||||
case VK_FORMAT_R32_SFLOAT:
|
||||
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_SNORM_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_UINT_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_SINT_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_USCALED_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_SSCALED_PACK32:
|
||||
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
|
||||
return 4;
|
||||
case VK_FORMAT_R16G16B16_UNORM:
|
||||
case VK_FORMAT_R16G16B16_SNORM:
|
||||
case VK_FORMAT_R16G16B16_UINT:
|
||||
case VK_FORMAT_R16G16B16_SINT:
|
||||
case VK_FORMAT_R16G16B16_USCALED:
|
||||
case VK_FORMAT_R16G16B16_SSCALED:
|
||||
case VK_FORMAT_R16G16B16_SFLOAT:
|
||||
return 6;
|
||||
case VK_FORMAT_R16G16B16A16_UNORM:
|
||||
case VK_FORMAT_R16G16B16A16_SNORM:
|
||||
case VK_FORMAT_R16G16B16A16_UINT:
|
||||
case VK_FORMAT_R16G16B16A16_SINT:
|
||||
case VK_FORMAT_R16G16B16A16_USCALED:
|
||||
case VK_FORMAT_R16G16B16A16_SSCALED:
|
||||
case VK_FORMAT_R16G16B16A16_SFLOAT:
|
||||
case VK_FORMAT_R32G32_UINT:
|
||||
case VK_FORMAT_R32G32_SINT:
|
||||
case VK_FORMAT_R32G32_SFLOAT:
|
||||
return 8;
|
||||
case VK_FORMAT_R32G32B32_UINT:
|
||||
case VK_FORMAT_R32G32B32_SINT:
|
||||
case VK_FORMAT_R32G32B32_SFLOAT:
|
||||
return 12;
|
||||
case VK_FORMAT_R32G32B32A32_UINT:
|
||||
case VK_FORMAT_R32G32B32A32_SINT:
|
||||
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
||||
return 16;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr Maxwell::VertexAttribute::Size DropLastVertexComponent(
|
||||
Maxwell::VertexAttribute::Size size) noexcept {
|
||||
using Size = Maxwell::VertexAttribute::Size;
|
||||
switch (size) {
|
||||
case Size::Size_R32_G32_B32_A32:
|
||||
return Size::Size_R32_G32_B32;
|
||||
case Size::Size_R32_G32_B32:
|
||||
return Size::Size_R32_G32;
|
||||
case Size::Size_R32_G32:
|
||||
return Size::Size_R32;
|
||||
case Size::Size_R16_G16_B16_A16:
|
||||
return Size::Size_R16_G16_B16;
|
||||
case Size::Size_R16_G16_B16:
|
||||
return Size::Size_R16_G16;
|
||||
case Size::Size_R16_G16:
|
||||
return Size::Size_R16;
|
||||
case Size::Size_R8_G8_B8_A8:
|
||||
case Size::Size_X8_B8_G8_R8:
|
||||
return Size::Size_R8_G8_B8;
|
||||
case Size::Size_R8_G8_B8:
|
||||
return Size::Size_R8_G8;
|
||||
case Size::Size_R8_G8:
|
||||
case Size::Size_G8_R8:
|
||||
return Size::Size_R8;
|
||||
default:
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
|
||||
Maxwell::VertexAttribute::Size size) {
|
||||
if (device.MustEmulateScaledFormats()) {
|
||||
|
|
@ -596,6 +715,46 @@ VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
|
|||
FormatType::Buffer);
|
||||
}
|
||||
|
||||
VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
|
||||
Maxwell::VertexAttribute::Size size, u32 offset, u32 stride,
|
||||
u32 used_components) {
|
||||
// Attributes reading past their binding stride are invalid on portability hosts and are
|
||||
// fetched incorrectly by Metal. Drop trailing components the shader never reads until the
|
||||
// format fits; keep the original format when that is impossible.
|
||||
const VkFormat original = VertexFormat(device, type, size);
|
||||
if (stride == 0 || offset >= stride) {
|
||||
return original;
|
||||
}
|
||||
const u32 original_size = VertexFormatSize(original);
|
||||
if (original_size == 0 || original_size <= stride - offset) {
|
||||
return original;
|
||||
}
|
||||
|
||||
auto reduced_size = size;
|
||||
while (true) {
|
||||
const auto next_size = DropLastVertexComponent(reduced_size);
|
||||
if (next_size == reduced_size) {
|
||||
return original;
|
||||
}
|
||||
Maxwell::VertexAttribute reduced_attribute{};
|
||||
reduced_attribute.size.Assign(next_size);
|
||||
if (reduced_attribute.ComponentCount() < used_components) {
|
||||
// A valid host layout cannot be obtained by discarding guest data that is read.
|
||||
// Keep the original format; such layouts require a separate repacking path.
|
||||
return original;
|
||||
}
|
||||
reduced_size = next_size;
|
||||
|
||||
const VkFormat reduced = VertexFormat(device, type, reduced_size);
|
||||
const u32 host_size = VertexFormatSize(reduced);
|
||||
if (host_size != 0 && host_size <= stride - offset &&
|
||||
device.IsFormatSupported(reduced, VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT,
|
||||
FormatType::Buffer)) {
|
||||
return reduced;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison) {
|
||||
switch (comparison) {
|
||||
case Maxwell::ComparisonOp::Never_D3D:
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@ VkPrimitiveTopology PrimitiveTopology(const Device& device, Maxwell::PrimitiveTo
|
|||
VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
|
||||
Maxwell::VertexAttribute::Size size);
|
||||
|
||||
VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
|
||||
Maxwell::VertexAttribute::Size size, u32 offset, u32 stride,
|
||||
u32 used_components);
|
||||
|
||||
[[nodiscard]] u32 VertexFormatSize(VkFormat format) noexcept;
|
||||
|
||||
VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison);
|
||||
|
||||
VkIndexType IndexFormat(Maxwell::IndexFormat index_format);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS;
|
|||
|
||||
class DescriptorLayoutBuilder {
|
||||
public:
|
||||
DescriptorLayoutBuilder(const Device& device_) : device{&device_} {}
|
||||
DescriptorLayoutBuilder(const Device& device_, VkShaderStageFlags storage_buffer_stages_ = 0)
|
||||
: device{&device_}, storage_buffer_stages{storage_buffer_stages_} {}
|
||||
|
||||
bool CanUsePushDescriptor() const noexcept {
|
||||
return device->IsKhrPushDescriptorSupported() &&
|
||||
|
|
@ -102,6 +103,9 @@ public:
|
|||
private:
|
||||
template <typename Descriptors>
|
||||
void Add(VkDescriptorType type, VkShaderStageFlags stage, const Descriptors& descriptors) {
|
||||
if (type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER && storage_buffer_stages != 0) {
|
||||
stage = storage_buffer_stages;
|
||||
}
|
||||
const size_t num{descriptors.size()};
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
bindings.push_back({
|
||||
|
|
@ -126,6 +130,7 @@ private:
|
|||
}
|
||||
|
||||
const Device* device{};
|
||||
VkShaderStageFlags storage_buffer_stages{};
|
||||
bool is_compute{};
|
||||
boost::container::small_vector<VkDescriptorSetLayoutBinding, 32> bindings;
|
||||
boost::container::small_vector<VkDescriptorUpdateTemplateEntry, 32> entries;
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
|
|||
.pNext = nullptr,
|
||||
.requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U,
|
||||
};
|
||||
bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size;
|
||||
bool use_setup_size = device.CanRequestComputeSubgroupSize() && optional_subgroup_size;
|
||||
pipeline = device.GetLogical().CreateComputePipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
|
|||
.stage{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.pNext =
|
||||
device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr,
|
||||
device.CanRequestComputeSubgroupSize() ? &subgroup_size_ci : nullptr,
|
||||
.flags = 0,
|
||||
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.module = *spv_module,
|
||||
|
|
|
|||
|
|
@ -45,16 +45,60 @@ using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
|
|||
constexpr size_t NUM_STAGES = Maxwell::MaxShaderStage;
|
||||
constexpr size_t MAX_IMAGE_ELEMENTS = 64;
|
||||
|
||||
DescriptorLayoutBuilder MakeBuilder(const Device& device, std::span<const Shader::Info> infos) {
|
||||
DescriptorLayoutBuilder builder{device};
|
||||
DescriptorLayoutBuilder MakeBuilder(const Device& device, std::span<const Shader::Info> infos,
|
||||
u32 num_color_attachments) {
|
||||
static constexpr std::array stages{
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
|
||||
VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
|
||||
VK_SHADER_STAGE_GEOMETRY_BIT,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
};
|
||||
VkShaderStageFlags storage_buffer_stages{};
|
||||
if (device.GetDriverID() == VK_DRIVER_ID_MOLTENVK) {
|
||||
// MoltenVK can lose the resource usage of a storage buffer reused across shader stages
|
||||
// (KhronosGroup/MoltenVK#1870). Like Ryubing, make every storage buffer binding visible
|
||||
// to all graphics stages using descriptors, as long as the widened layout stays within
|
||||
// the per-stage limits.
|
||||
std::array<u32, NUM_STAGES> stage_resources{};
|
||||
std::array<u32, NUM_STAGES> stage_storage_buffers{};
|
||||
u32 num_storage_buffers{};
|
||||
for (size_t index = 0; index < infos.size(); ++index) {
|
||||
const auto& info = infos[index];
|
||||
const u32 storage_buffers = Shader::NumDescriptors(info.storage_buffers_descriptors);
|
||||
const u32 resources = Shader::NumDescriptors(info.constant_buffer_descriptors) +
|
||||
storage_buffers +
|
||||
Shader::NumDescriptors(info.texture_buffer_descriptors) +
|
||||
Shader::NumDescriptors(info.image_buffer_descriptors) +
|
||||
Shader::NumDescriptors(info.texture_descriptors) +
|
||||
Shader::NumDescriptors(info.image_descriptors);
|
||||
stage_resources[index] = resources;
|
||||
stage_storage_buffers[index] = storage_buffers;
|
||||
num_storage_buffers += storage_buffers;
|
||||
if (resources != 0) {
|
||||
storage_buffer_stages |= stages.at(index);
|
||||
}
|
||||
}
|
||||
bool can_widen = num_storage_buffers != 0 &&
|
||||
num_storage_buffers <= device.GetMaxPerStageStorageBuffers();
|
||||
for (size_t index = 0; can_widen && index < infos.size(); ++index) {
|
||||
if ((storage_buffer_stages & stages.at(index)) == 0) {
|
||||
continue;
|
||||
}
|
||||
u32 resources =
|
||||
stage_resources[index] - stage_storage_buffers[index] + num_storage_buffers;
|
||||
if (stages.at(index) == VK_SHADER_STAGE_FRAGMENT_BIT) {
|
||||
// Color attachments also count against maxPerStageResources.
|
||||
resources += num_color_attachments;
|
||||
}
|
||||
can_widen = resources <= device.GetMaxPerStageResources();
|
||||
}
|
||||
if (!can_widen) {
|
||||
storage_buffer_stages = 0;
|
||||
}
|
||||
}
|
||||
DescriptorLayoutBuilder builder{device, storage_buffer_stages};
|
||||
for (size_t index = 0; index < infos.size(); ++index) {
|
||||
static constexpr std::array stages{
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
|
||||
VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
|
||||
VK_SHADER_STAGE_GEOMETRY_BIT,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
};
|
||||
builder.Add(infos[index], stages.at(index));
|
||||
}
|
||||
return builder;
|
||||
|
|
@ -259,8 +303,27 @@ GraphicsPipeline::GraphicsPipeline(
|
|||
std::ranges::copy(info->constant_buffer_used_sizes, uniform_buffer_sizes[stage].begin());
|
||||
num_textures += Shader::NumDescriptors(info->texture_descriptors);
|
||||
}
|
||||
// Number of leading components the vertex shader may read from each attribute. Used to keep
|
||||
// vertex formats within their binding stride without discarding data the shader reads.
|
||||
const Shader::Info& vertex_info{stage_infos[0]};
|
||||
for (size_t index = 0; index < vertex_attribute_components.size(); ++index) {
|
||||
if (!vertex_info.loads.Generic(index)) {
|
||||
continue;
|
||||
}
|
||||
if (vertex_info.loads_indexed_attributes) {
|
||||
vertex_attribute_components[index] = 4;
|
||||
continue;
|
||||
}
|
||||
for (u8 component = 4; component != 0; --component) {
|
||||
if (vertex_info.loads.Generic(index, component - 1)) {
|
||||
vertex_attribute_components[index] = component;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool, pipeline_statistics] {
|
||||
DescriptorLayoutBuilder builder{MakeBuilder(device, stage_infos)};
|
||||
DescriptorLayoutBuilder builder{MakeBuilder(
|
||||
device, stage_infos, static_cast<u32>(NumAttachments(key.state)))};
|
||||
uses_push_descriptor = builder.CanUsePushDescriptor();
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor);
|
||||
if (!uses_push_descriptor) {
|
||||
|
|
@ -491,7 +554,27 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
|||
|
||||
void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
const RenderAreaPushConstant& render_area) {
|
||||
scheduler.RequestRenderpass(texture_cache.GetFramebuffer());
|
||||
const Framebuffer* const framebuffer = texture_cache.GetFramebuffer();
|
||||
u32 color_scratch_mask{};
|
||||
if (device.GetDriverID() == VK_DRIVER_ID_MOLTENVK) {
|
||||
const u32 duplicate_mask = framebuffer->DuplicateColorAttachmentMask();
|
||||
if (duplicate_mask != 0) {
|
||||
u32 color_write_mask{};
|
||||
const auto& regs = maxwell3d->regs;
|
||||
const Shader::Info& fragment_info = stage_infos[4];
|
||||
for (size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index];
|
||||
if ((mask.R || mask.G || mask.B || mask.A) &&
|
||||
fragment_info.stores_frag_color[index]) {
|
||||
color_write_mask |= 1U << index;
|
||||
}
|
||||
}
|
||||
// Avoid independent Metal tile copies of
|
||||
// the same image. Keep the render-pass shape, using scratch for masked slots.
|
||||
color_scratch_mask = framebuffer->ColorScratchMask(color_write_mask);
|
||||
}
|
||||
}
|
||||
scheduler.RequestRenderpass(framebuffer, color_scratch_mask);
|
||||
|
||||
if (!is_built.load(std::memory_order::relaxed)) {
|
||||
// Wait for the pipeline to be built
|
||||
|
|
@ -540,6 +623,20 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
|
|||
descriptor_set, nullptr);
|
||||
}
|
||||
});
|
||||
if (bind_pipeline && device.GetDriverID() == VK_DRIVER_ID_MOLTENVK &&
|
||||
key.state.extended_dynamic_state_3_blend != 0) {
|
||||
std::array<VkBool32, Maxwell::NumRenderTargets> setup_enables{};
|
||||
const auto& regs = maxwell3d->regs;
|
||||
for (size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
const bool integer_format = VideoCore::Surface::IsPixelFormatInteger(
|
||||
DecodeFormat(key.state.color_formats[index]));
|
||||
setup_enables[index] =
|
||||
regs.blend.enable[index] != 0 && !integer_format ? VK_TRUE : VK_FALSE;
|
||||
}
|
||||
scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetColorBlendEnableEXT(0, setup_enables);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
|
|
@ -576,10 +673,17 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
|||
if (!attribute.enabled || !stage_infos[0].loads.Generic(index)) {
|
||||
continue;
|
||||
}
|
||||
const VkFormat format =
|
||||
key.state.vertex_stride_workaround != 0 && attribute.buffer < num_vertex_arrays
|
||||
? MaxwellToVK::VertexFormat(device, attribute.Type(), attribute.Size(),
|
||||
attribute.offset,
|
||||
key.state.vertex_strides[attribute.buffer],
|
||||
vertex_attribute_components[index])
|
||||
: MaxwellToVK::VertexFormat(device, attribute.Type(), attribute.Size());
|
||||
vertex_attributes.push_back({
|
||||
.location = static_cast<u32>(index),
|
||||
.binding = attribute.buffer,
|
||||
.format = MaxwellToVK::VertexFormat(device, attribute.Type(), attribute.Size()),
|
||||
.format = format,
|
||||
.offset = attribute.offset,
|
||||
});
|
||||
}
|
||||
|
|
@ -771,8 +875,11 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
|||
for (size_t i = 0; i < mask_table.size(); ++i) {
|
||||
write_mask |= mask[i] ? mask_table[i] : 0;
|
||||
}
|
||||
// Blending is not defined for integer formats.
|
||||
const bool disable_integer_blend =
|
||||
VideoCore::Surface::IsPixelFormatInteger(DecodeFormat(key.state.color_formats[index]));
|
||||
cb_attachments.push_back({
|
||||
.blendEnable = blend.enable != 0,
|
||||
.blendEnable = blend.enable != 0 && !disable_integer_blend,
|
||||
.srcColorBlendFactor = MaxwellToVK::BlendFactor(blend.SourceRGBFactor()),
|
||||
.dstColorBlendFactor = MaxwellToVK::BlendFactor(blend.DestRGBFactor()),
|
||||
.colorBlendOp = MaxwellToVK::BlendEquation(blend.EquationRGB()),
|
||||
|
|
@ -842,6 +949,14 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
|||
dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end());
|
||||
}
|
||||
}
|
||||
if (!device.CanUseDepthBoundsDynamicState()) {
|
||||
dynamic_states.erase(
|
||||
std::remove_if(dynamic_states.begin(), dynamic_states.end(), [](VkDynamicState state) {
|
||||
return state == VK_DYNAMIC_STATE_DEPTH_BOUNDS ||
|
||||
state == VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT;
|
||||
}),
|
||||
dynamic_states.end());
|
||||
}
|
||||
const VkPipelineDynamicStateCreateInfo dynamic_state_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ private:
|
|||
std::array<vk::ShaderModule, NUM_STAGES> spv_modules;
|
||||
|
||||
std::array<Shader::Info, NUM_STAGES> stage_infos;
|
||||
std::array<u8, Maxwell::NumVertexAttributes> vertex_attribute_components{};
|
||||
std::array<u32, 5> enabled_uniform_buffer_masks{};
|
||||
VideoCommon::UniformBufferSizes uniform_buffer_sizes{};
|
||||
u32 num_textures{};
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include "yuzu_video_core/shader_cache.h"
|
||||
#include "yuzu_video_core/shader_environment.h"
|
||||
#include "yuzu_video_core/shader_notify.h"
|
||||
#include "yuzu_video_core/surface.h"
|
||||
#include "yuzu_video_core/vulkan_common/vulkan_device.h"
|
||||
#include "yuzu_video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_settings.h"
|
||||
|
|
@ -225,6 +226,21 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
|||
info.alpha_test_func = MaxwellToCompareFunction(
|
||||
key.state.UnpackComparisonOp(key.state.alpha_test_func.Value()));
|
||||
info.alpha_test_reference = Common::BitCast<float>(key.state.alpha_test_ref);
|
||||
// Vulkan requires integer fragment outputs for integer color attachments.
|
||||
for (size_t index = 0; index < info.color_output_types.size(); ++index) {
|
||||
const auto format =
|
||||
static_cast<Tegra::RenderTargetFormat>(key.state.color_formats[index]);
|
||||
if (format == Tegra::RenderTargetFormat::NONE) {
|
||||
continue;
|
||||
}
|
||||
const auto pixel_format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(format);
|
||||
if (VideoCore::Surface::IsPixelFormatInteger(pixel_format)) {
|
||||
info.color_output_types[index] =
|
||||
VideoCore::Surface::IsPixelFormatSignedInteger(pixel_format)
|
||||
? Shader::AttributeType::SignedInt
|
||||
: Shader::AttributeType::UnsignedInt;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -317,6 +333,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
|||
.supported_spirv = device.SupportedSpirvVersion(),
|
||||
.unified_descriptor_binding = true,
|
||||
.support_descriptor_aliasing = device.IsDescriptorAliasingSupported(),
|
||||
.disable_uniform_buffer_descriptor_aliasing =
|
||||
device.IsDescriptorAliasingSupported() && device.GetDriverID() == VK_DRIVER_ID_MOLTENVK,
|
||||
.support_int8 = device.IsInt8Supported(),
|
||||
.support_int16 = device.IsShaderInt16Supported(),
|
||||
.support_int64 = device.IsShaderInt64Supported(),
|
||||
|
|
@ -406,6 +424,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
|||
.has_extended_dynamic_state_3_blend = device.IsExtExtendedDynamicState3BlendingSupported(),
|
||||
.has_extended_dynamic_state_3_enables = device.IsExtExtendedDynamicState3EnablesSupported(),
|
||||
.has_dynamic_vertex_input = device.IsExtVertexInputDynamicStateSupported(),
|
||||
.has_vertex_stride_workaround = device.NeedsVertexAttributeStrideWorkaround(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -516,7 +535,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.dynamic_vertex_input != 0) != dynamic_features.has_dynamic_vertex_input ||
|
||||
(key.state.vertex_stride_workaround != 0) !=
|
||||
(dynamic_features.has_vertex_stride_workaround &&
|
||||
!dynamic_features.has_dynamic_vertex_input)) {
|
||||
return;
|
||||
}
|
||||
workers.QueueWork([this, key, envs_ = std::move(envs), &state, &callback]() mutable {
|
||||
|
|
|
|||
|
|
@ -359,10 +359,10 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
|||
|
||||
std::scoped_lock lock{texture_cache.mutex};
|
||||
texture_cache.UpdateRenderTargets(true);
|
||||
const Framebuffer* const framebuffer = texture_cache.GetFramebuffer();
|
||||
const Framebuffer* framebuffer = texture_cache.GetFramebuffer();
|
||||
const VkExtent2D render_area = framebuffer->RenderArea();
|
||||
scheduler.RequestRenderpass(framebuffer);
|
||||
|
||||
const u32 color_write_mask = use_color ? (1U << regs.clear_surface.RT) : 0;
|
||||
const u32 color_scratch_mask = framebuffer->ColorScratchMask(color_write_mask);
|
||||
u32 up_scale = 1;
|
||||
u32 down_shift = 0;
|
||||
if (texture_cache.IsRescaling()) {
|
||||
|
|
@ -399,7 +399,9 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
|||
bool is_signed = IsPixelFormatSignedInteger(format);
|
||||
size_t int_size = PixelComponentSizeBitsInteger(format);
|
||||
VkClearValue clear_value{};
|
||||
if (!is_integer) {
|
||||
if (device.GetDriverID() == VK_DRIVER_ID_MOLTENVK || !is_integer) {
|
||||
// Clear registers contain raw float/int/uint bits selected by the target format.
|
||||
// Copying the words also preserves integer values whose float view is a NaN.
|
||||
std::memcpy(clear_value.color.float32, regs.clear_color.data(),
|
||||
regs.clear_color.size() * sizeof(f32));
|
||||
} else if (!is_signed) {
|
||||
|
|
@ -415,8 +417,13 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
|||
}
|
||||
}
|
||||
|
||||
// MoltenVK's native attachment clears can round large 32-bit integers.
|
||||
// The typed shader clear preserves their exact values, including full clears.
|
||||
const bool integer_shader_clear =
|
||||
device.GetDriverID() == VK_DRIVER_ID_MOLTENVK && is_integer;
|
||||
if (regs.clear_surface.R && regs.clear_surface.G && regs.clear_surface.B &&
|
||||
regs.clear_surface.A) {
|
||||
regs.clear_surface.A && !integer_shader_clear) {
|
||||
scheduler.RequestRenderpass(framebuffer, color_scratch_mask);
|
||||
scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) {
|
||||
const VkClearAttachment attachment{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
|
|
@ -434,7 +441,18 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
|||
static_cast<s32>(clear_rect.rect.extent.width),
|
||||
.y = clear_rect.rect.offset.y +
|
||||
static_cast<s32>(clear_rect.rect.extent.height)}};
|
||||
blit_image.ClearColor(framebuffer, color_mask, regs.clear_color, dst_region);
|
||||
if (device.GetDriverID() == VK_DRIVER_ID_MOLTENVK) {
|
||||
for (u32 layer = 0; layer < clear_rect.layerCount; ++layer) {
|
||||
const Framebuffer* clear_framebuffer = texture_cache.GetClearFramebuffer(
|
||||
color_attachment, clear_rect.baseArrayLayer + layer);
|
||||
blit_image.ClearColor(clear_framebuffer, color_mask, clear_value.color, format,
|
||||
dst_region);
|
||||
}
|
||||
// The framebuffer slot storage can grow while creating the helper framebuffer.
|
||||
framebuffer = texture_cache.GetFramebuffer();
|
||||
} else {
|
||||
blit_image.ClearColor(framebuffer, color_mask, regs.clear_color, dst_region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -459,10 +477,23 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
|||
Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width),
|
||||
.y = clear_rect.rect.offset.y +
|
||||
static_cast<s32>(clear_rect.rect.extent.height)}};
|
||||
blit_image.ClearDepthStencil(framebuffer, use_depth, regs.clear_depth,
|
||||
static_cast<u8>(regs.stencil_front_mask), regs.clear_stencil,
|
||||
regs.stencil_front_func_mask, dst_region);
|
||||
if (device.GetDriverID() == VK_DRIVER_ID_MOLTENVK) {
|
||||
for (u32 layer = 0; layer < clear_rect.layerCount; ++layer) {
|
||||
const Framebuffer* clear_framebuffer = texture_cache.GetClearFramebuffer(
|
||||
Maxwell::NumRenderTargets, clear_rect.baseArrayLayer + layer);
|
||||
blit_image.ClearDepthStencil(clear_framebuffer, use_depth, regs.clear_depth,
|
||||
static_cast<u8>(regs.stencil_front_mask),
|
||||
regs.clear_stencil, regs.stencil_front_func_mask,
|
||||
dst_region);
|
||||
}
|
||||
} else {
|
||||
blit_image.ClearDepthStencil(
|
||||
framebuffer, use_depth, regs.clear_depth, static_cast<u8>(regs.stencil_front_mask),
|
||||
regs.clear_stencil, regs.stencil_front_func_mask, dst_region);
|
||||
}
|
||||
} else {
|
||||
// A masked color clear may have selected a color-only helper framebuffer.
|
||||
scheduler.RequestRenderpass(framebuffer, color_scratch_mask);
|
||||
scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil,
|
||||
clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) {
|
||||
VkClearAttachment attachment;
|
||||
|
|
@ -753,7 +784,10 @@ void RasterizerVulkan::FragmentBarrier() {
|
|||
}
|
||||
|
||||
void RasterizerVulkan::TiledCacheBarrier() {
|
||||
// TODO: Implementing tiled barriers requires rewriting a good chunk of the Vulkan backend
|
||||
// EndRenderPass publishes previous attachment writes before subsequent guest reads.
|
||||
if (device.GetDriverID() == VK_DRIVER_ID_MOLTENVK) {
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::FlushCommands() {
|
||||
|
|
@ -1111,7 +1145,7 @@ void RasterizerVulkan::UpdateBlendConstants(Tegra::Engines::Maxwell3D::Regs& reg
|
|||
}
|
||||
|
||||
void RasterizerVulkan::UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
if (!state_tracker.TouchDepthBounds()) {
|
||||
if (!state_tracker.TouchDepthBounds() || !device.CanUseDepthBoundsDynamicState()) {
|
||||
return;
|
||||
}
|
||||
scheduler.Record([min = regs.depth_bounds[0], max = regs.depth_bounds[1]](
|
||||
|
|
@ -1230,7 +1264,7 @@ void RasterizerVulkan::UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs) {
|
|||
}
|
||||
|
||||
void RasterizerVulkan::UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
if (!state_tracker.TouchDepthBoundsTestEnable()) {
|
||||
if (!state_tracker.TouchDepthBoundsTestEnable() || !device.CanUseDepthBoundsDynamicState()) {
|
||||
return;
|
||||
}
|
||||
bool enabled = regs.depth_bounds_enable;
|
||||
|
|
@ -1434,9 +1468,17 @@ 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 enable = regs.blend.enable[index] != 0;
|
||||
const auto rt_format = regs.rt[index].format;
|
||||
// Blending is not defined for integer formats.
|
||||
if (enable && rt_format != Tegra::RenderTargetFormat::NONE) {
|
||||
const auto format =
|
||||
VideoCore::Surface::PixelFormatFromRenderTargetFormat(rt_format);
|
||||
enable = !IsPixelFormatInteger(format);
|
||||
}
|
||||
setup_enables[index] = enable ? VK_TRUE : VK_FALSE;
|
||||
}
|
||||
scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetColorBlendEnableEXT(0, setup_enables);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,7 +52,13 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
|
|||
.layout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
};
|
||||
if (is_valid) {
|
||||
descriptions.push_back(AttachmentDescription(*device, format, key.samples));
|
||||
auto description = AttachmentDescription(*device, format, key.samples);
|
||||
if ((key.color_scratch_mask & (1U << index)) != 0) {
|
||||
description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
description.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
}
|
||||
descriptions.push_back(description);
|
||||
num_attachments = static_cast<u32>(index + 1);
|
||||
++num_colors;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ struct RenderPassKey {
|
|||
std::array<VideoCore::Surface::PixelFormat, 8> color_formats;
|
||||
VideoCore::Surface::PixelFormat depth_format;
|
||||
VkSampleCountFlagBits samples;
|
||||
u32 color_scratch_mask{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
@ -30,6 +31,7 @@ struct hash<Vulkan::RenderPassKey> {
|
|||
for (size_t i = 0; i < key.color_formats.size(); ++i) {
|
||||
value ^= static_cast<size_t>(key.color_formats[i]) << (i * 6);
|
||||
}
|
||||
value ^= static_cast<size_t>(key.color_scratch_mask) << 56;
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -86,9 +86,10 @@ void Scheduler::DispatchWork() {
|
|||
AcquireNewChunk();
|
||||
}
|
||||
|
||||
void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) {
|
||||
const VkRenderPass renderpass = framebuffer->RenderPass();
|
||||
const VkFramebuffer framebuffer_handle = framebuffer->Handle();
|
||||
void Scheduler::RequestRenderpass(const Framebuffer* framebuffer, u32 color_scratch_mask) {
|
||||
color_scratch_mask &= framebuffer->DuplicateColorAttachmentMask();
|
||||
const VkRenderPass renderpass = framebuffer->RenderPassVariant(color_scratch_mask);
|
||||
const VkFramebuffer framebuffer_handle = framebuffer->HandleVariant(color_scratch_mask);
|
||||
const VkExtent2D render_area = framebuffer->RenderArea();
|
||||
if (renderpass == state.renderpass && framebuffer_handle == state.framebuffer &&
|
||||
render_area.width == state.render_area.width &&
|
||||
|
|
@ -117,8 +118,8 @@ void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) {
|
|||
cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
|
||||
});
|
||||
num_renderpass_images = framebuffer->NumImages();
|
||||
renderpass_images = framebuffer->Images();
|
||||
renderpass_image_ranges = framebuffer->ImageRanges();
|
||||
renderpass_images = framebuffer->Images(color_scratch_mask);
|
||||
renderpass_image_ranges = framebuffer->ImageRanges(color_scratch_mask);
|
||||
}
|
||||
|
||||
void Scheduler::RequestOutsideRenderPassOperationContext() {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public:
|
|||
void DispatchWork();
|
||||
|
||||
/// Requests to begin a renderpass.
|
||||
void RequestRenderpass(const Framebuffer* framebuffer);
|
||||
void RequestRenderpass(const Framebuffer* framebuffer, u32 color_scratch_mask = 0);
|
||||
|
||||
/// Requests the current execution context to be able to execute operations only allowed outside
|
||||
/// of a renderpass.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
|
@ -25,6 +26,7 @@
|
|||
#include "yuzu_video_core/texture_cache/samples_helper.h"
|
||||
#include "yuzu_video_core/texture_cache/util.h"
|
||||
#include "yuzu_video_core/vulkan_common/vulkan_device.h"
|
||||
#include "yuzu_video_core/vulkan_common/moltenvk_workarounds.h"
|
||||
#include "yuzu_video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "yuzu_video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
|
|
@ -1977,6 +1979,16 @@ Framebuffer::Framebuffer(TextureCacheRuntime& runtime, ImageView* color_buffer,
|
|||
|
||||
Framebuffer::~Framebuffer() = default;
|
||||
|
||||
Framebuffer& Framebuffer::operator=(Framebuffer&& other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
// Default member assignment would release scratch images before the old
|
||||
// framebuffers that reference them. Destroy the complete owner in order.
|
||||
std::destroy_at(this);
|
||||
return *std::construct_at(this, std::move(other));
|
||||
}
|
||||
|
||||
void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
||||
std::span<ImageView*, NUM_RT> color_buffers,
|
||||
ImageView* depth_buffer, bool is_rescaled_) {
|
||||
|
|
@ -1989,6 +2001,8 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
|||
|
||||
u32 width = std::numeric_limits<u32>::max();
|
||||
u32 height = std::numeric_limits<u32>::max();
|
||||
std::array<VkImage, NUM_RT> color_images{};
|
||||
std::array<VkImageSubresourceRange, NUM_RT> color_ranges{};
|
||||
for (size_t index = 0; index < NUM_RT; ++index) {
|
||||
const ImageView* const color_buffer = color_buffers[index];
|
||||
if (!color_buffer) {
|
||||
|
|
@ -2001,13 +2015,25 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
|||
: color_buffer->size.height);
|
||||
attachments.push_back(color_buffer->RenderTarget());
|
||||
renderpass_key.color_formats[index] = color_buffer->format;
|
||||
num_color_attachments = static_cast<u32>(index + 1);
|
||||
num_layers = std::max(num_layers, color_buffer->range.extent.layers);
|
||||
images[num_images] = color_buffer->ImageHandle();
|
||||
color_images[index] = images[num_images];
|
||||
// Use the selected view range here. Barrier ranges normalize 3D slices to layer 0.
|
||||
color_ranges[index] =
|
||||
MakeSubresourceRange(ImageAspectMask(color_buffer->format), color_buffer->range);
|
||||
image_ranges[num_images] = MakeSubresourceRange(color_buffer);
|
||||
rt_map[index] = num_images;
|
||||
samples = color_buffer->Samples();
|
||||
++num_images;
|
||||
}
|
||||
color_attachment_alias_masks = MoltenVK::ColorAttachmentAliasMasks(
|
||||
runtime.device.GetDriverID(), color_images, color_ranges);
|
||||
for (size_t slot = 0; slot < NUM_RT; ++slot) {
|
||||
if (color_attachment_alias_masks[slot] != 0) {
|
||||
duplicate_color_attachment_mask |= 1U << slot;
|
||||
}
|
||||
}
|
||||
const size_t num_colors = attachments.size();
|
||||
if (depth_buffer) {
|
||||
width = std::min(width, is_rescaled ? resolution.ScaleUp(depth_buffer->size.width)
|
||||
|
|
@ -2030,10 +2056,14 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
|||
renderpass_key.samples = samples;
|
||||
|
||||
renderpass = runtime.render_pass_cache.Get(renderpass_key);
|
||||
render_pass_key = renderpass_key;
|
||||
runtime_ptr = &runtime;
|
||||
render_area.width = std::min(render_area.width, width);
|
||||
render_area.height = std::min(render_area.height, height);
|
||||
|
||||
num_color_buffers = static_cast<u32>(num_colors);
|
||||
framebuffer_layers = static_cast<u32>(std::max(num_layers, 1));
|
||||
std::copy(attachments.begin(), attachments.end(), framebuffer_attachments.begin());
|
||||
framebuffer = runtime.device.GetLogical().CreateFramebuffer({
|
||||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
|
|
@ -2043,10 +2073,188 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
|||
.pAttachments = attachments.data(),
|
||||
.width = render_area.width,
|
||||
.height = render_area.height,
|
||||
.layers = static_cast<u32>(std::max(num_layers, 1)),
|
||||
.layers = framebuffer_layers,
|
||||
});
|
||||
}
|
||||
|
||||
u32 Framebuffer::ColorScratchMask(u32 color_write_mask) const {
|
||||
if (duplicate_color_attachment_mask == 0) {
|
||||
return 0;
|
||||
}
|
||||
const u32 writers = duplicate_color_attachment_mask & color_write_mask;
|
||||
if (writers != last_checked_color_write_mask) {
|
||||
last_checked_color_write_mask = writers;
|
||||
for (size_t slot = 0; slot < NUM_RT; ++slot) {
|
||||
if ((writers & (1U << slot)) == 0) {
|
||||
continue;
|
||||
}
|
||||
// Only direct overlaps conflict. In A-B-C, writers A and C may be disjoint.
|
||||
const u32 new_conflicts = color_attachment_alias_masks[slot] & writers &
|
||||
~warned_color_attachment_aliases[slot] & ((1U << slot) - 1);
|
||||
for (size_t other = 0; other < slot; ++other) {
|
||||
if ((new_conflicts & (1U << other)) == 0) {
|
||||
continue;
|
||||
}
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Color attachments {} and {} overlap with multiple real writers "
|
||||
"(mask {:02x}); Vulkan cannot represent this losslessly, "
|
||||
"preserving guest writers",
|
||||
other, slot, writers);
|
||||
warned_color_attachment_aliases[slot] |= 1U << other;
|
||||
}
|
||||
}
|
||||
}
|
||||
// No writer: redirect every alias. Preserve all real writers; scratch cannot faithfully
|
||||
// emulate simultaneous writes to overlapping storage.
|
||||
return duplicate_color_attachment_mask & ~color_write_mask;
|
||||
}
|
||||
|
||||
void Framebuffer::EnsureScratchColorAttachment(size_t slot) const {
|
||||
ASSERT(runtime_ptr != nullptr);
|
||||
ASSERT(slot < NUM_RT);
|
||||
ScratchColorAttachment& scratch = scratch_color_attachments[slot];
|
||||
if (scratch.image) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PixelFormat format = render_pass_key.color_formats[slot];
|
||||
ASSERT(format != PixelFormat::Invalid);
|
||||
const VkFormat vk_format =
|
||||
MaxwellToVK::SurfaceFormat(runtime_ptr->device, FormatType::Optimal, true, format).format;
|
||||
const auto create_image = [&](VkSampleCountFlagBits image_samples) {
|
||||
return runtime_ptr->memory_allocator.CreateTransientImage(VkImageCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = vk_format,
|
||||
.extent = {render_area.width, render_area.height, 1},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = framebuffer_layers,
|
||||
.samples = image_samples,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
});
|
||||
};
|
||||
const auto create_view = [&](VkImage image) {
|
||||
return runtime_ptr->device.GetLogical().CreateImageView(VkImageViewCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.image = image,
|
||||
.viewType = framebuffer_layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY
|
||||
: VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = vk_format,
|
||||
.components{},
|
||||
.subresourceRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = framebuffer_layers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
scratch.image = create_image(samples);
|
||||
scratch.view = create_view(*scratch.image);
|
||||
}
|
||||
|
||||
VkFramebuffer Framebuffer::HandleVariant(u32 color_scratch_mask) const {
|
||||
color_scratch_mask &= duplicate_color_attachment_mask;
|
||||
if (color_scratch_mask == 0) {
|
||||
return *framebuffer;
|
||||
}
|
||||
for (const auto& [mask, cached] : scratch_framebuffers) {
|
||||
if (mask == color_scratch_mask) {
|
||||
return *cached;
|
||||
}
|
||||
}
|
||||
|
||||
auto attachments = framebuffer_attachments;
|
||||
for (size_t slot = 0; slot < NUM_RT; ++slot) {
|
||||
if ((color_scratch_mask & (1u << slot)) == 0) {
|
||||
continue;
|
||||
}
|
||||
EnsureScratchColorAttachment(slot);
|
||||
const ScratchColorAttachment& scratch = scratch_color_attachments[slot];
|
||||
attachments[rt_map[slot]] = *scratch.view;
|
||||
}
|
||||
|
||||
vk::Framebuffer variant = runtime_ptr->device.GetLogical().CreateFramebuffer({
|
||||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.renderPass = renderpass,
|
||||
.attachmentCount = num_images,
|
||||
.pAttachments = attachments.data(),
|
||||
.width = render_area.width,
|
||||
.height = render_area.height,
|
||||
.layers = framebuffer_layers,
|
||||
});
|
||||
const VkFramebuffer handle = *variant;
|
||||
LOG_DEBUG(Render_Vulkan, "Redirecting duplicate color attachment mask {:02x} to scratch",
|
||||
color_scratch_mask);
|
||||
scratch_framebuffers.emplace_back(color_scratch_mask, std::move(variant));
|
||||
return handle;
|
||||
}
|
||||
|
||||
VkRenderPass Framebuffer::RenderPassVariant(u32 color_scratch_mask) const {
|
||||
color_scratch_mask &= duplicate_color_attachment_mask;
|
||||
if (color_scratch_mask == 0) {
|
||||
return renderpass;
|
||||
}
|
||||
for (const auto& [mask, cached] : scratch_renderpasses) {
|
||||
if (mask == color_scratch_mask) {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
RenderPassKey key = render_pass_key;
|
||||
key.color_scratch_mask = color_scratch_mask;
|
||||
const VkRenderPass variant = runtime_ptr->render_pass_cache.Get(key);
|
||||
scratch_renderpasses.emplace_back(color_scratch_mask, variant);
|
||||
return variant;
|
||||
}
|
||||
|
||||
std::array<VkImage, 9> Framebuffer::Images(u32 color_scratch_mask) const noexcept {
|
||||
auto result = images;
|
||||
color_scratch_mask &= duplicate_color_attachment_mask;
|
||||
if (color_scratch_mask == 0) {
|
||||
return result;
|
||||
}
|
||||
for (size_t slot = 0; slot < NUM_RT; ++slot) {
|
||||
if ((color_scratch_mask & (1U << slot)) != 0) {
|
||||
result[rt_map[slot]] = *scratch_color_attachments[slot].image;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::array<VkImageSubresourceRange, 9> Framebuffer::ImageRanges(
|
||||
u32 color_scratch_mask) const noexcept {
|
||||
auto result = image_ranges;
|
||||
color_scratch_mask &= duplicate_color_attachment_mask;
|
||||
if (color_scratch_mask == 0) {
|
||||
return result;
|
||||
}
|
||||
for (size_t slot = 0; slot < NUM_RT; ++slot) {
|
||||
if ((color_scratch_mask & (1U << slot)) != 0) {
|
||||
result[rt_map[slot]] = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = framebuffer_layers,
|
||||
};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void TextureCacheRuntime::AccelerateImageUpload(
|
||||
Image& image, const StagingBufferRef& map,
|
||||
std::span<const VideoCommon::SwizzleParameters> swizzles) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "yuzu_shader_recompiler/shader_info.h"
|
||||
#include "yuzu_video_core/renderer_vulkan/vk_compute_pass.h"
|
||||
#include "yuzu_video_core/renderer_vulkan/vk_render_pass_cache.h"
|
||||
#include "yuzu_video_core/renderer_vulkan/vk_staging_buffer_pool.h"
|
||||
#include "yuzu_video_core/texture_cache/image_view_base.h"
|
||||
#include "yuzu_video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
|
|
@ -311,7 +312,7 @@ public:
|
|||
Framebuffer& operator=(const Framebuffer&) = delete;
|
||||
|
||||
Framebuffer(Framebuffer&&) = default;
|
||||
Framebuffer& operator=(Framebuffer&&) = default;
|
||||
Framebuffer& operator=(Framebuffer&&) noexcept;
|
||||
|
||||
void CreateFramebuffer(TextureCacheRuntime& runtime,
|
||||
std::span<ImageView*, NUM_RT> color_buffers, ImageView* depth_buffer,
|
||||
|
|
@ -325,6 +326,15 @@ public:
|
|||
return renderpass;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkFramebuffer HandleVariant(u32 color_scratch_mask) const;
|
||||
[[nodiscard]] VkRenderPass RenderPassVariant(u32 color_scratch_mask) const;
|
||||
|
||||
[[nodiscard]] u32 DuplicateColorAttachmentMask() const noexcept {
|
||||
return duplicate_color_attachment_mask;
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 ColorScratchMask(u32 color_write_mask) const;
|
||||
|
||||
[[nodiscard]] VkExtent2D RenderArea() const noexcept {
|
||||
return render_area;
|
||||
}
|
||||
|
|
@ -337,20 +347,22 @@ public:
|
|||
return num_color_buffers;
|
||||
}
|
||||
|
||||
// Number of subpass color slots, including gaps before the last valid attachment.
|
||||
[[nodiscard]] u32 NumColorAttachments() const noexcept {
|
||||
return num_color_attachments;
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 NumImages() const noexcept {
|
||||
return num_images;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::array<VkImage, 9>& Images() const noexcept {
|
||||
return images;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::array<VkImageSubresourceRange, 9>& ImageRanges() const noexcept {
|
||||
return image_ranges;
|
||||
}
|
||||
[[nodiscard]] std::array<VkImage, 9> Images(u32 color_scratch_mask = 0) const noexcept;
|
||||
[[nodiscard]] std::array<VkImageSubresourceRange, 9> ImageRanges(
|
||||
u32 color_scratch_mask = 0) const noexcept;
|
||||
|
||||
[[nodiscard]] bool HasAspectColorBit(size_t index) const noexcept {
|
||||
return (image_ranges.at(rt_map[index]).aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
|
||||
return render_pass_key.color_formats.at(index) != PixelFormat::Invalid &&
|
||||
(image_ranges.at(rt_map[index]).aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasAspectDepthBit() const noexcept {
|
||||
|
|
@ -366,11 +378,31 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
struct ScratchColorAttachment {
|
||||
vk::Image image;
|
||||
vk::ImageView view;
|
||||
};
|
||||
|
||||
void EnsureScratchColorAttachment(size_t slot) const;
|
||||
|
||||
// Framebuffers must be destroyed before the scratch views and images they reference.
|
||||
mutable std::array<ScratchColorAttachment, NUM_RT> scratch_color_attachments{};
|
||||
mutable std::vector<std::pair<u32, vk::Framebuffer>> scratch_framebuffers;
|
||||
mutable std::vector<std::pair<u32, VkRenderPass>> scratch_renderpasses;
|
||||
TextureCacheRuntime* runtime_ptr{};
|
||||
std::array<VkImageView, NUM_RT + 1> framebuffer_attachments{};
|
||||
u32 framebuffer_layers{1};
|
||||
u32 duplicate_color_attachment_mask{};
|
||||
std::array<u32, NUM_RT> color_attachment_alias_masks{};
|
||||
mutable std::array<u32, NUM_RT> warned_color_attachment_aliases{};
|
||||
mutable u32 last_checked_color_write_mask{~u32{0}};
|
||||
RenderPassKey render_pass_key{};
|
||||
vk::Framebuffer framebuffer;
|
||||
VkRenderPass renderpass{};
|
||||
VkExtent2D render_area{};
|
||||
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
u32 num_color_buffers = 0;
|
||||
u32 num_color_attachments = 0;
|
||||
u32 num_images = 0;
|
||||
std::array<VkImage, 9> images{};
|
||||
std::array<VkImageSubresourceRange, 9> image_ranges{};
|
||||
|
|
|
|||
|
|
@ -470,6 +470,28 @@ typename P::Framebuffer* TextureCache<P>::GetFramebuffer() {
|
|||
return &slot_framebuffers[GetFramebufferId(render_targets)];
|
||||
}
|
||||
|
||||
template <class P>
|
||||
typename P::Framebuffer* TextureCache<P>::GetClearFramebuffer(size_t color_attachment, u32 layer) {
|
||||
const bool is_color = color_attachment < NUM_RT;
|
||||
const ImageViewId source_id = is_color ? render_targets.color_buffer_ids[color_attachment]
|
||||
: render_targets.depth_buffer_id;
|
||||
const ImageView& source = slot_image_views[source_id];
|
||||
ASSERT(layer < static_cast<u32>(source.range.extent.layers));
|
||||
SubresourceRange range = source.range;
|
||||
range.base.layer += static_cast<s32>(layer);
|
||||
range.extent.layers = 1;
|
||||
const ImageId image_id = source.image_id;
|
||||
const ImageViewInfo view_info{ImageViewType::e2D, source.format, range};
|
||||
const ImageViewId view_id = FindOrEmplaceImageView(image_id, view_info);
|
||||
const RenderTargets key{
|
||||
.color_buffer_ids = {is_color ? view_id : ImageViewId{}},
|
||||
.depth_buffer_id = is_color ? ImageViewId{} : view_id,
|
||||
.size = render_targets.size,
|
||||
.is_rescaled = render_targets.is_rescaled,
|
||||
};
|
||||
return &slot_framebuffers[GetFramebufferId(key)];
|
||||
}
|
||||
|
||||
template <class P>
|
||||
template <bool has_blacklists>
|
||||
void TextureCache<P>::FillImageViews(DescriptorTable<TICEntry>& table,
|
||||
|
|
|
|||
|
|
@ -192,6 +192,9 @@ public:
|
|||
/// UpdateRenderTargets should be called before this
|
||||
Framebuffer* GetFramebuffer();
|
||||
|
||||
/// Isolate one attachment/layer for helper draws. NUM_RT selects depth/stencil.
|
||||
Framebuffer* GetClearFramebuffer(size_t color_attachment, u32 layer);
|
||||
|
||||
/// Mark images in a range as modified from the CPU
|
||||
void WriteMemory(DAddr cpu_addr, size_t size);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
/*
|
||||
The following notice applies to portions of the duplicate-attachment workaround.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) Ryujinx Team and Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
namespace Vulkan::MoltenVK {
|
||||
|
||||
// MoltenVK must not enable unsupported depth-bounds dynamic states.
|
||||
constexpr bool UseDepthBoundsDynamicState(VkDriverId driver, bool supported) {
|
||||
return driver != VK_DRIVER_ID_MOLTENVK || supported;
|
||||
}
|
||||
// Larger MoltenVK push layouts can produce unstable
|
||||
// Metal bindings. Use ordinary descriptor sets beyond this conservative limit.
|
||||
constexpr uint32_t PushDescriptorLimit(VkDriverId driver, uint32_t advertised_limit) {
|
||||
return driver == VK_DRIVER_ID_MOLTENVK ? std::min(advertised_limit, 16U) : advertised_limit;
|
||||
}
|
||||
|
||||
constexpr bool SubresourceRangesOverlap(const VkImageSubresourceRange& lhs,
|
||||
const VkImageSubresourceRange& rhs) {
|
||||
if ((lhs.aspectMask & rhs.aspectMask) == 0 || lhs.levelCount == 0 || rhs.levelCount == 0 ||
|
||||
lhs.layerCount == 0 || rhs.layerCount == 0) {
|
||||
return false;
|
||||
}
|
||||
const uint64_t lhs_mip_end = static_cast<uint64_t>(lhs.baseMipLevel) + lhs.levelCount;
|
||||
const uint64_t rhs_mip_end = static_cast<uint64_t>(rhs.baseMipLevel) + rhs.levelCount;
|
||||
const uint64_t lhs_layer_end = static_cast<uint64_t>(lhs.baseArrayLayer) + lhs.layerCount;
|
||||
const uint64_t rhs_layer_end = static_cast<uint64_t>(rhs.baseArrayLayer) + rhs.layerCount;
|
||||
return lhs.baseMipLevel < rhs_mip_end && rhs.baseMipLevel < lhs_mip_end &&
|
||||
lhs.baseArrayLayer < rhs_layer_end && rhs.baseArrayLayer < lhs_layer_end;
|
||||
}
|
||||
|
||||
template <typename Handle>
|
||||
constexpr bool ColorAttachmentsAlias(Handle lhs_image, const VkImageSubresourceRange& lhs_range,
|
||||
Handle rhs_image, const VkImageSubresourceRange& rhs_range) {
|
||||
return lhs_image != Handle{} && lhs_image == rhs_image &&
|
||||
SubresourceRangesOverlap(lhs_range, rhs_range);
|
||||
}
|
||||
|
||||
// Compare underlying images, including different views.
|
||||
// Keep disjoint mip/layer views and record actual overlapping pairs, not transitive groups.
|
||||
template <typename Handle, size_t N>
|
||||
constexpr std::array<uint32_t, N> ColorAttachmentAliasMasks(
|
||||
VkDriverId driver, const std::array<Handle, N>& images,
|
||||
const std::array<VkImageSubresourceRange, N>& ranges) {
|
||||
static_assert(N <= 32);
|
||||
std::array<uint32_t, N> aliases{};
|
||||
if (driver != VK_DRIVER_ID_MOLTENVK) {
|
||||
return aliases;
|
||||
}
|
||||
for (size_t slot = 0; slot < N; ++slot) {
|
||||
for (size_t previous = 0; previous < slot; ++previous) {
|
||||
if (ColorAttachmentsAlias(images[slot], ranges[slot], images[previous], ranges[previous])) {
|
||||
aliases[slot] |= 1U << previous;
|
||||
aliases[previous] |= 1U << slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
return aliases;
|
||||
}
|
||||
|
||||
} // namespace Vulkan::MoltenVK
|
||||
|
|
@ -4,6 +4,9 @@
|
|||
#pragma once
|
||||
|
||||
#define VK_NO_PROTOTYPES
|
||||
#ifndef VK_ENABLE_BETA_EXTENSIONS
|
||||
#define VK_ENABLE_BETA_EXTENSIONS
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#define VK_USE_PLATFORM_WIN32_KHR
|
||||
#elif defined(__APPLE__)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "yuzu_video_core/vulkan_common/moltenvk_workarounds.h"
|
||||
|
||||
#include "yuzu_common/common_types.h"
|
||||
#include "yuzu_common/logging/log.h"
|
||||
#include "yuzu_common/settings.h"
|
||||
|
|
@ -49,6 +51,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
|||
FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
|
||||
FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
|
||||
FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
|
||||
FEATURE(KHR, PortabilitySubset, PORTABILITY_SUBSET, portability_subset) \
|
||||
FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
|
||||
primitive_topology_list_restart) \
|
||||
FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \
|
||||
|
|
@ -294,6 +297,16 @@ public:
|
|||
return properties.properties.limits.maxStorageBufferRange;
|
||||
}
|
||||
|
||||
/// Returns the maximum number of storage-buffer descriptors visible to one shader stage.
|
||||
u32 GetMaxPerStageStorageBuffers() const {
|
||||
return properties.properties.limits.maxPerStageDescriptorStorageBuffers;
|
||||
}
|
||||
|
||||
/// Returns the maximum total number of descriptor resources visible to one shader stage.
|
||||
u32 GetMaxPerStageResources() const {
|
||||
return properties.properties.limits.maxPerStageResources;
|
||||
}
|
||||
|
||||
/// Returns the maximum size for push constants.
|
||||
VkDeviceSize GetMaxPushConstantsSize() const {
|
||||
return properties.properties.limits.maxPushConstantsSize;
|
||||
|
|
@ -361,7 +374,7 @@ public:
|
|||
|
||||
/// Returns the maximum number of push descriptors.
|
||||
u32 MaxPushDescriptors() const {
|
||||
return properties.push_descriptor.maxPushDescriptors;
|
||||
return MoltenVK::PushDescriptorLimit(GetDriverID(), properties.push_descriptor.maxPushDescriptors);
|
||||
}
|
||||
|
||||
/// Returns true if formatless image load is supported.
|
||||
|
|
@ -384,6 +397,15 @@ public:
|
|||
return features.features.depthBounds;
|
||||
}
|
||||
|
||||
bool CanUseDepthBoundsDynamicState() const {
|
||||
return MoltenVK::UseDepthBoundsDynamicState(GetDriverID(), IsDepthBoundsSupported());
|
||||
}
|
||||
|
||||
bool CanRequestComputeSubgroupSize() const {
|
||||
return IsExtSubgroupSizeControlSupported() &&
|
||||
IsGuestWarpSizeSupported(VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
}
|
||||
|
||||
/// Returns true when blitting from and to D24S8 images is supported.
|
||||
bool IsBlitDepth24Stencil8Supported() const {
|
||||
return is_blit_depth24_stencil8_supported;
|
||||
|
|
@ -419,6 +441,20 @@ public:
|
|||
return extensions.push_descriptor;
|
||||
}
|
||||
|
||||
/// Returns true if vertex attributes may access bytes beyond their binding stride.
|
||||
bool SupportsVertexAttributeAccessBeyondStride() const {
|
||||
return !extensions.portability_subset ||
|
||||
features.portability_subset.vertexAttributeAccessBeyondStride != VK_FALSE;
|
||||
}
|
||||
|
||||
/// Returns true if vertex attribute formats must be kept within their binding stride.
|
||||
/// MoltenVK advertises access beyond the stride, but Metal fetches such attributes
|
||||
/// incorrectly, so the workaround is also applied there.
|
||||
bool NeedsVertexAttributeStrideWorkaround() const {
|
||||
return !SupportsVertexAttributeAccessBeyondStride() ||
|
||||
GetDriverID() == VK_DRIVER_ID_MOLTENVK;
|
||||
}
|
||||
|
||||
/// Returns true if VK_KHR_pipeline_executable_properties is enabled.
|
||||
bool IsKhrPipelineExecutablePropertiesEnabled() const {
|
||||
return extensions.pipeline_executable_properties;
|
||||
|
|
|
|||
|
|
@ -251,6 +251,27 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
|
|||
device.GetDispatchLoader());
|
||||
}
|
||||
|
||||
vk::Image MemoryAllocator::CreateTransientImage(const VkImageCreateInfo& ci) const {
|
||||
const VmaAllocationCreateInfo alloc_ci = {
|
||||
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
|
||||
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
|
||||
.requiredFlags = 0,
|
||||
.preferredFlags =
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT,
|
||||
.memoryTypeBits = 0,
|
||||
.pool = VK_NULL_HANDLE,
|
||||
.pUserData = nullptr,
|
||||
.priority = 0.f,
|
||||
};
|
||||
VkImage handle{};
|
||||
VmaAllocation allocation{};
|
||||
|
||||
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr));
|
||||
|
||||
return vk::Image(handle, *device.GetLogical(), allocator, allocation,
|
||||
device.GetDispatchLoader());
|
||||
}
|
||||
|
||||
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
|
||||
const VmaAllocationCreateInfo alloc_ci = {
|
||||
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ public:
|
|||
MemoryAllocator(const MemoryAllocator&) = delete;
|
||||
|
||||
vk::Image CreateImage(const VkImageCreateInfo& ci) const;
|
||||
vk::Image CreateTransientImage(const VkImageCreateInfo& ci) const;
|
||||
|
||||
vk::Buffer CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -207,6 +207,7 @@
|
|||
<ClInclude Include="vulkan_common\vulkan.h" />
|
||||
<ClInclude Include="vulkan_common\vulkan_debug_callback.h" />
|
||||
<ClInclude Include="vulkan_common\vulkan_device.h" />
|
||||
<ClInclude Include="vulkan_common\moltenvk_workarounds.h" />
|
||||
<ClInclude Include="vulkan_common\vulkan_instance.h" />
|
||||
<ClInclude Include="vulkan_common\vulkan_library.h" />
|
||||
<ClInclude Include="vulkan_common\vulkan_memory_allocator.h" />
|
||||
|
|
@ -392,4 +393,4 @@
|
|||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -633,6 +633,9 @@
|
|||
<ClInclude Include="vulkan_common\vulkan_device.h">
|
||||
<Filter>Header Files\vulkan_common</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vulkan_common\moltenvk_workarounds.h">
|
||||
<Filter>Header Files\vulkan_common</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vulkan_common\vulkan_instance.h">
|
||||
<Filter>Header Files\vulkan_common</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -1089,4 +1092,4 @@
|
|||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
|||
Loading…
Reference in New Issue