Compare commits

...

14 Commits

Author SHA1 Message Date
CamilleLaVey 95759ae726 [TEST] Quick change on depth's handling 2026-08-12 01:30:23 -04:00
CamilleLaVey dba3bfe74b [TEST] Depth stencil resolve + MSAA depth stencil 2026-08-12 01:19:52 -04:00
CamilleLaVey 6d9e4b97a6 [TEST] MSAA uploads 2026-08-12 00:44:14 -04:00
CamilleLaVey c14bfc3d5e [TEST] Fix vkFormat accessors 2026-08-11 23:25:46 -04:00
CamilleLaVey a238536d6d [TEST] Geometry shader changes + barrier fixes 2026-08-11 23:07:23 -04:00
CamilleLaVey 9fe8674760 [TEST] Fix TFB subdependency 2026-08-11 21:30:31 -04:00
CamilleLaVey 97547657b5 [TEST] Fix missing barrier + removal of required aligments checks inside swizzle textures 2026-08-11 21:00:58 -04:00
CamilleLaVey f73164b191 [TEST] Adjustments on pipeline hash collisions 2026-08-11 20:34:08 -04:00
CamilleLaVey 35c420582f [TEST] Remove subpass dependency 2026-08-11 20:10:53 -04:00
CamilleLaVey b2b7a34957 [TEST] Ported PSO optimizations from tiled-gpu-v2 2026-08-10 03:54:18 -04:00
CamilleLaVey d7a564b088 [TEST] Another bind vertex buffer optimization from previous handling 2026-08-10 03:30:58 -04:00
CamilleLaVey 1d57d4a73d [TEST] Playing with SGSR 2026-08-10 02:57:05 -04:00
CamilleLaVey 3af03332cc [TEST] fast path on swizzle decoding 2026-08-10 01:41:21 -04:00
CamilleLaVey a6e506c328 [TEST] Bring the unswizzle work from tiled-gpu-v2 2026-08-10 01:29:27 -04:00
39 changed files with 2018 additions and 461 deletions
@@ -586,6 +586,7 @@ abstract class SettingsItem(
IntSetting.FSR_SHARPENING_SLIDER,
titleId = R.string.fsr_sharpness,
descriptionId = R.string.fsr_sharpness_description,
max = 200,
units = "%"
)
)
@@ -1182,7 +1182,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
container,
IntSetting.FSR_SHARPENING_SLIDER,
minValue = 0,
maxValue = 100,
maxValue = 200,
units = "%"
)
}
@@ -88,25 +88,10 @@ Result IApplicationDisplayService::GetIndirectDisplayTransactionService(
}
Result IApplicationDisplayService::OpenDisplay(Out<u64> out_display_id, DisplayName display_name) {
LOG_DEBUG(Service_VI, "called with display_name={}", display_name.data());
// Ensure the display name is null-terminated
display_name[display_name.size() - 1] = '\0';
// According to switchbrew, only "Default", "External", "Edid", "Internal" and "Null" are valid
const std::array<std::string_view, 5> valid_names = {
"Default", "External", "Edid", "Internal", "Null"
};
bool valid_name = false;
for (const auto& name : valid_names) {
if (name == display_name.data()) {
valid_name = true;
break;
}
}
R_UNLESS(valid_name, ResultOperationFailed);
LOG_DEBUG(Service_VI, "called with display_name={}", display_name.data());
R_RETURN(m_container->OpenDisplay(out_display_id, display_name));
}
@@ -50,12 +50,6 @@ constexpr std::array RGBA_LUT{
R | G | B | A, //
};
void CheckAlignment(IR::Reg reg, size_t alignment) {
if (!IR::IsAligned(reg, alignment)) {
throw NotImplementedException("Unaligned source register {}", reg);
}
}
template <typename... Args>
IR::Value Composite(TranslatorVisitor& v, Args... regs) {
return v.ir.CompositeConstruct(v.F(regs)...);
@@ -86,67 +80,53 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
info.type.Assign(TextureType::Color2D);
return v.ir.ImageSampleExplicitLod(handle, Composite(v, reg_a, reg_b), zero, {}, info);
case 3: // 2D.LL
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::Color2D);
return v.ir.ImageSampleExplicitLod(handle, Composite(v, reg_a, reg_a + 1), v.F(reg_b), {},
info);
case 4: // 2D.DC
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::Color2D);
info.is_depth.Assign(1);
return v.ir.ImageSampleDrefImplicitLod(handle, Composite(v, reg_a, reg_a + 1), v.F(reg_b),
{}, {}, {}, info);
case 5: // 2D.LL.DC
CheckAlignment(reg_a, 2);
CheckAlignment(reg_b, 2);
info.type.Assign(TextureType::Color2D);
info.is_depth.Assign(1);
return v.ir.ImageSampleDrefExplicitLod(handle, Composite(v, reg_a, reg_a + 1),
v.F(reg_b + 1), v.F(reg_b), {}, info);
case 6: // 2D.LZ.DC
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::Color2D);
info.is_depth.Assign(1);
return v.ir.ImageSampleDrefExplicitLod(handle, Composite(v, reg_a, reg_a + 1), v.F(reg_b),
zero, {}, info);
case 7: // ARRAY_2D
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::ColorArray2D);
return v.ir.ImageSampleImplicitLod(
handle, v.ir.CompositeConstruct(v.F(reg_a + 1), v.F(reg_b), ReadArray(v, v.X(reg_a))),
{}, {}, {}, info);
case 8: // ARRAY_2D.LZ
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::ColorArray2D);
return v.ir.ImageSampleExplicitLod(
handle, v.ir.CompositeConstruct(v.F(reg_a + 1), v.F(reg_b), ReadArray(v, v.X(reg_a))),
zero, {}, info);
case 9: // ARRAY_2D.LZ.DC
CheckAlignment(reg_a, 2);
CheckAlignment(reg_b, 2);
info.type.Assign(TextureType::ColorArray2D);
info.is_depth.Assign(1);
return v.ir.ImageSampleDrefExplicitLod(
handle, v.ir.CompositeConstruct(v.F(reg_a + 1), v.F(reg_b), ReadArray(v, v.X(reg_a))),
v.F(reg_b + 1), zero, {}, info);
case 10: // 3D
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::Color3D);
return v.ir.ImageSampleImplicitLod(handle, Composite(v, reg_a, reg_a + 1, reg_b), {}, {},
{}, info);
case 11: // 3D.LZ
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::Color3D);
return v.ir.ImageSampleExplicitLod(handle, Composite(v, reg_a, reg_a + 1, reg_b), zero, {},
info);
case 12: // CUBE
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::ColorCube);
return v.ir.ImageSampleImplicitLod(handle, Composite(v, reg_a, reg_a + 1, reg_b), {}, {},
{}, info);
case 13: // CUBE.LL
CheckAlignment(reg_a, 2);
CheckAlignment(reg_b, 2);
info.type.Assign(TextureType::ColorCube);
return v.ir.ImageSampleExplicitLod(handle, Composite(v, reg_a, reg_a + 1, reg_b),
v.F(reg_b + 1), {}, info);
@@ -187,12 +167,10 @@ IR::Reg RegStoreComponent32(u64 insn, unsigned index) {
case 0:
return texs.dest_reg_a;
case 1:
CheckAlignment(texs.dest_reg_a, 2);
return texs.dest_reg_a + 1;
case 2:
return texs.dest_reg_b;
case 3:
CheckAlignment(texs.dest_reg_b, 2);
return texs.dest_reg_b + 1;
}
throw LogicError("Invalid store index {}", index);
@@ -34,12 +34,6 @@ union Encoding {
BitField<36, 13, u64> cbuf_offset;
};
void CheckAlignment(IR::Reg reg, size_t alignment) {
if (!IR::IsAligned(reg, alignment)) {
throw NotImplementedException("Unaligned source register {}", reg);
}
}
IR::Value MakeOffset(TranslatorVisitor& v, IR::Reg reg) {
const IR::U32 value{v.X(reg)};
return v.ir.CompositeConstruct(v.ir.BitFieldExtract(value, v.ir.Imm32(0), v.ir.Imm32(6), true),
@@ -60,18 +54,15 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
info.is_depth.Assign(tld4s.dc != 0 ? 1 : 0);
IR::Value coords;
if (tld4s.aoffi != 0) {
CheckAlignment(reg_a, 2);
coords = v.ir.CompositeConstruct(v.F(reg_a), v.F(reg_a + 1));
IR::Value offset = MakeOffset(v, reg_b);
if (tld4s.dc != 0) {
CheckAlignment(reg_b, 2);
IR::F32 dref = v.F(reg_b + 1);
return v.ir.ImageGatherDref(handle, coords, offset, {}, dref, info);
}
return v.ir.ImageGather(handle, coords, offset, {}, info);
}
if (tld4s.dc != 0) {
CheckAlignment(reg_a, 2);
coords = v.ir.CompositeConstruct(v.F(reg_a), v.F(reg_a + 1));
IR::F32 dref = v.F(reg_b);
return v.ir.ImageGatherDref(handle, coords, {}, {}, dref, info);
@@ -86,12 +77,10 @@ IR::Reg RegStoreComponent32(u64 insn, size_t index) {
case 0:
return tlds4.dest_reg_a;
case 1:
CheckAlignment(tlds4.dest_reg_a, 2);
return tlds4.dest_reg_a + 1;
case 2:
return tlds4.dest_reg_b;
case 3:
CheckAlignment(tlds4.dest_reg_b, 2);
return tlds4.dest_reg_b + 1;
}
throw LogicError("Invalid store index {}", index);
@@ -55,12 +55,6 @@ union Encoding {
BitField<53, 4, u64> encoding;
};
void CheckAlignment(IR::Reg reg, size_t alignment) {
if (!IR::IsAligned(reg, alignment)) {
throw NotImplementedException("Unaligned source register {}", reg);
}
}
IR::Value MakeOffset(TranslatorVisitor& v, IR::Reg reg) {
const IR::U32 value{v.X(reg)};
return v.ir.CompositeConstruct(v.ir.BitFieldExtract(value, v.ir.Imm32(0), v.ir.Imm32(4), true),
@@ -92,38 +86,31 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_b));
break;
case 4:
CheckAlignment(reg_a, 2);
texture_type = Shader::TextureType::Color2D;
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_a + 1));
offsets = MakeOffset(v, reg_b);
break;
case 5:
CheckAlignment(reg_a, 2);
texture_type = Shader::TextureType::Color2D;
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_a + 1));
lod = v.X(reg_b);
break;
case 6:
CheckAlignment(reg_a, 2);
texture_type = Shader::TextureType::Color2D;
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_a + 1));
multisample = v.X(reg_b);
break;
case 7:
CheckAlignment(reg_a, 2);
texture_type = Shader::TextureType::Color3D;
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_a + 1), v.X(reg_b));
break;
case 8: {
CheckAlignment(reg_b, 2);
const IR::U32 array{v.ir.BitFieldExtract(v.X(reg_a), v.ir.Imm32(0), v.ir.Imm32(16))};
texture_type = Shader::TextureType::ColorArray2D;
coords = v.ir.CompositeConstruct(v.X(reg_b), v.X(reg_b + 1), array);
break;
}
case 12:
CheckAlignment(reg_a, 2);
CheckAlignment(reg_b, 2);
texture_type = Shader::TextureType::Color2D;
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_a + 1));
lod = v.X(reg_b);
@@ -166,12 +153,10 @@ IR::Reg RegStoreComponent32(u64 insn, unsigned index) {
case 0:
return tlds.dest_reg_a;
case 1:
CheckAlignment(tlds.dest_reg_a, 2);
return tlds.dest_reg_a + 1;
case 2:
return tlds.dest_reg_b;
case 3:
CheckAlignment(tlds.dest_reg_b, 2);
return tlds.dest_reg_b + 1;
}
throw LogicError("Invalid store index {}", index);
@@ -169,11 +169,36 @@ std::map<IR::Attribute, IR::Attribute> GenerateLegacyToGenericMappings(
return mapping;
}
struct PassthroughVertices {
u32 count;
u32 first;
u32 stride;
};
PassthroughVertices GetPassthroughVertices(InputTopology input_topology) {
switch (input_topology) {
case InputTopology::Points:
return {1, 0, 1};
case InputTopology::Lines:
return {2, 0, 1};
case InputTopology::LinesAdjacency:
return {2, 1, 1};
case InputTopology::Triangles:
return {3, 0, 1};
case InputTopology::TrianglesAdjacency:
return {3, 0, 2};
}
return {3, 0, 1};
}
void EmitGeometryPassthrough(IR::IREmitter& ir, const IR::Program& program,
const Shader::VaryingState& passthrough_mask,
bool passthrough_position,
std::optional<IR::Attribute> passthrough_layer_attr) {
for (u32 i = 0; i < program.output_vertices; i++) {
std::optional<IR::Attribute> passthrough_layer_attr,
InputTopology input_topology) {
const PassthroughVertices vertices{GetPassthroughVertices(input_topology)};
for (u32 vertex = 0; vertex < vertices.count; vertex++) {
const u32 i = vertices.first + vertex * vertices.stride;
// Assign generics from input
for (u32 j = 0; j < 32; j++) {
if (!passthrough_mask.Generic(j)) {
@@ -208,25 +233,16 @@ void EmitGeometryPassthrough(IR::IREmitter& ir, const IR::Program& program,
ir.EndPrimitive(ir.Imm32(0));
}
u32 GetOutputTopologyVertices(OutputTopology output_topology) {
switch (output_topology) {
case OutputTopology::PointList:
return 1;
case OutputTopology::LineStrip:
return 2;
default:
return 3;
}
}
void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInfo& host_info) {
void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInfo& host_info,
InputTopology input_topology) {
for (IR::Block* const block : program.blocks) {
for (IR::Inst& inst : block->Instructions()) {
if (inst.GetOpcode() == IR::Opcode::Epilogue) {
IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)};
EmitGeometryPassthrough(
ir, program, program.info.passthrough,
program.info.passthrough.AnyComponent(IR::Attribute::PositionX), {});
program.info.passthrough.AnyComponent(IR::Attribute::PositionX), {},
input_topology);
}
}
}
@@ -235,7 +251,8 @@ void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInf
} // Anonymous namespace
IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool,
Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) {
Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info,
InputTopology input_topology) {
HostTranslateInfo normalized_host_info{host_info};
normalized_host_info.ApplyDescriptorLimitPolicy();
@@ -264,8 +281,9 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
}
if (!normalized_host_info.support_geometry_shader_passthrough) {
program.output_vertices = GetOutputTopologyVertices(program.output_topology);
LowerGeometryPassthrough(program, normalized_host_info);
program.output_vertices = GetPassthroughVertices(input_topology).count;
LowerGeometryPassthrough(program, normalized_host_info, input_topology);
program.is_geometry_passthrough = false;
}
}
break;
@@ -414,11 +432,12 @@ IR::Program GenerateGeometryPassthrough(ObjectPool<IR::Inst>& inst_pool,
ObjectPool<IR::Block>& block_pool,
const HostTranslateInfo& host_info,
IR::Program& source_program,
Shader::OutputTopology output_topology) {
Shader::OutputTopology output_topology,
InputTopology input_topology) {
IR::Program program;
program.stage = Stage::Geometry;
program.output_topology = output_topology;
program.output_vertices = GetOutputTopologyVertices(output_topology);
program.output_vertices = GetPassthroughVertices(input_topology).count;
program.is_geometry_passthrough = false;
program.info.loads.mask = source_program.info.stores.mask;
@@ -433,7 +452,7 @@ IR::Program GenerateGeometryPassthrough(ObjectPool<IR::Inst>& inst_pool,
IR::IREmitter ir{*current_block};
EmitGeometryPassthrough(ir, program, program.info.stores, true,
source_program.info.emulated_layer);
source_program.info.emulated_layer, input_topology);
IR::Block* return_block{block_pool.Create(inst_pool)};
IR::IREmitter{*return_block}.Epilogue();
@@ -18,7 +18,8 @@ namespace Shader::Maxwell {
[[nodiscard]] IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool,
ObjectPool<IR::Block>& block_pool, Environment& env,
Flow::CFG& cfg, const HostTranslateInfo& host_info);
Flow::CFG& cfg, const HostTranslateInfo& host_info,
InputTopology input_topology);
[[nodiscard]] IR::Program MergeDualVertexPrograms(IR::Program& vertex_a, IR::Program& vertex_b,
Environment& env_vertex_b);
@@ -32,6 +33,7 @@ void ConvertLegacyToGeneric(IR::Program& program, const RuntimeInfo& runtime_inf
ObjectPool<IR::Block>& block_pool,
const HostTranslateInfo& host_info,
IR::Program& source_program,
Shader::OutputTopology output_topology);
Shader::OutputTopology output_topology,
InputTopology input_topology);
} // namespace Shader::Maxwell
+28 -27
View File
@@ -7,6 +7,7 @@
#pragma once
#include <algorithm>
#include <bit>
#include <memory>
#include <numeric>
@@ -809,46 +810,46 @@ void BufferCache<P>::BindHostVertexBuffers() {
if (use_optimized_vertex_buffers) {
auto& flags = maxwell3d->dirty.flags;
u32 enabled_mask = enabled_vertex_buffers_mask;
HostBindings<Buffer> bindings{};
u32 last_index = (std::numeric_limits<u32>::max)();
const auto flush_bindings = [&]() {
if (bindings.buffers.empty()) {
return;
}
bindings.max_index = bindings.min_index + static_cast<u32>(bindings.buffers.size());
runtime.BindVertexBuffers(bindings);
bindings = HostBindings<Buffer>{};
last_index = (std::numeric_limits<u32>::max)();
};
while (enabled_mask != 0) {
const u32 index = std::countr_zero(enabled_mask);
enabled_mask &= (enabled_mask - 1);
const u32 enabled_mask = enabled_vertex_buffers_mask;
bool any_dirty = false;
u32 pending_mask = enabled_mask;
while (pending_mask != 0) {
const u32 index = std::countr_zero(pending_mask);
pending_mask &= (pending_mask - 1);
const Binding& binding = VertexBufferSlot(index);
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
SynchronizeBuffer(buffer, binding.device_addr, binding.size);
if (!flags[Dirty::VertexBuffer0 + index]) {
flush_bindings();
continue;
}
any_dirty |= flags[Dirty::VertexBuffer0 + index];
}
if (enabled_mask == 0 || !any_dirty) {
return;
}
const u32 min_index = static_cast<u32>(std::countr_zero(enabled_mask));
const u32 max_index = 32u - static_cast<u32>(std::countl_zero(enabled_mask));
HostBindings<Buffer> bindings{};
bindings.min_index = min_index;
bindings.max_index = max_index;
for (u32 index = min_index; index < max_index; ++index) {
flags[Dirty::VertexBuffer0 + index] = false;
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
if ((enabled_mask & (1u << index)) == 0) {
bindings.buffers.push_back(&slot_buffers[NULL_BUFFER_ID]);
bindings.offsets.push_back(0);
bindings.sizes.push_back(0);
bindings.strides.push_back(stride);
continue;
}
const Binding& binding = VertexBufferSlot(index);
Buffer& buffer = slot_buffers[binding.buffer_id];
const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, binding.size);
if (!bindings.buffers.empty() && index != last_index + 1) {
flush_bindings();
}
if (bindings.buffers.empty()) {
bindings.min_index = index;
}
bindings.buffers.push_back(&buffer);
bindings.offsets.push_back(offset);
bindings.sizes.push_back(binding.size);
bindings.strides.push_back(stride);
last_index = index;
}
flush_bindings();
runtime.BindVertexBuffers(bindings);
} else {
HostBindings<typename P::Buffer> host_bindings;
bool any_valid{false};
@@ -17,11 +17,13 @@ set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d_buffer.comp
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_buffer.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d32f.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_d32f_to_abgr8.frag
@@ -32,6 +34,8 @@ set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa_depth_stencil.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_s8d24_to_abgr8.frag
${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag
@@ -0,0 +1,104 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 430
#extension GL_EXT_shader_16bit_storage : require
#extension GL_EXT_shader_8bit_storage : require
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_BUFFER 1
layout(push_constant) uniform PushConstants {
uvec3 dim;
uint bytes_per_block_log2;
uvec3 origin;
uint layer_stride;
uint block_size;
uint x_shift;
uint block_height;
uint block_height_mask;
} pc;
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
uint out_u32[];
};
layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in;
const uint GOB_SIZE_X = 64;
const uint GOB_SIZE_Y = 8;
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
uint SwizzleOffset(uvec2 pos) {
pos = pos & SWIZZLE_MASK;
return SwizzleTable(pos.y * 64u + pos.x);
}
uvec4 ReadTexel(uint offset) {
switch (pc.bytes_per_block_log2) {
case 2u:
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
case 3u:
return uvec4(u64data[offset / 8u], 0u, 0u);
case 4u:
return u128data[offset / 16u];
}
return uvec4(0u);
}
void main() {
uvec3 coord = gl_GlobalInvocationID;
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
return;
}
uvec3 pos = coord + pc.origin;
pos.x <<= pc.bytes_per_block_log2;
uint swizzle = SwizzleOffset(pos.xy);
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
uint offset = 0u;
offset += pos.z * pc.layer_stride;
offset += (block_y >> pc.block_height) * pc.block_size;
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
offset += swizzle;
uvec4 texel = ReadTexel(offset);
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
uint out_idx = linear_index * words;
out_u32[out_idx] = texel.x;
if (words > 1u) {
out_u32[out_idx + 1u] = texel.y;
}
if (words > 2u) {
out_u32[out_idx + 2u] = texel.z;
out_u32[out_idx + 3u] = texel.w;
}
}
@@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 430
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_BUFFER 1
layout(push_constant) uniform PushConstants {
uvec3 dim;
uint bytes_per_block_log2;
uvec3 origin;
uint slice_size;
uint block_size;
uint x_shift;
uint block_height;
uint block_height_mask;
uint block_depth;
uint block_depth_mask;
} pc;
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
uint out_u32[];
};
layout(local_size_x = 8, local_size_y = 8, local_size_z = 4) in;
const uint GOB_SIZE_X = 64;
const uint GOB_SIZE_Y = 8;
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
uint SwizzleOffset(uvec2 pos) {
pos = pos & SWIZZLE_MASK;
return SwizzleTable(pos.y * 64u + pos.x);
}
uvec4 ReadTexel(uint offset) {
switch (pc.bytes_per_block_log2) {
case 2u:
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
case 3u:
return uvec4(u64data[offset / 8u], 0u, 0u);
case 4u:
return u128data[offset / 16u];
}
return uvec4(0u);
}
void main() {
uvec3 coord = gl_GlobalInvocationID;
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
return;
}
uvec3 pos = coord + pc.origin;
pos.x <<= pc.bytes_per_block_log2;
uint swizzle = SwizzleOffset(pos.xy);
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
uint offset = 0u;
offset += (pos.z >> pc.block_depth) * pc.slice_size;
offset += (pos.z & pc.block_depth_mask) << (GOB_SIZE_SHIFT + pc.block_height);
offset += (block_y >> pc.block_height) * pc.block_size;
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
offset += swizzle;
uvec4 texel = ReadTexel(offset);
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
uint out_idx = linear_index * words;
out_u32[out_idx] = texel.x;
if (words > 1u) {
out_u32[out_idx + 1u] = texel.y;
}
if (words > 2u) {
out_u32[out_idx + 2u] = texel.z;
out_u32[out_idx + 3u] = texel.w;
}
}
@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 450 core
layout(binding = 0) uniform sampler2D img_in;
layout(push_constant) uniform PushConstants {
ivec2 dst_offset;
ivec2 src_offset;
ivec2 scale;
};
void main() {
const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset;
const ivec2 sample_offset = ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x);
const ivec2 coord = msaa_coord * scale + sample_offset + src_offset;
gl_FragDepth = texelFetch(img_in, coord, 0).r;
}
@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 450 core
#extension GL_ARB_shader_stencil_export : require
layout(binding = 0) uniform sampler2D depth_tex;
layout(binding = 1) uniform usampler2D stencil_tex;
layout(push_constant) uniform PushConstants {
ivec2 dst_offset;
ivec2 src_offset;
ivec2 scale;
};
void main() {
const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset;
const ivec2 sample_offset = ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x);
const ivec2 coord = msaa_coord * scale + sample_offset + src_offset;
gl_FragDepth = texelFetch(depth_tex, coord, 0).r;
gl_FragStencilRefARB = int(texelFetch(stencil_tex, coord, 0).r);
}
@@ -7,6 +7,7 @@ layout(push_constant) uniform constants {
vec2 scale;
vec2 size;
vec2 resize_factor;
vec2 crop_offset;
float edge_sharpness;
};
layout(location = 0) out highp vec2 texcoord;
@@ -15,5 +16,5 @@ void main() {
float x = float((gl_VertexIndex & 1) << 2);
float y = float((gl_VertexIndex & 2) << 1);
gl_Position = vec4(x - 1.0f, y - 1.0f, 0.0, 1.0f) * vec4(sign(resize_factor), 1.f, 1.f);
texcoord = vec2(x, y) * abs(resize_factor) * 0.5;
texcoord = crop_offset + vec2(x, y) * abs(resize_factor) * 0.5;
}
@@ -14,6 +14,7 @@ layout(push_constant) uniform constants {
vec2 scale;
vec2 size;
vec2 resize_factor;
vec2 crop_offset;
float edge_sharpness;
};
layout(set = 0, binding = 0) uniform sampler2D sampler0;
@@ -13,6 +13,7 @@
layout( push_constant ) uniform constants {
vec4 ViewportInfo[1];
vec2 ResizeFactor;
vec2 CropOffset;
float EdgeSharpness;
};
layout(set = 0, binding = 0) uniform sampler2D ps0;
@@ -72,6 +72,25 @@ Shader::OutputTopology MaxwellToOutputTopology(Maxwell::PrimitiveTopology topolo
}
}
Shader::InputTopology MaxwellToInputTopology(Maxwell::PrimitiveTopology topology) {
switch (topology) {
case Maxwell::PrimitiveTopology::Points:
return Shader::InputTopology::Points;
case Maxwell::PrimitiveTopology::Lines:
case Maxwell::PrimitiveTopology::LineLoop:
case Maxwell::PrimitiveTopology::LineStrip:
return Shader::InputTopology::Lines;
case Maxwell::PrimitiveTopology::LinesAdjacency:
case Maxwell::PrimitiveTopology::LineStripAdjacency:
return Shader::InputTopology::LinesAdjacency;
case Maxwell::PrimitiveTopology::TrianglesAdjacency:
case Maxwell::PrimitiveTopology::TriangleStripAdjacency:
return Shader::InputTopology::TrianglesAdjacency;
default:
return Shader::InputTopology::Triangles;
}
}
Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key,
const Shader::IR::Program& program,
const Shader::IR::Program* previous_program,
@@ -127,33 +146,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key,
default:
break;
}
switch (key.gs_input_topology) {
case Maxwell::PrimitiveTopology::Points:
info.input_topology = Shader::InputTopology::Points;
break;
case Maxwell::PrimitiveTopology::Lines:
case Maxwell::PrimitiveTopology::LineLoop:
case Maxwell::PrimitiveTopology::LineStrip:
info.input_topology = Shader::InputTopology::Lines;
break;
case Maxwell::PrimitiveTopology::Triangles:
case Maxwell::PrimitiveTopology::TriangleStrip:
case Maxwell::PrimitiveTopology::TriangleFan:
case Maxwell::PrimitiveTopology::Quads:
case Maxwell::PrimitiveTopology::QuadStrip:
case Maxwell::PrimitiveTopology::Polygon:
case Maxwell::PrimitiveTopology::Patches:
info.input_topology = Shader::InputTopology::Triangles;
break;
case Maxwell::PrimitiveTopology::LinesAdjacency:
case Maxwell::PrimitiveTopology::LineStripAdjacency:
info.input_topology = Shader::InputTopology::LinesAdjacency;
break;
case Maxwell::PrimitiveTopology::TrianglesAdjacency:
case Maxwell::PrimitiveTopology::TriangleStripAdjacency:
info.input_topology = Shader::InputTopology::TrianglesAdjacency;
break;
}
info.input_topology = MaxwellToInputTopology(key.gs_input_topology);
info.glasm_use_storage_buffers = glasm_use_storage_buffers;
return info;
}
@@ -483,8 +476,10 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
&& index == u32(Maxwell::ShaderType::Geometry);
if (key.unique_hashes[index] == 0 && is_emulated_stage) {
auto topology = MaxwellToOutputTopology(key.gs_input_topology);
programs[index] = GenerateGeometryPassthrough(pools.inst, pools.block, host_info,
*layer_source_program, topology);
programs[index] =
GenerateGeometryPassthrough(pools.inst, pools.block, host_info,
*layer_source_program, topology,
MaxwellToInputTopology(key.gs_input_topology));
continue;
}
if (key.unique_hashes[index] == 0) {
@@ -502,13 +497,15 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
if (!uses_vertex_a || index != 1) {
// Normal path
programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);
programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info,
MaxwellToInputTopology(key.gs_input_topology));
total_storage_buffers += Shader::NumDescriptors(programs[index].info.storage_buffers_descriptors);
} else {
// VertexB path when VertexA is present.
auto& program_va{programs[0]};
auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info,
MaxwellToInputTopology(key.gs_input_topology))};
total_storage_buffers += Shader::NumDescriptors(program_vb.info.storage_buffers_descriptors);
programs[index] = MergeDualVertexPrograms(program_va, program_vb, env);
}
@@ -597,7 +594,8 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
env.Dump(hash, key.unique_hash);
}
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info,
Shader::InputTopology::Points)};
const u32 num_storage_buffers{Shader::NumDescriptors(program.info.storage_buffers_descriptors)};
Shader::RuntimeInfo info;
info.glasm_use_storage_buffers = num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks();
+293 -13
View File
@@ -22,6 +22,8 @@
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
#include "video_core/host_shaders/convert_msaa_to_non_msaa_frag_spv.h"
#include "video_core/host_shaders/convert_non_msaa_to_msaa_frag_spv.h"
#include "video_core/host_shaders/convert_non_msaa_to_msaa_depth_frag_spv.h"
#include "video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil_frag_spv.h"
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
#include "video_core/host_shaders/full_screen_triangle_vert_spv.h"
#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
@@ -519,7 +521,8 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view)
}
[[nodiscard]] vk::ImageView MakeMSAACopyView(const vk::Device& device, VkImage image,
VkFormat format, u32 base_level) {
VkFormat format, u32 base_level,
VkImageAspectFlags aspect_mask) {
return device.CreateImageView(VkImageViewCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
@@ -534,7 +537,7 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view)
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
},
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.aspectMask = aspect_mask,
.baseMipLevel = base_level,
.levelCount = 1,
.baseArrayLayer = 0,
@@ -586,6 +589,10 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
msaa_copy_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
one_texture_set_layout.address(),
PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(MSAACopyPushConstants)>))),
msaa_copy_depth_stencil_pipeline_layout(
device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
two_textures_set_layout.address(),
PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(MSAACopyPushConstants)>))),
full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)),
blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)),
blit_color_msaa_frag(BuildShader(device, BLIT_COLOR_MSAA_FRAG_SPV)),
@@ -610,6 +617,12 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
convert_msaa_to_non_msaa_frag(BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_FRAG_SPV)),
convert_non_msaa_to_msaa_frag(BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_FRAG_SPV)),
convert_non_msaa_to_msaa_depth_frag(
BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_DEPTH_FRAG_SPV)),
convert_non_msaa_to_msaa_depth_stencil_frag(
device.IsExtShaderStencilExportSupported()
? BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_DEPTH_STENCIL_FRAG_SPV)
: vk::ShaderModule{}),
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
@@ -920,10 +933,12 @@ void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_i
ASSERT(copy.dst_subresource.num_layers == 1);
vk::ImageView src_view =
MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format,
static_cast<u32>(copy.src_subresource.base_level));
static_cast<u32>(copy.src_subresource.base_level),
VK_IMAGE_ASPECT_COLOR_BIT);
vk::ImageView dst_view =
MakeMSAACopyView(device.GetLogical(), dst_image, dst_vk_format,
static_cast<u32>(copy.dst_subresource.base_level));
static_cast<u32>(copy.dst_subresource.base_level),
VK_IMAGE_ASPECT_COLOR_BIT);
const VkOffset2D dst_offset{copy.dst_offset.x, copy.dst_offset.y};
const VkExtent2D dst_extent{copy.extent.width, copy.extent.height};
const VkRect2D render_area{
@@ -1191,7 +1206,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
}));
}, device.StaticPipelineCache()));
return *blit_color_pipelines.back();
}
@@ -1223,7 +1238,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
}));
}, device.StaticPipelineCache()));
return *blit_depth_stencil_pipelines.back();
}
@@ -1276,7 +1291,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
}));
}, device.StaticPipelineCache()));
return *clear_color_pipelines.back();
}
@@ -1332,7 +1347,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
}));
}, device.StaticPipelineCache()));
return *clear_stencil_pipelines.back();
}
@@ -1375,7 +1390,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPip
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
}));
}, device.StaticPipelineCache()));
return *blit_msaa_color_pipelines.back();
}
@@ -1413,10 +1428,202 @@ VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPas
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
}));
}, device.StaticPipelineCache()));
return *pipelines.back();
}
void BlitImageHelper::CopyMSAADepth(RenderPassCache& render_pass_cache, VkImage dst_image,
VideoCore::Surface::PixelFormat dst_format, VkImage src_image,
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
std::span<const VideoCommon::ImageCopy> copies,
bool copy_stencil) {
while (!msaa_copy_resources.empty() && scheduler.IsFree(msaa_copy_resources.front().tick)) {
msaa_copy_resources.pop_front();
}
const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast<int>(num_samples));
const s32 scale_x = 1 << samples_x;
const s32 scale_y = 1 << samples_y;
const VkSampleCountFlagBits samples = SampleCountFlag(num_samples);
RenderPassKey renderpass_key{};
renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid);
renderpass_key.depth_format = dst_format;
renderpass_key.samples = samples;
const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key);
const MSAACopyPipelineKey key{
.renderpass = renderpass,
.samples = samples,
.msaa_to_non_msaa = false,
};
const VkPipeline pipeline = FindOrEmplaceMSAACopyDepthPipeline(key, copy_stencil);
const VkPipelineLayout layout = copy_stencil ? *msaa_copy_depth_stencil_pipeline_layout
: *msaa_copy_pipeline_layout;
const VkSampler sampler = *nearest_sampler;
const VkFormat src_vk_format =
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src_format).format;
const VkFormat dst_vk_format =
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, dst_format).format;
const VkImageAspectFlags attachment_aspect =
VideoCore::Surface::GetFormatType(dst_format) ==
VideoCore::Surface::SurfaceType::DepthStencil
? VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT
: VK_IMAGE_ASPECT_DEPTH_BIT;
for (const VideoCommon::ImageCopy& copy : copies) {
ASSERT(copy.src_subresource.base_layer == 0);
ASSERT(copy.src_subresource.num_layers == 1);
ASSERT(copy.dst_subresource.base_layer == 0);
ASSERT(copy.dst_subresource.num_layers == 1);
vk::ImageView src_view =
MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format,
static_cast<u32>(copy.src_subresource.base_level),
VK_IMAGE_ASPECT_DEPTH_BIT);
vk::ImageView src_stencil_view =
copy_stencil ? MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format,
static_cast<u32>(copy.src_subresource.base_level),
VK_IMAGE_ASPECT_STENCIL_BIT)
: vk::ImageView{};
vk::ImageView dst_view =
MakeMSAACopyView(device.GetLogical(), dst_image, dst_vk_format,
static_cast<u32>(copy.dst_subresource.base_level),
attachment_aspect);
const VkOffset2D dst_offset{copy.dst_offset.x, copy.dst_offset.y};
const VkExtent2D dst_extent{copy.extent.width, copy.extent.height};
const VkRect2D render_area{
.offset = dst_offset,
.extent = dst_extent,
};
vk::Framebuffer framebuffer = device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.renderPass = renderpass,
.attachmentCount = 1,
.pAttachments = dst_view.address(),
.width = static_cast<u32>(dst_offset.x) + dst_extent.width,
.height = static_cast<u32>(dst_offset.y) + dst_extent.height,
.layers = 1,
});
const MSAACopyPushConstants push_constants{
.dst_offset = {dst_offset.x, dst_offset.y},
.src_offset = {copy.src_offset.x, copy.src_offset.y},
.scale = {scale_x, scale_y},
};
scheduler.RequestOutsideRenderPassOperationContext();
const VkImageView src_stencil_handle = copy_stencil ? *src_stencil_view : VK_NULL_HANDLE;
scheduler.Record([this, pipeline, layout, sampler, renderpass,
framebuffer_handle = *framebuffer, src_view_handle = *src_view,
src_stencil_handle, src = src_image, dst = dst_image, render_area,
attachment_aspect, push_constants](vk::CommandBuffer cmdbuf) {
const VkImageSubresourceRange src_range{
.aspectMask = attachment_aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
};
const std::array pre_barriers{
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = src,
.subresourceRange = src_range,
},
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst,
.subresourceRange = src_range,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
0, nullptr, nullptr, pre_barriers);
const VkRenderPassBeginInfo renderpass_bi{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.pNext = nullptr,
.renderPass = renderpass,
.framebuffer = framebuffer_handle,
.renderArea = render_area,
.clearValueCount = 0,
.pClearValues = nullptr,
};
cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
const VkDescriptorSet descriptor_set =
src_stencil_handle != VK_NULL_HANDLE
? two_textures_descriptor_allocator.Commit()
: one_texture_descriptor_allocator.Commit();
if (src_stencil_handle != VK_NULL_HANDLE) {
UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_view_handle,
src_stencil_handle);
} else {
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view_handle);
}
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
nullptr);
const VkViewport viewport{
.x = static_cast<float>(render_area.offset.x),
.y = static_cast<float>(render_area.offset.y),
.width = static_cast<float>(render_area.extent.width),
.height = static_cast<float>(render_area.extent.height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
cmdbuf.SetViewport(0, viewport);
cmdbuf.SetScissor(0, render_area);
cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, push_constants);
cmdbuf.Draw(3, 1, 0, 0);
cmdbuf.EndRenderPass();
const VkImageMemoryBarrier post_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst,
.subresourceRange = src_range,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, post_barrier);
});
msaa_copy_resources.push_back(MSAACopyResources{
.tick = scheduler.CurrentTick(),
.src_view = std::move(src_view),
.dst_view = std::move(dst_view),
.framebuffer = std::move(framebuffer),
});
if (copy_stencil) {
msaa_copy_resources.push_back(MSAACopyResources{
.tick = scheduler.CurrentTick(),
.src_view = std::move(src_stencil_view),
.dst_view = vk::ImageView{},
.framebuffer = vk::Framebuffer{},
});
}
}
}
VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key) {
const auto it = std::ranges::find(msaa_copy_keys, key);
if (it != msaa_copy_keys.end()) {
@@ -1458,10 +1665,83 @@ VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipeline
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
}));
}, device.StaticPipelineCache()));
return *msaa_copy_pipelines.back();
}
VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyDepthPipeline(const MSAACopyPipelineKey& key,
bool copy_stencil) {
auto& keys = copy_stencil ? msaa_copy_depth_stencil_keys : msaa_copy_depth_keys;
auto& pipelines = copy_stencil ? msaa_copy_depth_stencil_pipelines : msaa_copy_depth_pipelines;
const auto it = std::ranges::find(keys, key);
if (it != keys.end()) {
return *pipelines[std::distance(keys.begin(), it)];
}
keys.push_back(key);
const std::array stages =
MakeStages(*clear_color_vert, copy_stencil ? *convert_non_msaa_to_msaa_depth_stencil_frag
: *convert_non_msaa_to_msaa_depth_frag);
const VkPipelineMultisampleStateCreateInfo multisample_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.rasterizationSamples = key.samples,
.sampleShadingEnable = VK_TRUE,
.minSampleShading = 1.0f,
.pSampleMask = nullptr,
.alphaToCoverageEnable = VK_FALSE,
.alphaToOneEnable = VK_FALSE,
};
static constexpr VkStencilOpState REPLACE_STENCIL_OP{
.failOp = VK_STENCIL_OP_REPLACE,
.passOp = VK_STENCIL_OP_REPLACE,
.depthFailOp = VK_STENCIL_OP_REPLACE,
.compareOp = VK_COMPARE_OP_ALWAYS,
.compareMask = 0xFF,
.writeMask = 0xFF,
.reference = 0,
};
const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.depthTestEnable = VK_TRUE,
.depthWriteEnable = VK_TRUE,
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
.depthBoundsTestEnable = VK_FALSE,
.stencilTestEnable = copy_stencil ? VK_TRUE : VK_FALSE,
.front = copy_stencil ? REPLACE_STENCIL_OP : VkStencilOpState{},
.back = copy_stencil ? REPLACE_STENCIL_OP : VkStencilOpState{},
.minDepthBounds = 0.0f,
.maxDepthBounds = 0.0f,
};
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci =
GetPipelineInputAssemblyStateCreateInfo(device);
pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stageCount = static_cast<u32>(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pMultisampleState = &multisample_ci,
.pDepthStencilState = &depth_stencil_ci,
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.layout = copy_stencil ? *msaa_copy_depth_stencil_pipeline_layout
: *msaa_copy_pipeline_layout,
.renderPass = key.renderpass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
}, device.StaticPipelineCache()));
return *pipelines.back();
}
void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
ConvertPipeline(pipeline, renderpass, false);
}
@@ -1499,7 +1779,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
});
}, device.StaticPipelineCache());
}
void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
@@ -1542,7 +1822,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
});
}, device.StaticPipelineCache());
}
} // namespace Vulkan
@@ -116,6 +116,11 @@ public:
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
std::span<const VideoCommon::ImageCopy> copies, bool msaa_to_non_msaa);
void CopyMSAADepth(RenderPassCache& render_pass_cache, VkImage dst_image,
VideoCore::Surface::PixelFormat dst_format, VkImage src_image,
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
std::span<const VideoCommon::ImageCopy> copies, bool copy_stencil);
private:
void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
const ImageView& src_image_view);
@@ -131,6 +136,9 @@ private:
[[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline(
const BlitDepthStencilPipelineKey& key);
[[nodiscard]] VkPipeline FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key);
[[nodiscard]] VkPipeline FindOrEmplaceMSAACopyDepthPipeline(const MSAACopyPipelineKey& key,
bool copy_stencil);
[[nodiscard]] VkPipeline FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPipelineKey& key);
[[nodiscard]] VkPipeline FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass,
bool resolve_stencil);
@@ -162,6 +170,7 @@ private:
vk::PipelineLayout two_textures_pipeline_layout;
vk::PipelineLayout clear_color_pipeline_layout;
vk::PipelineLayout msaa_copy_pipeline_layout;
vk::PipelineLayout msaa_copy_depth_stencil_pipeline_layout;
vk::ShaderModule full_screen_vert;
vk::ShaderModule blit_color_to_color_frag;
vk::ShaderModule blit_color_msaa_frag;
@@ -180,6 +189,8 @@ private:
vk::ShaderModule convert_s8d24_to_abgr8_frag;
vk::ShaderModule convert_msaa_to_non_msaa_frag;
vk::ShaderModule convert_non_msaa_to_msaa_frag;
vk::ShaderModule convert_non_msaa_to_msaa_depth_frag;
vk::ShaderModule convert_non_msaa_to_msaa_depth_stencil_frag;
vk::Sampler linear_sampler;
vk::Sampler nearest_sampler;
@@ -193,6 +204,10 @@ private:
std::vector<vk::Pipeline> clear_stencil_pipelines;
std::vector<MSAACopyPipelineKey> msaa_copy_keys;
std::vector<vk::Pipeline> msaa_copy_pipelines;
std::vector<MSAACopyPipelineKey> msaa_copy_depth_keys;
std::vector<vk::Pipeline> msaa_copy_depth_pipelines;
std::vector<MSAACopyPipelineKey> msaa_copy_depth_stencil_keys;
std::vector<vk::Pipeline> msaa_copy_depth_stencil_pipelines;
std::vector<BlitMSAAPipelineKey> blit_msaa_color_keys;
std::vector<vk::Pipeline> blit_msaa_color_pipelines;
std::vector<VkRenderPass> resolve_depth_keys;
+18 -14
View File
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include "common/common_types.h"
#include "common/div_ceil.h"
#include "common/settings.h"
@@ -17,7 +19,7 @@
namespace Vulkan {
using PushConstants = std::array<u32, 4 + 2 + 1>;
using PushConstants = std::array<u32, 4 + 2 + 2 + 1>;
SGSR::SGSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent, bool edge_dir)
: m_memory_allocator{memory_allocator}
@@ -100,26 +102,28 @@ VkImageView SGSR::Draw(const Device& device, Scheduler& scheduler, size_t image_
const f32 input_image_width = f32(input_image_extent.width);
const f32 input_image_height = f32(input_image_extent.height);
const f32 viewport_width = (crop_rect.right - crop_rect.left) * input_image_width;
const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
// expected [0, 2]
const f32 sharpening = f32(Settings::values.fsr_sharpening_slider.GetValue()) / 100.0f;
const f32 crop_width = (crop_rect.right - crop_rect.left) * input_image_width;
const f32 crop_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
static constexpr f32 EDGE_SHARPNESS_MAX = 2.0f;
const f32 edge_sharpness =
EDGE_SHARPNESS_MAX - f32(Settings::values.fsr_sharpening_slider.GetValue()) / 200.0f;
// p = (tex * viewport) / input = [0,n] (normalized texcoords)
// p * input = [0,1024], [0,768]
// layout( push_constant ) uniform constants {
// highp vec4 ViewportInfo[1];
// highp vec2 ResizeFactor;
// highp vec2 CropOffset;
// highp float EdgeSharpness;
// };
PushConstants viewport_con{};
viewport_con[0] = std::bit_cast<u32>(std::abs(1.f / viewport_width));
viewport_con[1] = std::bit_cast<u32>(std::abs(1.f / viewport_height));
viewport_con[2] = std::bit_cast<u32>(std::abs(viewport_width));
viewport_con[3] = std::bit_cast<u32>(std::abs(viewport_height));
viewport_con[4] = std::bit_cast<u32>(viewport_width / input_image_width);
viewport_con[5] = std::bit_cast<u32>(viewport_height / input_image_height);
viewport_con[6] = std::bit_cast<u32>(sharpening);
viewport_con[0] = std::bit_cast<u32>(1.f / input_image_width);
viewport_con[1] = std::bit_cast<u32>(1.f / input_image_height);
viewport_con[2] = std::bit_cast<u32>(input_image_width);
viewport_con[3] = std::bit_cast<u32>(input_image_height);
viewport_con[4] = std::bit_cast<u32>(crop_width / input_image_width);
viewport_con[5] = std::bit_cast<u32>(crop_height / input_image_height);
viewport_con[6] = std::bit_cast<u32>((std::min)(crop_rect.left, crop_rect.right));
viewport_con[7] = std::bit_cast<u32>((std::min)(crop_rect.top, crop_rect.bottom));
viewport_con[8] = std::bit_cast<u32>(edge_sharpness);
UploadImages(device, scheduler);
UpdateDescriptorSets(device, source_image_view, image_index);
@@ -491,7 +491,7 @@ static vk::Pipeline CreateWrappedPipelineImpl(
.subpass = 0,
.basePipelineHandle = 0,
.basePipelineIndex = 0,
});
}, device.StaticPipelineCache());
}
vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
@@ -22,7 +22,9 @@
#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_2d_buffer_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_buffer_comp_spv.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/surface.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
@@ -268,7 +270,7 @@ ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, Descriptor
.layout = *layout,
.basePipelineHandle = {},
.basePipelineIndex = 0,
});
}, device.StaticPipelineCache());
}
ComputePass::~ComputePass() = default;
@@ -872,4 +874,479 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
});
}
namespace {
constexpr u32 BL2D_BINDING_INPUT_BUFFER = 0;
constexpr u32 BL2D_BINDING_OUTPUT_BUFFER = 1;
struct alignas(16) BlockLinearUnswizzle2DPushConstants {
std::array<u32, 3> dim;
u32 bytes_per_block_log2;
std::array<u32, 3> origin;
u32 layer_stride;
u32 block_size;
u32 x_shift;
u32 block_height;
u32 block_height_mask;
};
static_assert(sizeof(BlockLinearUnswizzle2DPushConstants) <= 128);
constexpr std::array<VkDescriptorSetLayoutBinding, 2> BL2D_BINDINGS{{
{
.binding = BL2D_BINDING_INPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
{
.binding = BL2D_BINDING_OUTPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
}};
constexpr std::array<VkDescriptorUpdateTemplateEntry, 2> BL2D_TEMPLATE{{
{
.dstBinding = BL2D_BINDING_INPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL2D_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
{
.dstBinding = BL2D_BINDING_OUTPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL2D_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
}};
constexpr DescriptorBankInfo BL2D_BANK_INFO{
.uniform_buffers = 0,
.storage_buffers = 2,
.texture_buffers = 0,
.image_buffers = 0,
.textures = 0,
.images = 0,
.score = 2,
};
} // Anonymous namespace
BlockLinearUnswizzle2DPass::BlockLinearUnswizzle2DPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, BL2D_BINDINGS, BL2D_TEMPLATE,
BL2D_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle2DPushConstants)>,
BLOCK_LINEAR_UNSWIZZLE_2D_BUFFER_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
BlockLinearUnswizzle2DPass::~BlockLinearUnswizzle2DPass() = default;
bool BlockLinearUnswizzle2DPass::IsSupported(const VideoCommon::ImageInfo& info) {
if (info.type != VideoCommon::ImageType::e2D) {
return false;
}
if (info.resources.levels != 1 || info.resources.layers != 1) {
return false;
}
if (info.num_samples > 1) {
return false;
}
if (VideoCore::Surface::IsPixelFormatASTC(info.format) ||
VideoCore::Surface::IsPixelFormatBCn(info.format)) {
return false;
}
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
if (bytes_per_block != 4 && bytes_per_block != 8 && bytes_per_block != 16) {
return false;
}
return VideoCore::Surface::DefaultBlockWidth(info.format) == 1 &&
VideoCore::Surface::DefaultBlockHeight(info.format) == 1;
}
void BlockLinearUnswizzle2DPass::Unswizzle(
Image& image, const StagingBufferRef& swizzled,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
if (swizzles.empty()) {
return;
}
const VideoCommon::SwizzleParameters& sw = swizzles.front();
const auto params = VideoCommon::Accelerated::MakeBlockLinearSwizzle2DParams(sw, image.info);
const u32 width = sw.num_tiles.width;
const u32 height = sw.num_tiles.height;
const u32 depth = image.info.resources.layers;
const u32 bytes_per_block = 1u << params.bytes_per_block_log2;
const VkDeviceSize output_size =
static_cast<VkDeviceSize>(width) * height * depth * bytes_per_block;
const StagingBufferRef output =
staging_buffer_pool.Request(static_cast<size_t>(output_size), MemoryUsage::DeviceLocal);
BlockLinearUnswizzle2DPushConstants pc{};
pc.dim = {width, height, depth};
pc.bytes_per_block_log2 = params.bytes_per_block_log2;
pc.origin = params.origin;
pc.layer_stride = params.layer_stride;
pc.block_size = params.block_size;
pc.x_shift = params.x_shift;
pc.block_height = params.block_height;
pc.block_height_mask = params.block_height_mask;
scheduler.RequestOutsideRenderPassOperationContext();
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(swizzled.buffer, sw.buffer_offset + swizzled.offset,
image.guest_size_bytes - sw.buffer_offset);
compute_pass_descriptor_queue.AddBuffer(output.buffer, output.offset, output_size);
const void* descriptor_data = compute_pass_descriptor_queue.UpdateData();
const VkDescriptorSet set = descriptor_allocator.Commit();
const u32 gx = Common::DivCeil(width, 16u);
const u32 gy = Common::DivCeil(height, 8u);
const bool is_initialized = image.ExchangeInitialization();
const VkBuffer out_buffer = output.buffer;
const VkDeviceSize out_offset = output.offset;
const VkImage dst_image = image.Handle();
const VkImageAspectFlags aspect = image.AspectMask();
scheduler.Record([this, set, descriptor_data, pc, gx, gy, depth, output_size, out_buffer,
out_offset, dst_image, aspect, width, height,
is_initialized](vk::CommandBuffer cmdbuf) {
if (dst_image == VK_NULL_HANDLE || out_buffer == VK_NULL_HANDLE) {
return;
}
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc);
cmdbuf.Dispatch(gx, gy, depth);
const VkBufferMemoryBarrier buffer_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = out_buffer,
.offset = out_offset,
.size = output_size,
};
const VkImageMemoryBarrier pre_copy{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = static_cast<VkAccessFlags>(is_initialized ? VK_ACCESS_SHADER_READ_BIT
: VK_ACCESS_NONE),
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE
: VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT),
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, buffer_barrier, pre_copy);
const VkBufferImageCopy copy{
.bufferOffset = out_offset,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource{
.aspectMask = aspect,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = depth,
},
.imageOffset = {0, 0, 0},
.imageExtent = {width, height, 1},
};
cmdbuf.CopyBufferToImage(out_buffer, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
const VkImageMemoryBarrier post_copy{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
0, {}, {}, post_copy);
});
}
namespace {
constexpr u32 BL3DB_BINDING_INPUT_BUFFER = 0;
constexpr u32 BL3DB_BINDING_OUTPUT_BUFFER = 1;
struct alignas(16) BlockLinearUnswizzle3DBufferPushConstants {
std::array<u32, 3> dim;
u32 bytes_per_block_log2;
std::array<u32, 3> origin;
u32 slice_size;
u32 block_size;
u32 x_shift;
u32 block_height;
u32 block_height_mask;
u32 block_depth;
u32 block_depth_mask;
};
static_assert(sizeof(BlockLinearUnswizzle3DBufferPushConstants) <= 128);
constexpr std::array<VkDescriptorSetLayoutBinding, 2> BL3DB_BINDINGS{{
{
.binding = BL3DB_BINDING_INPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
{
.binding = BL3DB_BINDING_OUTPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
}};
constexpr std::array<VkDescriptorUpdateTemplateEntry, 2> BL3DB_TEMPLATE{{
{
.dstBinding = BL3DB_BINDING_INPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL3DB_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
{
.dstBinding = BL3DB_BINDING_OUTPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL3DB_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
}};
constexpr DescriptorBankInfo BL3DB_BANK_INFO{
.uniform_buffers = 0,
.storage_buffers = 2,
.texture_buffers = 0,
.image_buffers = 0,
.textures = 0,
.images = 0,
.score = 2,
};
} // Anonymous namespace
BlockLinearUnswizzle3DBufferPass::BlockLinearUnswizzle3DBufferPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, BL3DB_BINDINGS, BL3DB_TEMPLATE,
BL3DB_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle3DBufferPushConstants)>,
BLOCK_LINEAR_UNSWIZZLE_3D_BUFFER_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
BlockLinearUnswizzle3DBufferPass::~BlockLinearUnswizzle3DBufferPass() = default;
bool BlockLinearUnswizzle3DBufferPass::IsSupported(const Device& device,
const VideoCommon::ImageInfo& info) {
if (info.type != VideoCommon::ImageType::e3D) {
return false;
}
if (info.resources.levels != 1 || info.resources.layers != 1) {
return false;
}
if (info.num_samples > 1) {
return false;
}
if (info.size.depth <= 1) {
return false;
}
if (VideoCore::Surface::IsPixelFormatASTC(info.format)) {
return false;
}
if (VideoCore::Surface::IsPixelFormatBCn(info.format) && !device.IsOptimalBcnSupported()) {
return false;
}
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
return bytes_per_block == 4 || bytes_per_block == 8 || bytes_per_block == 16;
}
void BlockLinearUnswizzle3DBufferPass::Unswizzle(
Image& image, const StagingBufferRef& swizzled,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
if (swizzles.empty()) {
return;
}
const VideoCommon::SwizzleParameters& sw = swizzles.front();
const auto params = VideoCommon::Accelerated::MakeBlockLinearSwizzle3DParams(sw, image.info);
const u32 blocks_x = sw.num_tiles.width;
const u32 blocks_y = sw.num_tiles.height;
const u32 blocks_z = sw.num_tiles.depth;
const u32 bytes_per_block = 1u << params.bytes_per_block_log2;
const VkDeviceSize output_size =
static_cast<VkDeviceSize>(blocks_x) * blocks_y * blocks_z * bytes_per_block;
const StagingBufferRef output =
staging_buffer_pool.Request(static_cast<size_t>(output_size), MemoryUsage::DeviceLocal);
BlockLinearUnswizzle3DBufferPushConstants pc{};
pc.dim = {blocks_x, blocks_y, blocks_z};
pc.bytes_per_block_log2 = params.bytes_per_block_log2;
pc.origin = params.origin;
pc.slice_size = params.slice_size;
pc.block_size = params.block_size;
pc.x_shift = params.x_shift;
pc.block_height = params.block_height;
pc.block_height_mask = params.block_height_mask;
pc.block_depth = params.block_depth;
pc.block_depth_mask = params.block_depth_mask;
scheduler.RequestOutsideRenderPassOperationContext();
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(swizzled.buffer, sw.buffer_offset + swizzled.offset,
image.guest_size_bytes - sw.buffer_offset);
compute_pass_descriptor_queue.AddBuffer(output.buffer, output.offset, output_size);
const void* descriptor_data = compute_pass_descriptor_queue.UpdateData();
const VkDescriptorSet set = descriptor_allocator.Commit();
const u32 gx = Common::DivCeil(blocks_x, 8u);
const u32 gy = Common::DivCeil(blocks_y, 8u);
const u32 gz = Common::DivCeil(blocks_z, 4u);
const bool is_initialized = image.ExchangeInitialization();
const VkBuffer out_buffer = output.buffer;
const VkDeviceSize out_offset = output.offset;
const VkImage dst_image = image.Handle();
const VkImageAspectFlags aspect = image.AspectMask();
const VkExtent3D extent{
.width = image.info.size.width,
.height = image.info.size.height,
.depth = image.info.size.depth,
};
scheduler.Record([this, set, descriptor_data, pc, gx, gy, gz, output_size, out_buffer,
out_offset, dst_image, aspect, extent,
is_initialized](vk::CommandBuffer cmdbuf) {
if (dst_image == VK_NULL_HANDLE || out_buffer == VK_NULL_HANDLE) {
return;
}
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc);
cmdbuf.Dispatch(gx, gy, gz);
const VkBufferMemoryBarrier buffer_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = out_buffer,
.offset = out_offset,
.size = output_size,
};
const VkImageMemoryBarrier pre_copy{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = static_cast<VkAccessFlags>(is_initialized ? VK_ACCESS_SHADER_READ_BIT
: VK_ACCESS_NONE),
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE
: VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT),
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, buffer_barrier, pre_copy);
const VkBufferImageCopy copy{
.bufferOffset = out_offset,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource{
.aspectMask = aspect,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.imageOffset = {0, 0, 0},
.imageExtent = extent,
};
cmdbuf.CopyBufferToImage(out_buffer, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
const VkImageMemoryBarrier post_copy{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
0, {}, {}, post_copy);
});
}
} // namespace Vulkan
@@ -164,4 +164,42 @@ private:
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class BlockLinearUnswizzle2DPass final : public ComputePass {
public:
explicit BlockLinearUnswizzle2DPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearUnswizzle2DPass();
[[nodiscard]] static bool IsSupported(const VideoCommon::ImageInfo& info);
void Unswizzle(Image& image, const StagingBufferRef& swizzled,
std::span<const VideoCommon::SwizzleParameters> swizzles);
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class BlockLinearUnswizzle3DBufferPass final : public ComputePass {
public:
explicit BlockLinearUnswizzle3DBufferPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearUnswizzle3DBufferPass();
[[nodiscard]] static bool IsSupported(const Device& device, const VideoCommon::ImageInfo& info);
void Unswizzle(Image& image, const StagingBufferRef& swizzled,
std::span<const VideoCommon::SwizzleParameters> swizzles);
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
} // namespace Vulkan
@@ -140,6 +140,8 @@ RenderPassKey MakeRenderPassKey(const FixedPipelineState& state, const Device& d
});
key.resolve_color =
key.samples != VK_SAMPLE_COUNT_1_BIT && has_color && device.IsTiler();
key.resolve_depth_stencil = key.samples != VK_SAMPLE_COUNT_1_BIT && device.IsTiler() &&
SupportsDepthStencilResolve(device, key.depth_format);
return key;
}
@@ -63,6 +63,8 @@ using VideoCommon::GenericEnvironment;
using VideoCommon::GraphicsEnvironment;
constexpr u32 CACHE_VERSION = 18;
constexpr size_t VULKAN_CACHE_FLUSH_PIPELINES = 128;
constexpr size_t VULKAN_CACHE_FLUSH_MIN_SECONDS = 30;
constexpr std::array<char, 8> VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'};
template <typename Container>
@@ -150,6 +152,25 @@ Shader::AttributeType AttributeType(const FixedPipelineState& state, size_t inde
return Shader::AttributeType::Disabled;
}
Shader::InputTopology MaxwellToInputTopology(Maxwell::PrimitiveTopology topology) {
switch (topology) {
case Maxwell::PrimitiveTopology::Points:
return Shader::InputTopology::Points;
case Maxwell::PrimitiveTopology::Lines:
case Maxwell::PrimitiveTopology::LineLoop:
case Maxwell::PrimitiveTopology::LineStrip:
return Shader::InputTopology::Lines;
case Maxwell::PrimitiveTopology::LinesAdjacency:
case Maxwell::PrimitiveTopology::LineStripAdjacency:
return Shader::InputTopology::LinesAdjacency;
case Maxwell::PrimitiveTopology::TrianglesAdjacency:
case Maxwell::PrimitiveTopology::TriangleStripAdjacency:
return Shader::InputTopology::TrianglesAdjacency;
default:
return Shader::InputTopology::Triangles;
}
}
Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> programs,
const GraphicsPipelineCacheKey& key,
const Shader::IR::Program& program,
@@ -268,33 +289,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
default:
break;
}
switch (key.state.topology) {
case Maxwell::PrimitiveTopology::Points:
info.input_topology = Shader::InputTopology::Points;
break;
case Maxwell::PrimitiveTopology::Lines:
case Maxwell::PrimitiveTopology::LineLoop:
case Maxwell::PrimitiveTopology::LineStrip:
info.input_topology = Shader::InputTopology::Lines;
break;
case Maxwell::PrimitiveTopology::Triangles:
case Maxwell::PrimitiveTopology::TriangleStrip:
case Maxwell::PrimitiveTopology::TriangleFan:
case Maxwell::PrimitiveTopology::Quads:
case Maxwell::PrimitiveTopology::QuadStrip:
case Maxwell::PrimitiveTopology::Polygon:
case Maxwell::PrimitiveTopology::Patches:
info.input_topology = Shader::InputTopology::Triangles;
break;
case Maxwell::PrimitiveTopology::LinesAdjacency:
case Maxwell::PrimitiveTopology::LineStripAdjacency:
info.input_topology = Shader::InputTopology::LinesAdjacency;
break;
case Maxwell::PrimitiveTopology::TrianglesAdjacency:
case Maxwell::PrimitiveTopology::TriangleStripAdjacency:
info.input_topology = Shader::InputTopology::TrianglesAdjacency;
break;
}
info.input_topology = MaxwellToInputTopology(key.state.topology);
info.force_early_z = key.state.early_z != 0;
info.y_negate = key.state.y_negate != 0;
return info;
@@ -699,6 +694,10 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
if (use_vulkan_pipeline_cache) {
SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache,
CACHE_VERSION);
size_t size = 0;
vulkan_pipeline_cache.Read(&size, nullptr);
last_cache_size.store(size, std::memory_order_relaxed);
last_flush = std::chrono::steady_clock::now();
}
if (state.statistics) {
@@ -706,6 +705,35 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
}
}
void PipelineCache::QueueVulkanPipelineCacheFlush() {
if (!use_vulkan_pipeline_cache || vulkan_pipeline_cache_filename.empty()) {
return;
}
if (++pipelines_since_flush < VULKAN_CACHE_FLUSH_PIPELINES) {
return;
}
const auto now = std::chrono::steady_clock::now();
const auto megabytes = last_cache_size.load(std::memory_order_relaxed) / (1024 * 1024);
const std::chrono::seconds interval{
std::max<size_t>(VULKAN_CACHE_FLUSH_MIN_SECONDS, megabytes)};
if (last_flush.time_since_epoch().count() != 0 && now - last_flush < interval) {
return;
}
if (flush_in_flight.exchange(true, std::memory_order_acq_rel)) {
return;
}
pipelines_since_flush = 0;
last_flush = now;
serialization_thread.QueueWork([this] {
SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache,
CACHE_VERSION);
size_t size = 0;
vulkan_pipeline_cache.Read(&size, nullptr);
last_cache_size.store(size, std::memory_order_relaxed);
flush_in_flight.store(false, std::memory_order_release);
});
}
GraphicsPipeline* PipelineCache::CurrentGraphicsPipelineSlowPath() {
const auto [pair, is_new]{graphics_cache.try_emplace(graphics_key)};
auto& pipeline{pair->second};
@@ -744,7 +772,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
std::span<Shader::Environment* const> envs, PipelineStatistics* statistics,
bool build_in_parallel) try {
auto hash = key.Hash();
LOG_INFO(Render_Vulkan, "{:#016x}", hash);
LOG_DEBUG(Render_Vulkan, "{:#016x}", hash);
size_t env_index{0};
std::array<Shader::IR::Program, Maxwell::MaxShaderProgram> programs;
const bool uses_vertex_a{key.unique_hashes[0] != 0};
@@ -758,8 +786,10 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
index == static_cast<u32>(Maxwell::ShaderType::Geometry);
if (key.unique_hashes[index] == 0 && is_emulated_stage) {
auto topology = MaxwellToOutputTopology(key.state.topology);
programs[index] = GenerateGeometryPassthrough(pools.inst, pools.block, host_info,
*layer_source_program, topology);
programs[index] =
GenerateGeometryPassthrough(pools.inst, pools.block, host_info,
*layer_source_program, topology,
MaxwellToInputTopology(key.state.topology));
continue;
}
if (key.unique_hashes[index] == 0) {
@@ -772,11 +802,13 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
if (!uses_vertex_a || index != 1) {
// Normal path
programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);
programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info,
MaxwellToInputTopology(key.state.topology));
} else {
// VertexB path when VertexA is present.
auto& program_va{programs[0]};
auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info,
MaxwellToInputTopology(key.state.topology))};
programs[index] = MergeDualVertexPrograms(program_va, program_vb, env);
}
@@ -880,6 +912,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
}
SerializePipeline(key, env_ptrs, pipeline_cache_filename, CACHE_VERSION);
});
QueueVulkanPipelineCacheFlush();
return pipeline;
}
@@ -899,6 +932,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
SerializePipeline(key, std::array<const GenericEnvironment*, 1>{&env_},
pipeline_cache_filename, CACHE_VERSION);
});
QueueVulkanPipelineCacheFlush();
return pipeline;
}
@@ -911,7 +945,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
return nullptr;
}
LOG_INFO(Render_Vulkan, "{:#016x}", hash);
LOG_DEBUG(Render_Vulkan, "{:#016x}", hash);
Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()};
@@ -920,7 +954,8 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
env.Dump(hash, key.unique_hash);
}
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info,
Shader::InputTopology::Points)};
const VkDriverIdKHR driver_id = device.GetDriverID();
const bool needs_shared_mem_clamp =
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
@@ -7,6 +7,8 @@
#pragma once
#include <array>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <filesystem>
#include <memory>
@@ -144,6 +146,8 @@ private:
vk::PipelineCache LoadVulkanPipelineCache(const std::filesystem::path& filename,
u32 expected_cache_version);
void QueueVulkanPipelineCacheFlush();
const Device& device;
Scheduler& scheduler;
DescriptorPool& descriptor_pool;
@@ -171,6 +175,10 @@ private:
std::filesystem::path vulkan_pipeline_cache_filename;
vk::PipelineCache vulkan_pipeline_cache;
size_t pipelines_since_flush{};
std::chrono::steady_clock::time_point last_flush{};
std::atomic<size_t> last_cache_size{};
std::atomic_bool flush_in_flight{};
Common::ThreadWorker workers;
Common::ThreadWorker serialization_thread;
@@ -725,6 +725,9 @@ public:
if (!device.IsExtTransformFeedbackSupported()) {
return;
}
if (!scheduler.IsRenderPassActive()) {
return;
}
FlushBeginTFB();
has_started = true;
}
@@ -741,6 +744,9 @@ public:
if (has_flushed_end_pending) {
if (scheduler.IsRenderPassActive()) {
FlushEndTFB();
} else {
has_flushed_end_pending = false;
has_started = false;
}
}
runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
@@ -1286,33 +1286,42 @@ void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs) {
regs.zeta.format == Tegra::DepthFormat::X8Z24_UNORM ||
regs.zeta.format == Tegra::DepthFormat::S8Z24_UNORM ||
regs.zeta.format == Tegra::DepthFormat::V8Z24_UNORM;
const bool forces_unorm_representation = device.IsExtDepthBiasControlSupported();
if (is_d24 && !device.SupportsD24DepthBuffer()) {
static constexpr const size_t length = sizeof(NEEDS_D24) / sizeof(NEEDS_D24[0]);
static constexpr double GUEST_TO_HOST_UNORM_BITS =
static_cast<double>(1ULL << (32 - 24));
static constexpr const u64* start = NEEDS_D24;
static constexpr const u64* end = NEEDS_D24 + length;
if (forces_unorm_representation) {
units = static_cast<float>(static_cast<double>(units) * GUEST_TO_HOST_UNORM_BITS);
} else {
static constexpr const size_t length = sizeof(NEEDS_D24) / sizeof(NEEDS_D24[0]);
const u64* it = std::find(start, end, program_id);
static constexpr const u64* start = NEEDS_D24;
static constexpr const u64* end = NEEDS_D24 + length;
if (it != end) {
// the base formulas can be obtained from here:
// https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias
const double rescale_factor =
static_cast<double>(1ULL << (32 - 24)) / (static_cast<double>(0x1.ep+127));
units = static_cast<float>(static_cast<double>(units) * rescale_factor);
const u64* it = std::find(start, end, program_id);
if (it != end) {
// the base formulas can be obtained from here:
// https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias
const double rescale_factor =
GUEST_TO_HOST_UNORM_BITS / (static_cast<double>(0x1.ep+127));
units = static_cast<float>(static_cast<double>(units) * rescale_factor);
}
}
}
scheduler.Record([constant = units, clamp = regs.depth_bias_clamp,
factor = regs.slope_scale_depth_bias, this](vk::CommandBuffer cmdbuf) {
if (device.IsExtDepthBiasControlSupported()) {
static VkDepthBiasRepresentationInfoEXT bias_info{
factor = regs.slope_scale_depth_bias,
forces_unorm_representation, this](vk::CommandBuffer cmdbuf) {
if (forces_unorm_representation) {
const VkDepthBiasRepresentationInfoEXT bias_info{
.sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_REPRESENTATION_INFO_EXT,
.pNext = nullptr,
.depthBiasRepresentation =
VK_DEPTH_BIAS_REPRESENTATION_LEAST_REPRESENTABLE_VALUE_FORCE_UNORM_EXT,
.depthBiasExact = VK_FALSE,
.depthBiasExact = static_cast<VkBool32>(device.HasExactDepthBiasControl()),
};
cmdbuf.SetDepthBias(constant, clamp, factor, &bias_info);
@@ -1336,8 +1345,14 @@ void RasterizerVulkan::UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs)
if (!state_tracker.TouchDepthBounds()) {
return;
}
scheduler.Record([min = regs.depth_bounds[0], max = regs.depth_bounds[1]](
vk::CommandBuffer cmdbuf) { cmdbuf.SetDepthBounds(min, max); });
float min = regs.depth_bounds[0];
float max = regs.depth_bounds[1];
if (!device.IsExtDepthRangeUnrestrictedSupported()) {
min = std::clamp(min, 0.0f, 1.0f);
max = std::clamp(max, 0.0f, 1.0f);
}
scheduler.Record(
[min, max](vk::CommandBuffer cmdbuf) { cmdbuf.SetDepthBounds(min, max); });
}
void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs) {
@@ -65,8 +65,61 @@ using VideoCore::Surface::SurfaceType;
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
};
}
struct ResolveAspects {
bool depth;
bool stencil;
};
struct ResolveModes {
VkResolveModeFlagBits depth;
VkResolveModeFlagBits stencil;
};
constexpr ResolveAspects GetResolveAspects(PixelFormat format) {
const SurfaceType surface_type = GetSurfaceType(format);
return ResolveAspects{
.depth = surface_type == SurfaceType::Depth ||
surface_type == SurfaceType::DepthStencil,
.stencil = surface_type == SurfaceType::Stencil ||
surface_type == SurfaceType::DepthStencil,
};
}
ResolveModes PickResolveModes(const Device& device, PixelFormat format) {
constexpr VkResolveModeFlagBits mode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
const ResolveAspects aspects = GetResolveAspects(format);
ResolveModes modes{
.depth = VK_RESOLVE_MODE_NONE,
.stencil = VK_RESOLVE_MODE_NONE,
};
if (aspects.depth && (device.GetDepthResolveModes() & mode) != 0) {
modes.depth = mode;
}
if (aspects.stencil && (device.GetStencilResolveModes() & mode) != 0) {
modes.stencil = mode;
}
return modes;
}
} // Anonymous namespace
bool SupportsDepthStencilResolve(const Device& device, PixelFormat depth_format) {
if (depth_format == PixelFormat::Invalid || !device.IsKhrDepthStencilResolveSupported()) {
return false;
}
const ResolveAspects aspects = GetResolveAspects(depth_format);
if (!aspects.depth && !aspects.stencil) {
return false;
}
const ResolveModes modes = PickResolveModes(device, depth_format);
if ((aspects.depth && modes.depth == VK_RESOLVE_MODE_NONE) ||
(aspects.stencil && modes.stencil == VK_RESOLVE_MODE_NONE)) {
return false;
}
return modes.depth == modes.stencil || device.SupportsIndependentResolveNone();
}
RenderPassCache::RenderPassCache(const Device& device_) : device{&device_} {}
VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
@@ -75,7 +128,9 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
if (!is_new) {
return *pair->second;
}
boost::container::static_vector<VkAttachmentDescription, 9> descriptions;
static constexpr size_t MAX_ATTACHMENTS =
2 * std::tuple_size_v<decltype(RenderPassKey::color_formats)> + 2;
boost::container::static_vector<VkAttachmentDescription, MAX_ATTACHMENTS> descriptions;
std::array<VkAttachmentReference, 8> references{};
u32 num_attachments{};
u32 num_colors{};
@@ -133,6 +188,21 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
}
}
}
const bool do_resolve_depth_stencil = key.resolve_depth_stencil && has_depth &&
key.samples != VK_SAMPLE_COUNT_1_BIT &&
SupportsDepthStencilResolve(*device, key.depth_format);
VkAttachmentReference depth_resolve_reference{};
if (do_resolve_depth_stencil) {
depth_resolve_reference = VkAttachmentReference{
.attachment = static_cast<u32>(descriptions.size()),
.layout = VK_IMAGE_LAYOUT_GENERAL,
};
VkAttachmentDescription resolve_desc =
AttachmentDescription(*device, key.depth_format, VK_SAMPLE_COUNT_1_BIT,
VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE);
resolve_desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
descriptions.push_back(resolve_desc);
}
const VkSubpassDescription subpass{
.flags = 0,
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
@@ -145,18 +215,117 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
.preserveAttachmentCount = 0,
.pPreserveAttachments = nullptr,
};
const VkSubpassDependency dependency{
.srcSubpass = 0, // Current subpass
.dstSubpass = 0, // Same subpass (self-dependency)
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT
const VkSubpassDependency counter_resume_dependency{
.srcSubpass = 0,
.dstSubpass = 0,
.srcStageMask = VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
.dstStageMask = VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
.srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT,
.dstAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT,
.dependencyFlags = 0,
};
const bool can_resume_transform_feedback = device->IsExtTransformFeedbackSupported();
if (device->IsKhrCreateRenderPass2Supported()) {
boost::container::static_vector<VkAttachmentDescription2, MAX_ATTACHMENTS> descriptions2;
for (const VkAttachmentDescription& description : descriptions) {
descriptions2.push_back(VkAttachmentDescription2{
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2,
.pNext = nullptr,
.flags = description.flags,
.format = description.format,
.samples = description.samples,
.loadOp = description.loadOp,
.storeOp = description.storeOp,
.stencilLoadOp = description.stencilLoadOp,
.stencilStoreOp = description.stencilStoreOp,
.initialLayout = description.initialLayout,
.finalLayout = description.finalLayout,
});
}
const auto promote = [](const VkAttachmentReference& reference) {
return VkAttachmentReference2{
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
.pNext = nullptr,
.attachment = reference.attachment,
.layout = reference.layout,
.aspectMask = 0,
};
};
std::array<VkAttachmentReference2, 8> references2{};
std::array<VkAttachmentReference2, 8> resolve_references2{};
for (size_t index = 0; index < references.size(); ++index) {
references2[index] = promote(references[index]);
resolve_references2[index] = promote(resolve_references[index]);
}
const VkAttachmentReference2 depth_reference2 = promote(depth_reference);
const VkAttachmentReference2 depth_resolve_reference2 = promote(depth_resolve_reference);
const ResolveModes resolve_modes = PickResolveModes(*device, key.depth_format);
const VkSubpassDescriptionDepthStencilResolve depth_stencil_resolve{
.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE,
.pNext = nullptr,
.depthResolveMode = resolve_modes.depth,
.stencilResolveMode = resolve_modes.stencil,
.pDepthStencilResolveAttachment = &depth_resolve_reference2,
};
const VkSubpassDescription2 subpass2{
.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2,
.pNext = do_resolve_depth_stencil ? &depth_stencil_resolve : nullptr,
.flags = 0,
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.viewMask = 0,
.inputAttachmentCount = 0,
.pInputAttachments = nullptr,
.colorAttachmentCount = num_attachments,
.pColorAttachments = references2.data(),
.pResolveAttachments = do_resolve_color ? resolve_references2.data() : nullptr,
.pDepthStencilAttachment = has_depth ? &depth_reference2 : nullptr,
.preserveAttachmentCount = 0,
.pPreserveAttachments = nullptr,
};
const VkMemoryBarrier2 counter_resume_barrier{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
.pNext = nullptr,
.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFORM_FEEDBACK_BIT_EXT,
.srcAccessMask = VK_ACCESS_2_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT,
.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFORM_FEEDBACK_BIT_EXT,
.dstAccessMask = VK_ACCESS_2_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT,
};
VkSubpassDependency2 counter_resume_dependency2{
.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2,
.pNext = nullptr,
.srcSubpass = counter_resume_dependency.srcSubpass,
.dstSubpass = counter_resume_dependency.dstSubpass,
.srcStageMask = counter_resume_dependency.srcStageMask,
.dstStageMask = counter_resume_dependency.dstStageMask,
.srcAccessMask = counter_resume_dependency.srcAccessMask,
.dstAccessMask = counter_resume_dependency.dstAccessMask,
.dependencyFlags = counter_resume_dependency.dependencyFlags,
.viewOffset = 0,
};
if (device->HasSynchronization2()) {
counter_resume_dependency2.pNext = &counter_resume_barrier;
counter_resume_dependency2.srcStageMask = 0;
counter_resume_dependency2.dstStageMask = 0;
counter_resume_dependency2.srcAccessMask = 0;
counter_resume_dependency2.dstAccessMask = 0;
}
pair->second = device->GetLogical().CreateRenderPass2({
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2,
.pNext = nullptr,
.flags = 0,
.attachmentCount = static_cast<u32>(descriptions2.size()),
.pAttachments = descriptions2.empty() ? nullptr : descriptions2.data(),
.subpassCount = 1,
.pSubpasses = &subpass2,
.dependencyCount = can_resume_transform_feedback ? 1u : 0u,
.pDependencies = can_resume_transform_feedback ? &counter_resume_dependency2 : nullptr,
.correlatedViewMaskCount = 0,
.pCorrelatedViewMasks = nullptr,
});
return *pair->second;
}
pair->second = device->GetLogical().CreateRenderPass({
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.pNext = nullptr,
@@ -165,8 +334,8 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
.pAttachments = descriptions.empty() ? nullptr : descriptions.data(),
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = &dependency,
.dependencyCount = can_resume_transform_feedback ? 1u : 0u,
.pDependencies = can_resume_transform_feedback ? &counter_resume_dependency : nullptr,
});
return *pair->second;
}
@@ -9,6 +9,7 @@
#include <mutex>
#include <ankerl/unordered_dense.h>
#include "common/container_hash.h"
#include "video_core/surface.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -21,6 +22,7 @@ struct RenderPassKey {
VideoCore::Surface::PixelFormat depth_format;
VkSampleCountFlagBits samples;
bool resolve_color;
bool resolve_depth_stencil;
u32 color_clear_mask;
bool depth_stencil_clear;
u32 color_discard_mask;
@@ -31,17 +33,27 @@ struct RenderPassKey {
namespace std {
template <>
struct hash<Vulkan::RenderPassKey> {
static_assert(std::tuple_size_v<decltype(Vulkan::RenderPassKey::color_formats)> <= 8);
static_assert(static_cast<u32>(VideoCore::Surface::PixelFormat::Invalid) <= 0xFF);
static_assert(static_cast<u32>(VideoCore::Surface::PixelFormat::Max) <= 0xFF);
static_assert(VK_SAMPLE_COUNT_64_BIT <= 0xFF);
[[nodiscard]] size_t operator()(const Vulkan::RenderPassKey& key) const noexcept {
size_t value = static_cast<size_t>(key.depth_format) << 48;
value ^= static_cast<size_t>(key.samples) << 52;
value ^= static_cast<size_t>(key.resolve_color) << 63;
value ^= static_cast<size_t>(key.color_clear_mask) << 54;
value ^= static_cast<size_t>(key.depth_stencil_clear) << 62;
value ^= static_cast<size_t>(key.color_discard_mask) << 24;
for (size_t i = 0; i < key.color_formats.size(); ++i) {
value ^= static_cast<size_t>(key.color_formats[i]) << (i * 6);
u64 formats = 0;
for (size_t index = 0; index < key.color_formats.size(); ++index) {
formats |= static_cast<u64>(key.color_formats[index]) << (index * 8);
}
return value;
const u64 state = static_cast<u64>(key.depth_format) |
(static_cast<u64>(key.samples) << 8) |
(static_cast<u64>(key.color_clear_mask) << 16) |
(static_cast<u64>(key.color_discard_mask) << 24) |
(static_cast<u64>(key.resolve_color) << 32) |
(static_cast<u64>(key.depth_stencil_clear) << 33) |
(static_cast<u64>(key.resolve_depth_stencil) << 34);
size_t seed = 0;
Common::HashCombine(seed, formats);
Common::HashCombine(seed, state);
return seed;
}
};
} // namespace std
@@ -50,6 +62,9 @@ namespace Vulkan {
class Device;
[[nodiscard]] bool SupportsDepthStencilResolve(const Device& device,
VideoCore::Surface::PixelFormat depth_format);
class RenderPassCache {
public:
explicit RenderPassCache(const Device& device_);
@@ -410,8 +410,17 @@ void Scheduler::EndRenderPass()
Record([num_images = num_renderpass_images,
images = renderpass_images,
ranges = renderpass_image_ranges,
consumer_stages = device.AttachmentConsumerStages(),
has_transform_feedback = device.IsExtTransformFeedbackSupported()](
vk::CommandBuffer cmdbuf) {
static constexpr VkAccessFlags SHADER_ACCESS =
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
static constexpr VkAccessFlags COLOR_ACCESS =
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
static constexpr VkAccessFlags DEPTH_STENCIL_ACCESS =
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
std::array<VkImageMemoryBarrier, 9> barriers;
for (size_t i = 0; i < num_images; ++i) {
const VkImageSubresourceRange& range = ranges[i];
@@ -421,24 +430,25 @@ void Scheduler::EndRenderPass()
| VK_IMAGE_ASPECT_STENCIL_BIT)) !=0;
VkAccessFlags src_access = 0;
VkAccessFlags dst_access = SHADER_ACCESS;
if (is_color)
if (is_color) {
src_access |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
else if (is_depth_stencil)
dst_access |= COLOR_ACCESS;
} else if (is_depth_stencil) {
src_access |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
else
dst_access |= DEPTH_STENCIL_ACCESS;
} else {
src_access |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
dst_access |= COLOR_ACCESS | DEPTH_STENCIL_ACCESS;
}
barriers[i] = VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = src_access,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.dstAccessMask = dst_access,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
@@ -449,7 +459,7 @@ void Scheduler::EndRenderPass()
}
cmdbuf.EndRenderPass();
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, consumer_stages,
0, nullptr, nullptr, vk::Span(barriers.data(), num_images));
if (has_transform_feedback) {
static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{
@@ -628,18 +628,12 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
.subresourceRange = subresource_range,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
read_barrier);
cmdbuf.CopyBufferToImage(src_buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copies);
// TODO: Move this to another API
cmdbuf.PipelineBarrier(
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, nullptr, nullptr, write_barrier);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0,
nullptr, nullptr, write_barrier);
}
[[nodiscard]] VkImageBlit MakeImageBlit(const Region2D& dst_region, const Region2D& src_region,
@@ -933,6 +927,10 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue);
}
bl2d_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
compute_pass_descriptor_queue);
bl3db_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
compute_pass_descriptor_queue);
}
void TextureCacheRuntime::Finish() {
@@ -995,13 +993,25 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) {
}
VkImageView TextureCacheRuntime::GetOrCreateResolveShadow(VkImage msaa_image, VkFormat format,
VkExtent2D extent, u32 layers) {
VkExtent2D extent, u32 layers,
VkImageAspectFlags aspect_mask) {
ResolveShadow& shadow = resolve_shadows[msaa_image];
if (shadow.image && shadow.format == format && shadow.extent.width == extent.width &&
shadow.extent.height == extent.height && shadow.layers == layers) {
shadow.extent.height == extent.height && shadow.layers == layers &&
shadow.aspect_mask == aspect_mask) {
shadow.up_to_date = true;
return *shadow.view;
}
VkImageUsageFlags shadow_usage =
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
shadow_usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
} else {
shadow_usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
}
if (shadow.image) {
pending_resolve_shadows.emplace_back(scheduler.CurrentTick(), std::move(shadow));
}
shadow.image = memory_allocator.CreateImage(VkImageCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
@@ -1013,8 +1023,7 @@ VkImageView TextureCacheRuntime::GetOrCreateResolveShadow(VkImage msaa_image, Vk
.arrayLayers = layers,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
.usage = shadow_usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
@@ -1029,7 +1038,7 @@ VkImageView TextureCacheRuntime::GetOrCreateResolveShadow(VkImage msaa_image, Vk
.format = format,
.components{},
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
@@ -1039,6 +1048,7 @@ VkImageView TextureCacheRuntime::GetOrCreateResolveShadow(VkImage msaa_image, Vk
shadow.format = format;
shadow.extent = extent;
shadow.layers = layers;
shadow.aspect_mask = aspect_mask;
shadow.up_to_date = true;
return *shadow.view;
}
@@ -1060,7 +1070,14 @@ void TextureCacheRuntime::InvalidateResolveShadow(VkImage msaa_image) {
}
void TextureCacheRuntime::EraseResolveShadow(VkImage msaa_image) {
resolve_shadows.erase(msaa_image);
const auto it = resolve_shadows.find(msaa_image);
if (it == resolve_shadows.end()) {
return;
}
if (it->second.image) {
pending_resolve_shadows.emplace_back(scheduler.CurrentTick(), std::move(it->second));
}
resolve_shadows.erase(it);
}
void TextureCacheRuntime::BarrierFeedbackLoop() {
@@ -1195,7 +1212,8 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, READ_BARRIER, {}, middle_out_barrier);
cmdbuf.CopyBufferToImage(copy_buffer, dst_image, VK_IMAGE_LAYOUT_GENERAL, vk_out_copies);
cmdbuf.CopyBufferToImage(copy_buffer, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
vk_out_copies);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
0, {}, {}, post_barriers);
});
@@ -1357,133 +1375,83 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
}
switch (dst_view.format) {
case PixelFormat::D24_UNORM_S8_UINT:
if (src_view.format == PixelFormat::A8B8G8R8_UNORM
|| src_view.format == PixelFormat::B8G8R8A8_UNORM
|| src_view.format == PixelFormat::A8B8G8R8_SRGB
|| src_view.format == PixelFormat::B8G8R8A8_SRGB) {
return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view);
case PixelFormat::R16_UNORM:
if (src_view.format == PixelFormat::D16_UNORM) {
return blit_image_helper.ConvertD16ToR16(dst, src_view);
}
break;
case PixelFormat::A8B8G8R8_SRGB:
case PixelFormat::B8G8R8A8_SRGB:
case PixelFormat::B8G8R8A8_UNORM:
if (src_view.format == PixelFormat::D32_FLOAT) {
return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
}
break;
case PixelFormat::A8B8G8R8_UNORM:
case PixelFormat::A8B8G8R8_SNORM:
case PixelFormat::A8B8G8R8_SINT:
case PixelFormat::A8B8G8R8_UINT:
case PixelFormat::R5G6B5_UNORM:
case PixelFormat::B5G6R5_UNORM:
case PixelFormat::A1R5G5B5_UNORM:
case PixelFormat::A2B10G10R10_UNORM:
case PixelFormat::A2B10G10R10_UINT:
case PixelFormat::A2R10G10B10_UNORM:
case PixelFormat::A1B5G5R5_UNORM:
case PixelFormat::A5B5G5R1_UNORM:
case PixelFormat::R8_UNORM:
case PixelFormat::R8_SNORM:
case PixelFormat::R8_SINT:
case PixelFormat::R8_UINT:
case PixelFormat::R16G16B16A16_FLOAT:
case PixelFormat::R16G16B16A16_UNORM:
case PixelFormat::R16G16B16A16_SNORM:
case PixelFormat::R16G16B16A16_SINT:
case PixelFormat::R16G16B16A16_UINT:
case PixelFormat::B10G11R11_FLOAT:
case PixelFormat::R32G32B32A32_UINT:
case PixelFormat::BC1_RGBA_UNORM:
case PixelFormat::BC2_UNORM:
case PixelFormat::BC3_UNORM:
case PixelFormat::BC4_UNORM:
case PixelFormat::BC4_SNORM:
case PixelFormat::BC5_UNORM:
case PixelFormat::BC5_SNORM:
case PixelFormat::BC7_UNORM:
case PixelFormat::BC6H_UFLOAT:
case PixelFormat::BC6H_SFLOAT:
case PixelFormat::ASTC_2D_4X4_UNORM:
case PixelFormat::B8G8R8A8_UNORM:
case PixelFormat::R32G32B32A32_FLOAT:
case PixelFormat::R32G32B32A32_SINT:
case PixelFormat::R32G32_FLOAT:
case PixelFormat::R32G32_SINT:
case PixelFormat::R32_FLOAT:
if (src_view.format == PixelFormat::D32_FLOAT &&
(dst_view.format == PixelFormat::B5G6R5_UNORM ||
Settings::values.fix_bloom_effects.GetValue())) {
const Region2D region{
.start = {0, 0},
.end = {static_cast<s32>(dst->RenderArea().width),
static_cast<s32>(dst->RenderArea().height)},
};
return blit_image_helper.BlitColor(dst, src_view, region, region,
Tegra::Engines::Fermi2D::Filter::Point,
Tegra::Engines::Fermi2D::Operation::SrcCopy);
if (src_view.format == PixelFormat::S8_UINT_D24_UNORM) {
return blit_image_helper.ConvertD24S8ToABGR8(dst, src_view);
}
if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) {
return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view);
}
if (src_view.format == PixelFormat::D32_FLOAT) {
return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
}
break;
case PixelFormat::R32_FLOAT:
if (src_view.format == PixelFormat::D32_FLOAT) {
return blit_image_helper.ConvertD32ToR32(dst, src_view);
}
break;
case PixelFormat::R16_FLOAT:
case PixelFormat::R16_UNORM:
case PixelFormat::R16_SNORM:
case PixelFormat::R16_UINT:
case PixelFormat::R16_SINT:
case PixelFormat::R16G16_UNORM:
case PixelFormat::R16G16_FLOAT:
case PixelFormat::R16G16_UINT:
case PixelFormat::R16G16_SINT:
case PixelFormat::R16G16_SNORM:
case PixelFormat::R32G32B32_FLOAT:
case PixelFormat::A8B8G8R8_SRGB:
case PixelFormat::R8G8_UNORM:
case PixelFormat::R8G8_SNORM:
case PixelFormat::R8G8_SINT:
case PixelFormat::R8G8_UINT:
case PixelFormat::R32G32_UINT:
case PixelFormat::R16G16B16X16_FLOAT:
case PixelFormat::R32_UINT:
case PixelFormat::R32_SINT:
case PixelFormat::ASTC_2D_8X8_UNORM:
case PixelFormat::ASTC_2D_8X5_UNORM:
case PixelFormat::ASTC_2D_5X4_UNORM:
case PixelFormat::B8G8R8A8_SRGB:
case PixelFormat::BC1_RGBA_SRGB:
case PixelFormat::BC2_SRGB:
case PixelFormat::BC3_SRGB:
case PixelFormat::BC7_SRGB:
case PixelFormat::A4B4G4R4_UNORM:
case PixelFormat::G4R4_UNORM:
case PixelFormat::ASTC_2D_4X4_SRGB:
case PixelFormat::ASTC_2D_8X8_SRGB:
case PixelFormat::ASTC_2D_8X5_SRGB:
case PixelFormat::ASTC_2D_5X4_SRGB:
case PixelFormat::ASTC_2D_5X5_UNORM:
case PixelFormat::ASTC_2D_5X5_SRGB:
case PixelFormat::ASTC_2D_10X8_UNORM:
case PixelFormat::ASTC_2D_10X8_SRGB:
case PixelFormat::ASTC_2D_6X6_UNORM:
case PixelFormat::ASTC_2D_6X6_SRGB:
case PixelFormat::ASTC_2D_10X6_UNORM:
case PixelFormat::ASTC_2D_10X6_SRGB:
case PixelFormat::ASTC_2D_10X5_UNORM:
case PixelFormat::ASTC_2D_10X5_SRGB:
case PixelFormat::ASTC_2D_10X10_UNORM:
case PixelFormat::ASTC_2D_10X10_SRGB:
case PixelFormat::ASTC_2D_12X10_UNORM:
case PixelFormat::ASTC_2D_12X10_SRGB:
case PixelFormat::ASTC_2D_12X12_UNORM:
case PixelFormat::ASTC_2D_12X12_SRGB:
case PixelFormat::ASTC_2D_8X6_UNORM:
case PixelFormat::ASTC_2D_8X6_SRGB:
case PixelFormat::ASTC_2D_6X5_UNORM:
case PixelFormat::ASTC_2D_6X5_SRGB:
case PixelFormat::E5B9G9R9_FLOAT:
case PixelFormat::D32_FLOAT:
case PixelFormat::D16_UNORM:
case PixelFormat::X8_D24_UNORM:
case PixelFormat::S8_UINT:
if (src_view.format == PixelFormat::R16_UNORM) {
return blit_image_helper.ConvertR16ToD16(dst, src_view);
}
break;
case PixelFormat::S8_UINT_D24_UNORM:
case PixelFormat::D32_FLOAT_S8_UINT:
case PixelFormat::Invalid:
if (src_view.format == PixelFormat::A8B8G8R8_UNORM ||
src_view.format == PixelFormat::B8G8R8A8_UNORM) {
return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view);
}
break;
case PixelFormat::D32_FLOAT:
if (src_view.format == PixelFormat::A8B8G8R8_UNORM ||
src_view.format == PixelFormat::B8G8R8A8_UNORM ||
src_view.format == PixelFormat::A8B8G8R8_SRGB ||
src_view.format == PixelFormat::B8G8R8A8_SRGB) {
return blit_image_helper.ConvertABGR8ToD32F(dst, src_view);
}
if (src_view.format == PixelFormat::R32_FLOAT) {
return blit_image_helper.ConvertR32ToD32(dst, src_view);
}
break;
case PixelFormat::D24_UNORM_S8_UINT:
if (src_view.format == PixelFormat::A8B8G8R8_UNORM ||
src_view.format == PixelFormat::B8G8R8A8_UNORM ||
src_view.format == PixelFormat::A8B8G8R8_SRGB ||
src_view.format == PixelFormat::B8G8R8A8_SRGB) {
return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view);
}
break;
default:
LOG_DEBUG(Render_Vulkan, "Unimplemented texture conversion from {} to {} format type", src_view.format, dst_view.format);
break;
}
if (src_view.format == PixelFormat::D32_FLOAT &&
VideoCore::Surface::GetFormatType(dst_view.format) == SurfaceType::ColorTexture &&
(dst_view.format == PixelFormat::B5G6R5_UNORM ||
Settings::values.fix_bloom_effects.GetValue())) {
const Region2D region{
.start = {0, 0},
.end = {static_cast<s32>(dst->RenderArea().width),
static_cast<s32>(dst->RenderArea().height)},
};
return blit_image_helper.BlitColor(dst, src_view, region, region,
Tegra::Engines::Fermi2D::Filter::Point,
Tegra::Engines::Fermi2D::Operation::SrcCopy);
}
LOG_DEBUG(Render_Vulkan, "Unimplemented texture conversion from {} to {} format type", src_view.format, dst_view.format);
}
VkFormat TextureCacheRuntime::GetSupportedFormat(VkFormat requested_format,
@@ -1640,31 +1608,41 @@ void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
std::span<const VideoCommon::ImageCopy> copies) {
const bool msaa_to_non_msaa = src.info.num_samples > 1 && dst.info.num_samples == 1;
const u32 num_samples = msaa_to_non_msaa ? src.info.num_samples : dst.info.num_samples;
if (dst.AspectMask() != VK_IMAGE_ASPECT_COLOR_BIT ||
VideoCore::Surface::IsPixelFormatInteger(dst.info.format)) {
UNIMPLEMENTED_MSG("Copying images with different samples is not supported.");
return;
}
if (ENABLE_MSAA_RESOLVE_CONSUME && msaa_to_non_msaa && copies.size() == 1 &&
src.info.format == dst.info.format) {
const VideoCommon::ImageCopy& copy = copies.front();
const ResolveShadow* const shadow = GetValidResolveShadow(src.Handle());
if (shadow != nullptr && copy.src_offset.x == 0 && copy.src_offset.y == 0 &&
if (shadow != nullptr && shadow->aspect_mask == dst.AspectMask() &&
copy.src_offset.x == 0 && copy.src_offset.y == 0 &&
copy.src_subresource.base_level == 0 &&
static_cast<u32>(copy.extent.width) <= shadow->extent.width &&
static_cast<u32>(copy.extent.height) <= shadow->extent.height) {
static_cast<u32>(copy.extent.height) <= shadow->extent.height &&
static_cast<u32>(copy.src_subresource.base_layer + copy.src_subresource.num_layers) <=
shadow->layers) {
const VkImageAspectFlags aspect_mask = shadow->aspect_mask;
VkPipelineStageFlags attachment_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkAccessFlags attachment_write = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkAccessFlags attachment_read_write =
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
attachment_stage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
attachment_write = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
attachment_read_write = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
}
const VkImage shadow_image = *shadow->image;
const VkImage dst_image = dst.Handle();
const VkImageCopy region{
.srcSubresource{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.aspectMask = aspect_mask,
.mipLevel = 0,
.baseArrayLayer = static_cast<u32>(copy.src_subresource.base_layer),
.layerCount = static_cast<u32>(copy.src_subresource.num_layers),
},
.srcOffset = {0, 0, 0},
.dstSubresource{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.aspectMask = aspect_mask,
.mipLevel = static_cast<u32>(copy.dst_subresource.base_level),
.baseArrayLayer = static_cast<u32>(copy.dst_subresource.base_layer),
.layerCount = static_cast<u32>(copy.dst_subresource.num_layers),
@@ -1673,26 +1651,27 @@ void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
.extent = {copy.extent.width, copy.extent.height, 1},
};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([shadow_image, dst_image, region](vk::CommandBuffer cmdbuf) {
scheduler.Record([shadow_image, dst_image, region, aspect_mask, attachment_stage,
attachment_write,
attachment_read_write](vk::CommandBuffer cmdbuf) {
const std::array pre_barriers{
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.srcAccessMask = attachment_write,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = shadow_image,
.subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0,
.subresourceRange{aspect_mask, 0, VK_REMAINING_MIP_LEVELS, 0,
VK_REMAINING_ARRAY_LAYERS},
},
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | attachment_write |
VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
@@ -1700,7 +1679,7 @@ void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0,
.subresourceRange{aspect_mask, 0, VK_REMAINING_MIP_LEVELS, 0,
VK_REMAINING_ARRAY_LAYERS},
},
};
@@ -1715,28 +1694,25 @@ void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = shadow_image,
.subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0,
.subresourceRange{aspect_mask, 0, VK_REMAINING_MIP_LEVELS, 0,
VK_REMAINING_ARRAY_LAYERS},
},
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | attachment_read_write |
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0,
.subresourceRange{aspect_mask, 0, VK_REMAINING_MIP_LEVELS, 0,
VK_REMAINING_ARRAY_LAYERS},
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT,
cmdbuf.PipelineBarrier(attachment_stage | VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, nullptr, nullptr,
pre_barriers);
cmdbuf.CopyImage(shadow_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image,
@@ -1748,6 +1724,11 @@ void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
return;
}
}
if (dst.AspectMask() != VK_IMAGE_ASPECT_COLOR_BIT ||
VideoCore::Surface::IsPixelFormatInteger(dst.info.format)) {
UNIMPLEMENTED_MSG("Copying images with different samples is not supported.");
return;
}
blit_image_helper.CopyMSAA(render_pass_cache, dst.Handle(), dst.info.format, src.Handle(),
src.info.format, num_samples, copies, msaa_to_non_msaa);
}
@@ -1772,6 +1753,9 @@ void TextureCacheRuntime::TickFrame() {
std::erase_if(pending_msaa_images, [this](const auto& pending) {
return scheduler.IsFree(pending.first);
});
std::erase_if(pending_resolve_shadows, [this](const auto& pending) {
return scheduler.IsFree(pending.first);
});
}
Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
@@ -1796,6 +1780,13 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
}
flags |= VideoCommon::ImageFlagBits::Converted;
flags |= VideoCommon::ImageFlagBits::CostlyLoad;
} else if (runtime->bl2d_unswizzle_pass && BlockLinearUnswizzle2DPass::IsSupported(info)) {
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
flags |= VideoCommon::ImageFlagBits::CostlyLoad;
} else if (runtime->bl3db_unswizzle_pass &&
BlockLinearUnswizzle3DBufferPass::IsSupported(runtime->device, info)) {
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
flags |= VideoCommon::ImageFlagBits::CostlyLoad;
}
if (IsPixelFormatBCn(info.format) && !runtime->device.IsOptimalBcnSupported()) {
flags |= VideoCommon::ImageFlagBits::Converted;
@@ -1882,11 +1873,21 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
ScaleDown(true);
}
const bool msaa_upload_is_depth = (aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0;
const bool wants_msaa_upload = info.num_samples > 1
&& (aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0
&& ((aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0 || msaa_upload_is_depth)
&& !VideoCore::Surface::IsPixelFormatInteger(info.format);
if (wants_msaa_upload) {
const bool msaa_upload_copies_stencil =
msaa_upload_is_depth && (aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0 &&
runtime->device.IsExtShaderStencilExportSupported();
const VkImageAspectFlags upload_aspect_mask =
msaa_upload_is_depth
? (msaa_upload_copies_stencil
? VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT
: VK_IMAGE_ASPECT_DEPTH_BIT)
: aspect_mask;
ImageInfo temp_info = info;
temp_info.num_samples = 1;
@@ -1898,10 +1899,10 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
vk::Image temp_image = runtime->memory_allocator.CreateImage(image_ci);
scheduler->RequestOutsideRenderPassOperationContext();
auto vk_copies = TransformBufferImageCopies(copies, offset, aspect_mask);
auto vk_copies = TransformBufferImageCopies(copies, offset, upload_aspect_mask);
const VkBuffer src_buffer = buffer;
const VkImage temp_vk_image = *temp_image;
const VkImageAspectFlags vk_aspect_mask = aspect_mask;
const VkImageAspectFlags vk_aspect_mask = upload_aspect_mask;
scheduler->Record([src_buffer, temp_vk_image, vk_aspect_mask,
vk_copies](vk::CommandBuffer cmdbuf) {
@@ -1923,9 +1924,16 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
image_copies.push_back(image_copy);
}
runtime->blit_image_helper.CopyMSAA(runtime->render_pass_cache, Handle(), info.format,
temp_vk_image, info.format, info.num_samples,
image_copies, false);
if (msaa_upload_is_depth) {
runtime->blit_image_helper.CopyMSAADepth(runtime->render_pass_cache, Handle(),
info.format, temp_vk_image, info.format,
info.num_samples, image_copies,
msaa_upload_copies_stencil);
} else {
runtime->blit_image_helper.CopyMSAA(runtime->render_pass_cache, Handle(), info.format,
temp_vk_image, info.format, info.num_samples,
image_copies, false);
}
initialized = true;
runtime->pending_msaa_images.emplace_back(scheduler->CurrentTick(), std::move(temp_image));
@@ -2690,7 +2698,7 @@ Framebuffer::~Framebuffer() = default;
void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
std::span<ImageView*, NUM_RT> color_buffers,
ImageView* depth_buffer, bool is_rescaled_) {
boost::container::small_vector<VkImageView, NUM_RT + 1> attachments;
boost::container::small_vector<VkImageView, NUM_RT * 2 + 2> attachments;
RenderPassKey renderpass_key{};
s32 num_layers = 1;
@@ -2719,6 +2727,8 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
++num_images;
}
const size_t num_colors = attachments.size();
VkImage depth_image = VK_NULL_HANDLE;
VkImageAspectFlags depth_aspect_mask = 0;
if (depth_buffer) {
width = (std::min)(width, is_rescaled ? resolution.ScaleUp(depth_buffer->size.width)
: depth_buffer->size.width);
@@ -2734,6 +2744,8 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
++num_images;
has_depth = (subresource_range.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0;
has_stencil = (subresource_range.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0;
depth_image = depth_buffer->ImageHandle();
depth_aspect_mask = subresource_range.aspectMask;
} else {
renderpass_key.depth_format = PixelFormat::Invalid;
}
@@ -2742,6 +2754,12 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
samples != VK_SAMPLE_COUNT_1_BIT && num_colors > 0 && runtime.device.IsTiler();
renderpass_key.resolve_color = do_resolve_color;
const bool do_resolve_depth_stencil =
samples != VK_SAMPLE_COUNT_1_BIT && depth_image != VK_NULL_HANDLE &&
runtime.device.IsTiler() &&
SupportsDepthStencilResolve(runtime.device, renderpass_key.depth_format);
renderpass_key.resolve_depth_stencil = do_resolve_depth_stencil;
discard_msaa_color =
ENABLE_MSAA_RESOLVE_CONSUME && ENABLE_MSAA_COLOR_DISCARD && do_resolve_color;
@@ -2762,8 +2780,8 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
MaxwellToVK::SurfaceFormat(runtime.device, FormatType::Optimal, true, format).format;
if (ENABLE_MSAA_RESOLVE_CONSUME) {
const VkImage msaa_image = images[rt_map[index]];
attachments.push_back(runtime.GetOrCreateResolveShadow(msaa_image, vk_format,
render_area, layers));
attachments.push_back(runtime.GetOrCreateResolveShadow(
msaa_image, vk_format, render_area, layers, VK_IMAGE_ASPECT_COLOR_BIT));
continue;
}
VkImageCreateInfo resolve_ci{
@@ -2808,6 +2826,16 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
}
}
if (do_resolve_depth_stencil) {
const u32 layers = static_cast<u32>((std::max)(num_layers, 1));
const VkFormat vk_format =
MaxwellToVK::SurfaceFormat(runtime.device, FormatType::Optimal, true,
renderpass_key.depth_format)
.format;
attachments.push_back(runtime.GetOrCreateResolveShadow(depth_image, vk_format, render_area,
layers, depth_aspect_mask));
}
num_color_buffers = static_cast<u32>(num_colors);
framebuffer = runtime.device.GetLogical().CreateFramebuffer({
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
@@ -2827,11 +2855,25 @@ VkRenderPass Framebuffer::RenderPassVariant(u32 color_clear_mask, bool depth_ste
if (color_clear_mask == 0 && !depth_stencil_clear && color_discard_mask == 0) {
return renderpass;
}
static_assert(NUM_RT <= 8);
const u32 variant_key = color_clear_mask | (color_discard_mask << 8) |
(static_cast<u32>(depth_stencil_clear) << 16);
for (u32 index = 0; index < num_memoized_variants; ++index) {
if (variant_keys[index] == variant_key) {
return variant_render_passes[index];
}
}
RenderPassKey key = render_pass_key;
key.color_clear_mask = color_clear_mask;
key.depth_stencil_clear = depth_stencil_clear;
key.color_discard_mask = color_discard_mask;
return render_pass_cache->Get(key);
const VkRenderPass variant = render_pass_cache->Get(key);
if (num_memoized_variants < variant_keys.size()) {
variant_keys[num_memoized_variants] = variant_key;
variant_render_passes[num_memoized_variants] = variant;
++num_memoized_variants;
}
return variant;
}
void TextureCacheRuntime::AccelerateImageUpload(
@@ -2843,6 +2885,15 @@ void TextureCacheRuntime::AccelerateImageUpload(
return astc_decoder_pass->Assemble(image, map, swizzles);
}
if (bl2d_unswizzle_pass && BlockLinearUnswizzle2DPass::IsSupported(image.info)) {
return bl2d_unswizzle_pass->Unswizzle(image, map, swizzles);
}
if (bl3db_unswizzle_pass &&
BlockLinearUnswizzle3DBufferPass::IsSupported(device, image.info)) {
return bl3db_unswizzle_pass->Unswizzle(image, map, swizzles);
}
if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) {
if (IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D) {
ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture");
@@ -117,11 +117,13 @@ public:
VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent2D extent{};
u32 layers = 0;
VkImageAspectFlags aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT;
bool up_to_date = false;
};
[[nodiscard]] VkImageView GetOrCreateResolveShadow(VkImage msaa_image, VkFormat format,
VkExtent2D extent, u32 layers);
VkExtent2D extent, u32 layers,
VkImageAspectFlags aspect_mask);
[[nodiscard]] const ResolveShadow* GetValidResolveShadow(VkImage msaa_image) const;
@@ -149,6 +151,8 @@ public:
std::optional<ASTCDecoderPass> astc_decoder_pass;
std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass;
std::optional<BlockLinearUnswizzle2DPass> bl2d_unswizzle_pass;
std::optional<BlockLinearUnswizzle3DBufferPass> bl3db_unswizzle_pass;
const Settings::ResolutionScalingInfo& resolution;
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
@@ -156,6 +160,7 @@ public:
std::array<vk::Buffer, indexing_slots> buffers{};
std::vector<std::pair<u64, vk::Image>> pending_msaa_images;
ankerl::unordered_dense::map<VkImage, ResolveShadow> resolve_shadows;
std::vector<std::pair<u64, ResolveShadow>> pending_resolve_shadows;
};
class Framebuffer {
@@ -246,6 +251,8 @@ public:
}
private:
static constexpr size_t NUM_MEMOIZED_RENDER_PASS_VARIANTS = 8;
vk::Framebuffer framebuffer;
VkRenderPass renderpass{};
VkExtent2D render_area{};
@@ -263,6 +270,9 @@ private:
RenderPassKey render_pass_key{};
RenderPassCache* render_pass_cache{nullptr};
bool discard_msaa_color{};
mutable std::array<u32, NUM_MEMOIZED_RENDER_PASS_VARIANTS> variant_keys{};
mutable std::array<VkRenderPass, NUM_MEMOIZED_RENDER_PASS_VARIANTS> variant_render_passes{};
mutable u32 num_memoized_variants{};
};
class Image : public VideoCommon::ImageBase {
+58 -35
View File
@@ -1,9 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
@@ -30,12 +31,62 @@ constexpr u32 pdep(u32 value) {
return result;
}
constexpr u32 SWIZZLE_RUN_BYTES = 16;
constexpr u32 SWIZZLE_RUN_SHIFT = 4;
constexpr u32 SWIZZLE_RUN_MASK = SWIZZLE_RUN_BYTES - 1;
constexpr u32 SWIZZLE_RUN_INDEX_MASK = GOB_SIZE_X / SWIZZLE_RUN_BYTES - 1;
static_assert((SWIZZLE_X_BITS & SWIZZLE_RUN_MASK) == SWIZZLE_RUN_MASK);
constexpr std::array<u32, GOB_SIZE_X / SWIZZLE_RUN_BYTES> SWIZZLE_X_RUN_TABLE = [] {
std::array<u32, GOB_SIZE_X / SWIZZLE_RUN_BYTES> table{};
for (u32 index = 0; index < static_cast<u32>(table.size()); ++index) {
table[index] = pdep<SWIZZLE_X_BITS>(index << SWIZZLE_RUN_SHIFT);
}
return table;
}();
template <u32 mask, u32 incr_amount>
void incrpdep(u32& value) {
static constexpr u32 swizzled_incr = pdep<mask>(incr_amount);
value = ((value | ~mask) + swizzled_incr) & mask;
}
template <bool TO_LINEAR>
void SwizzleRow(std::span<u8> output, std::span<const u8> input, u32 offset_zy, u32 swizzled_y,
u32 x_shift, u32 x, u32 num_bytes, u32 linear) {
const auto copy = [&](u32 swizzled_x, u32 count) {
const u32 swizzled =
offset_zy + ((x >> GOB_SIZE_X_SHIFT) << x_shift) + (swizzled_x | swizzled_y);
u8* const dst = &output[TO_LINEAR ? swizzled : linear];
const u8* const src = &input[TO_LINEAR ? linear : swizzled];
std::memcpy(dst, src, count);
x += count;
linear += count;
};
u32 swizzled_run = SWIZZLE_X_RUN_TABLE[(x >> SWIZZLE_RUN_SHIFT) & SWIZZLE_RUN_INDEX_MASK];
u32 remaining = num_bytes;
const u32 head =
(std::min)(SWIZZLE_RUN_BYTES - (x & SWIZZLE_RUN_MASK), remaining) & SWIZZLE_RUN_MASK;
if (head != 0) {
copy(swizzled_run | (x & SWIZZLE_RUN_MASK), head);
remaining -= head;
incrpdep<SWIZZLE_X_BITS, SWIZZLE_RUN_BYTES>(swizzled_run);
}
while (remaining >= SWIZZLE_RUN_BYTES) {
copy(swizzled_run, SWIZZLE_RUN_BYTES);
remaining -= SWIZZLE_RUN_BYTES;
incrpdep<SWIZZLE_X_BITS, SWIZZLE_RUN_BYTES>(swizzled_run);
}
if (remaining != 0) {
copy(swizzled_run, remaining);
}
}
template <bool TO_LINEAR, u32 BYTES_PER_PIXEL>
void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth,
u32 block_height, u32 block_depth, u32 stride) {
@@ -70,23 +121,9 @@ void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32
const u32 offset_y = (block_y >> block_height) * block_size +
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
for (u32 column = 0; column < width;
++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
std::memcpy(dst, src, BYTES_PER_PIXEL);
}
SwizzleRow<TO_LINEAR>(output, input, offset_z + offset_y, swizzled_y, x_shift,
origin_x * BYTES_PER_PIXEL, width * BYTES_PER_PIXEL,
slice * pitch * height + line * pitch);
}
}
}
@@ -129,23 +166,9 @@ void SwizzleSubrectImpl(std::span<u8> output, std::span<const u8> input, u32 wid
const u32 offset_y = (block_y >> block_height) * block_size +
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
for (u32 column = 0; column < extent_x;
++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
std::memcpy(dst, src, BYTES_PER_PIXEL);
}
SwizzleRow<TO_LINEAR>(output, input, offset_z + offset_y, swizzled_y, x_shift,
origin_x * BYTES_PER_PIXEL, extent_x * BYTES_PER_PIXEL,
slice * pitch * height + line * pitch);
}
unprocessed_lines -= lines_in_y;
if (unprocessed_lines == 0) {
@@ -7,6 +7,8 @@
#include <algorithm>
#include <bitset>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <optional>
#include <thread>
#include <ankerl/unordered_dense.h>
@@ -16,6 +18,8 @@
#include <fmt/format.h>
#include "common/assert.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/literals.h"
#include <ranges>
#include "common/settings.h"
@@ -393,6 +397,17 @@ std::vector<const char*> ExtensionListForVulkan(
return output;
}
constexpr std::array<char, 8> STATIC_CACHE_MAGIC_NUMBER{'e', 'd', 'e', 'n', 's', 't', 'p', 'c'};
constexpr u32 STATIC_CACHE_VERSION = 1;
std::filesystem::path StaticPipelineCacheFilename() {
const auto shader_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
if (!Common::FS::CreateDir(shader_dir)) {
return {};
}
return shader_dir / "vulkan_static_pipelines.bin";
}
} // Anonymous namespace
void Device::RemoveExtension(bool& extension, const std::string& extension_name) {
@@ -780,15 +795,100 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
vk::Check(vmaCreateAllocator(&allocator_info, &allocator));
owns_static_pipeline_cache = surface != VkSurfaceKHR{};
LoadStaticPipelineCache();
// Initialize GPU logging if enabled
InitializeGPULogging();
}
Device::~Device() {
SaveStaticPipelineCache();
ShutdownGPULogging();
vmaDestroyAllocator(allocator);
}
void Device::LoadStaticPipelineCache() {
const auto create = [this](size_t size, const void* data) {
static_pipeline_cache = logical.CreatePipelineCache({
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.initialDataSize = size,
.pInitialData = data,
});
};
if (!owns_static_pipeline_cache) {
create(0, nullptr);
return;
}
const auto filename = StaticPipelineCacheFilename();
if (filename.empty()) {
create(0, nullptr);
return;
}
std::vector<char> data;
try {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
create(0, nullptr);
return;
}
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
const size_t total = static_cast<size_t>(file.tellg());
file.seekg(0, std::ios::beg);
std::array<char, 8> magic{};
u32 version{};
if (total < magic.size() + sizeof(version)) {
create(0, nullptr);
return;
}
file.read(magic.data(), magic.size())
.read(reinterpret_cast<char*>(&version), sizeof(version));
if (magic != STATIC_CACHE_MAGIC_NUMBER || version != STATIC_CACHE_VERSION) {
create(0, nullptr);
return;
}
data.resize(total - magic.size() - sizeof(version));
file.read(data.data(), static_cast<std::streamsize>(data.size()));
} catch (const std::ios_base::failure& e) {
create(0, nullptr);
return;
}
create(data.size(), data.empty() ? nullptr : data.data());
}
void Device::SaveStaticPipelineCache() const {
if (!owns_static_pipeline_cache || !static_pipeline_cache) {
return;
}
const auto filename = StaticPipelineCacheFilename();
if (filename.empty()) {
return;
}
size_t size = 0;
std::vector<char> data;
static_pipeline_cache.Read(&size, nullptr);
if (size == 0) {
return;
}
data.resize(size);
static_pipeline_cache.Read(&size, data.data());
try {
std::ofstream file(filename, std::ios::binary | std::ios::trunc);
file.exceptions(std::ofstream::failbit);
if (!file.is_open()) {
return;
}
file.write(STATIC_CACHE_MAGIC_NUMBER.data(), STATIC_CACHE_MAGIC_NUMBER.size())
.write(reinterpret_cast<const char*>(&STATIC_CACHE_VERSION),
sizeof(STATIC_CACHE_VERSION))
.write(data.data(), static_cast<std::streamsize>(size));
} catch (const std::ios_base::failure& e) {
Common::FS::RemoveFile(filename);
}
}
VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
FormatType format_type) const {
if (IsFormatSupported(wanted_format, wanted_usage, format_type)) {
@@ -975,6 +1075,12 @@ bool Device::GetSuitability(bool requires_swapchain) {
FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION);
FOR_EACH_VK_EXTENSION(EXTENSION);
extensions.depth_stencil_resolve =
extensions.depth_stencil_resolve &&
(instance_version >= VK_API_VERSION_1_2 || extensions.create_renderpass2);
RemoveExtensionIfUnsuitable(extensions.depth_stencil_resolve,
VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
if (supported_extensions.contains(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME)) {
loaded_extensions.erase(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
loaded_extensions.insert(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME);
@@ -1122,6 +1228,11 @@ bool Device::GetSuitability(bool requires_swapchain) {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR;
SetNext(next, properties.push_descriptor);
}
if (extensions.depth_stencil_resolve || instance_version >= VK_API_VERSION_1_2) {
properties.depth_stencil_resolve.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES;
SetNext(next, properties.depth_stencil_resolve);
}
if (extensions.descriptor_buffer) {
properties.descriptor_buffer.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT;
@@ -90,6 +90,8 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION(EXT, SHADER_VIEWPORT_INDEX_LAYER, shader_viewport_index_layer) \
EXTENSION(EXT, TOOLING_INFO, tooling_info) \
EXTENSION(EXT, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \
EXTENSION(KHR, CREATE_RENDERPASS_2, create_renderpass2) \
EXTENSION(KHR, DEPTH_STENCIL_RESOLVE, depth_stencil_resolve) \
EXTENSION(KHR, DRAW_INDIRECT_COUNT, draw_indirect_count) \
EXTENSION(KHR, DRIVER_PROPERTIES, driver_properties) \
EXTENSION(KHR, PUSH_DESCRIPTOR, push_descriptor) \
@@ -268,6 +270,10 @@ public:
return physical;
}
VkPipelineCache StaticPipelineCache() const noexcept {
return *static_pipeline_cache;
}
/// Returns the main graphics queue.
vk::Queue GetGraphicsQueue() const {
return graphics_queue;
@@ -607,6 +613,37 @@ FN_MAX_LIMIT_LIST
return extensions.shader_stencil_export;
}
/// Returns true if the device supports VK_KHR_create_renderpass2.
bool IsKhrCreateRenderPass2Supported() const {
return extensions.create_renderpass2 || instance_version >= VK_API_VERSION_1_2;
}
/// Returns true if the device supports VK_KHR_depth_stencil_resolve.
bool IsKhrDepthStencilResolveSupported() const {
return (extensions.depth_stencil_resolve || instance_version >= VK_API_VERSION_1_2) &&
IsKhrCreateRenderPass2Supported();
}
/// Returns the supported resolve modes for the depth aspect.
VkResolveModeFlags GetDepthResolveModes() const {
return properties.depth_stencil_resolve.supportedDepthResolveModes;
}
/// Returns the supported resolve modes for the stencil aspect.
VkResolveModeFlags GetStencilResolveModes() const {
return properties.depth_stencil_resolve.supportedStencilResolveModes;
}
/// Returns true if the depth and stencil aspects may resolve with different modes.
bool SupportsIndependentResolve() const {
return properties.depth_stencil_resolve.independentResolve == VK_TRUE;
}
/// Returns true if only one of the depth and stencil aspects may be resolved.
bool SupportsIndependentResolveNone() const {
return properties.depth_stencil_resolve.independentResolveNone == VK_TRUE;
}
/// Returns true if depth/stencil operations can be performed efficiently.
/// Either through shader export or hardware blits.
bool CanPerformDepthStencilOperations() const {
@@ -957,6 +994,21 @@ FN_MAX_LIMIT_LIST
return features2.features.multiViewport;
}
VkPipelineStageFlags AttachmentConsumerStages() const {
VkPipelineStageFlags stages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT |
VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
if (features2.features.geometryShader) {
stages |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
}
return stages;
}
/// Returns true if the device supports VK_KHR_maintenance1.
bool IsKhrMaintenance1Supported() const {
return extensions.maintenance1;
@@ -1088,6 +1140,9 @@ private:
/// Returns true if the device natively supports blitting depth stencil images.
bool TestDepthStencilBlits(VkFormat format) const;
void LoadStaticPipelineCache();
void SaveStaticPipelineCache() const;
private:
VkInstance instance; ///< Vulkan instance.
VmaAllocator allocator; ///< VMA allocator.
@@ -1096,6 +1151,8 @@ private:
vk::Device logical; ///< Logical device.
vk::Queue graphics_queue; ///< Main graphics queue.
vk::Queue present_queue; ///< Main present queue.
vk::PipelineCache static_pipeline_cache;
bool owns_static_pipeline_cache{};
u32 instance_version{}; ///< Vulkan instance version.
u32 graphics_family{}; ///< Main graphics queue family index.
u32 present_family{}; ///< Main present queue family index.
@@ -1142,6 +1199,7 @@ private:
VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{};
VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{};
VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{};
VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{};
VkPhysicalDeviceProperties properties{};
};
@@ -184,6 +184,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkCreatePipelineLayout);
X(vkCreateQueryPool);
X(vkCreateRenderPass);
X(vkCreateRenderPass2);
X(vkCreateSampler);
X(vkCreateSemaphore);
X(vkCreateShaderModule);
@@ -270,6 +271,11 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
if (!dld.vkQueueSubmit2) {
Proc(dld.vkQueueSubmit2, dld, "vkQueueSubmit2KHR", device);
}
// Render pass creation v2 is core in Vulkan 1.2, otherwise requires VK_KHR_create_renderpass2
if (!dld.vkCreateRenderPass2) {
Proc(dld.vkCreateRenderPass2, dld, "vkCreateRenderPass2KHR", device);
}
#undef X
}
@@ -725,6 +731,12 @@ RenderPass Device::CreateRenderPass(const VkRenderPassCreateInfo& ci) const {
return RenderPass(object, handle, *dld);
}
RenderPass Device::CreateRenderPass2(const VkRenderPassCreateInfo2& ci) const {
VkRenderPass object;
Check(dld->vkCreateRenderPass2(handle, &ci, nullptr, &object));
return RenderPass(object, handle, *dld);
}
DescriptorSetLayout Device::CreateDescriptorSetLayout(
const VkDescriptorSetLayoutCreateInfo& ci) const {
VkDescriptorSetLayout object;
@@ -300,6 +300,7 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkCreatePipelineLayout vkCreatePipelineLayout{};
PFN_vkCreateQueryPool vkCreateQueryPool{};
PFN_vkCreateRenderPass vkCreateRenderPass{};
PFN_vkCreateRenderPass2 vkCreateRenderPass2{};
PFN_vkCreateSampler vkCreateSampler{};
PFN_vkCreateSemaphore vkCreateSemaphore{};
PFN_vkCreateShaderModule vkCreateShaderModule{};
@@ -1045,6 +1046,8 @@ public:
[[nodiscard]] RenderPass CreateRenderPass(const VkRenderPassCreateInfo& ci) const;
[[nodiscard]] RenderPass CreateRenderPass2(const VkRenderPassCreateInfo2& ci) const;
[[nodiscard]] DescriptorSetLayout CreateDescriptorSetLayout(
const VkDescriptorSetLayoutCreateInfo& ci) const;
@@ -1526,7 +1529,7 @@ public:
}
void SetDepthBias(float constant_factor, float clamp, float slope_factor,
VkDepthBiasRepresentationInfoEXT* extra) const noexcept {
const VkDepthBiasRepresentationInfoEXT* extra) const noexcept {
VkDepthBiasInfoEXT info{
.sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_INFO_EXT,
.pNext = extra,