aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMako <137927095+Takoroka@users.noreply.github.com>2026-11-02 02:38:47 -0500
committerGitHub <noreply@github.com>2026-11-02 08:38:47 +0100
commite38b738b84eb671b7567a37b4932f11a58843dad (patch)
tree6b7ceef02e90971a052b79d9972994413d1ef46f /src
parente0f348ec127964762c81e1a4f77e290d0bfa8f96 (diff)
vulkan: repack 69-byte vertices to 72 bytes (#281)
Splatoon Raiders draws with a 69-byte vertex stride and a packed 4-byte attribute at byte 65. Those fetches are unaligned, which Vulkan leaves undefined: NVIDIA reads them as meant, the AMD Windows driver does not, and the outdoor scenes turn into a white haze with horizontal streaks. When a binding uses that layout, a compute pass copies it to a 72-byte stride with the tail moved to byte 68, and the vertex input offsets and stride follow. EGLANTINE_TEST_VERTEX_REPACK=0 turns it off; EGLANTINE_EXPERIMENTAL_STATIC_VERTEX_REPACK=1 also covers pipelines without dynamic vertex input. Based on an outside patch (02-portable-vertex-repacking). Changes from it: a range over the device limits skips the repack instead of throwing, and the pipeline cache version stays, so caches are kept.
Diffstat (limited to 'src')
-rw-r--r--src/tests/CMakeLists.txt1
-rw-r--r--src/tests/video_core/vertex_repack.cpp111
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt1
-rw-r--r--src/video_core/host_shaders/vulkan_vertex_repack.comp26
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.cpp13
-rw-r--r--src/video_core/renderer_vulkan/vertex_repack.h99
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp51
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h8
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp58
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp17
11 files changed, 397 insertions, 2 deletions
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index dd6f5d97b5..194e90a243 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -19,6 +19,7 @@ add_executable(tests
core/core_timing.cpp
core/internal_network/network.cpp
video_core/memory_tracker.cpp
+ video_core/vertex_repack.cpp
video_core/ampere_returns.cpp
input_common/calibration_configuration_job.cpp
)
diff --git a/src/tests/video_core/vertex_repack.cpp b/src/tests/video_core/vertex_repack.cpp
new file mode 100644
index 0000000000..6409be3ca4
--- /dev/null
+++ b/src/tests/video_core/vertex_repack.cpp
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <catch2/catch_test_macros.hpp>
+#include "video_core/renderer_vulkan/vertex_repack.h"
+
+namespace {
+using Regs = Tegra::Engines::Maxwell3D::Regs;
+using Size = Regs::VertexAttribute::Size;
+
+Regs MakeLayout() {
+ Regs regs{};
+ for (auto& attr : regs.vertex_attrib_format) {
+ attr.constant.Assign(1);
+ }
+ regs.vertex_streams[0].stride.Assign(69);
+ auto& position = regs.vertex_attrib_format[0];
+ position.constant.Assign(0);
+ position.size.Assign(Size::Size_R32_G32_B32);
+ auto& packed = regs.vertex_attrib_format[1];
+ packed.constant.Assign(0);
+ packed.offset.Assign(65);
+ packed.size.Assign(Size::Size_A2_B10_G10_R10);
+ return regs;
+}
+}
+
+TEST_CASE("Vertex repacking preserves aligned fields and relocates packed tail", "[vertex_repack]") {
+ const auto regs = MakeLayout();
+ const auto mask = Vulkan::VertexRepackMask(regs);
+ REQUIRE(mask == 1);
+ REQUIRE(Vulkan::RepackedVertexOffset(mask, 0, 0) == 0);
+ REQUIRE(Vulkan::RepackedVertexOffset(mask, 0, 64) == 64);
+ REQUIRE(Vulkan::RepackedVertexOffset(mask, 0, 65) == 68);
+ REQUIRE(Vulkan::RepackedVertexStride(mask, 0, 69) == 72);
+ REQUIRE(Vulkan::RepackedVertexStride(mask, 1, 16) == 16);
+}
+
+TEST_CASE("Vertex repacking excludes overlapping and oversized attributes", "[vertex_repack]") {
+ auto regs = MakeLayout();
+ auto& attr = regs.vertex_attrib_format[2];
+ attr.constant.Assign(0);
+ attr.offset.Assign(64);
+ attr.size.Assign(Size::Size_R32);
+ REQUIRE(Vulkan::VertexRepackMask(regs) == 0);
+ attr.offset.Assign(68);
+ REQUIRE(Vulkan::VertexRepackMask(regs) == 0);
+ attr.constant.Assign(1);
+ REQUIRE(Vulkan::VertexRepackMask(regs) == 1);
+}
+
+TEST_CASE("Vertex repacking ignores constant invalid formats", "[vertex_repack]") {
+ auto regs = MakeLayout();
+ REQUIRE(Vulkan::VertexRepackMask(regs) == 1);
+ regs.vertex_attrib_format[2].constant.Assign(0);
+ REQUIRE(Vulkan::VertexRepackMask(regs) == 0);
+}
+
+TEST_CASE("Vertex repacking selects bindings independently", "[vertex_repack]") {
+ auto regs = MakeLayout();
+ regs.vertex_streams[31].stride.Assign(69);
+ auto& attr = regs.vertex_attrib_format[2];
+ attr = regs.vertex_attrib_format[1];
+ attr.buffer.Assign(31);
+ REQUIRE(Vulkan::VertexRepackMask(regs) == 0x80000001U);
+ regs.vertex_streams[0].stride.Assign(72);
+ REQUIRE(Vulkan::VertexRepackMask(regs) == 0x80000000U);
+ REQUIRE(Vulkan::RepackedVertexOffset(0x80000000U, 0, 65) == 65);
+}
+
+TEST_CASE("Vertex repacking requires the captured tail and stride", "[vertex_repack]") {
+ auto regs = MakeLayout();
+ regs.vertex_streams[0].stride.Assign(68);
+ REQUIRE(Vulkan::VertexRepackMask(regs) == 0);
+ regs.vertex_streams[0].stride.Assign(69);
+ regs.vertex_attrib_format[1].offset.Assign(64);
+ REQUIRE(Vulkan::VertexRepackMask(regs) == 0);
+}
+
+TEST_CASE("Static vertex repacking requires explicit experimental opt-in", "[vertex_repack]") {
+ REQUIRE(Vulkan::VertexRepackInputSupported(true, false));
+ REQUIRE_FALSE(Vulkan::VertexRepackInputSupported(false, false));
+ REQUIRE(Vulkan::VertexRepackInputSupported(false, true));
+}
+
+TEST_CASE("Vertex repack planning keeps descriptor offsets aligned", "[vertex_repack]") {
+ for (const u32 alignment : {4U, 16U, 64U, 256U, 4096U}) {
+ for (const u32 offset : {0U, 1U, 3U, 255U, 4099U}) {
+ const auto plan = Vulkan::MakeVertexRepackParameters(offset, 70, alignment, 1U << 20, 65535);
+ REQUIRE(plan.has_value());
+ REQUIRE(plan->source_base % alignment == 0);
+ REQUIRE(plan->source_base + plan->source_bias == offset);
+ REQUIRE(plan->vertices == 2);
+ REQUIRE(plan->output_size == 144);
+ REQUIRE(plan->source_range >= plan->source_bias + 70);
+ REQUIRE(plan->source_range % 4 == 0);
+ REQUIRE(plan->dispatch_groups == 1);
+ }
+ }
+}
+
+TEST_CASE("Vertex repack planning rejects overflow and device-limit violations", "[vertex_repack]") {
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 0, 256, 1U << 20, 65535));
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 69, 0, 1U << 20, 65535));
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 69, 256, 71, 65535));
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(255, 69, 256, 323, 65535));
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 69, 256, 1U << 20, 0));
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 0xffffffffU, 256, ~u64{0}, 0xffffffffU));
+ const auto one = Vulkan::MakeVertexRepackParameters(0, 69, 256, 72, 1);
+ REQUIRE(one.has_value());
+ REQUIRE(one->source_range == 72);
+}
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index ada5c779dc..9ff7563ff7 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -14,6 +14,7 @@ set(GLSL_INCLUDES
)
set(SHADER_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_vertex_repack.comp
${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp
${CMAKE_CURRENT_SOURCE_DIR}/bc_encoder.comp
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
diff --git a/src/video_core/host_shaders/vulkan_vertex_repack.comp b/src/video_core/host_shaders/vulkan_vertex_repack.comp
new file mode 100644
index 0000000000..de9c649d93
--- /dev/null
+++ b/src/video_core/host_shaders/vulkan_vertex_repack.comp
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#version 450
+layout(local_size_x = 128) in;
+layout(std430, set = 0, binding = 0) readonly buffer Source { uint source_words[]; };
+layout(std430, set = 0, binding = 1) writeonly buffer Destination { uint dest_words[]; };
+layout(push_constant) uniform Parameters { uint source_bias; uint source_size; uint total_words; };
+
+// Diagnostic conversion of the captured 69-byte layout: insert three bytes before offset 65.
+void main() {
+ uint word = gl_GlobalInvocationID.x;
+ if (word >= total_words) return;
+ uint vertex = word / 18;
+ uint offset = (word % 18) * 4;
+ uint value = 0;
+ for (uint i = 0; i < 4; ++i) {
+ uint dest_byte = offset + i;
+ if (dest_byte >= 65 && dest_byte < 68) continue;
+ uint src_byte = vertex * 69 + dest_byte - (dest_byte >= 68 ? 3 : 0);
+ if (src_byte >= source_size) continue;
+ uint address = source_bias + src_byte;
+ uint byte_value = (source_words[address / 4] >> ((address % 4) * 8)) & 255;
+ value |= byte_value << (i * 8);
+ }
+ dest_words[word] = value;
+}
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
index 9dac98c830..d1f78c0034 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
@@ -14,6 +14,7 @@
#include "common/settings.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
+#include "video_core/renderer_vulkan/vertex_repack.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
namespace Vulkan {
@@ -268,6 +269,18 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
if (xfb_enabled) {
RefreshXfbState(xfb_state, regs);
}
+ if (!dynamic_vertex_input && ExperimentalStaticVertexRepackEnabled()) {
+ const u32 repack_mask = VertexRepackMask(regs);
+ // Refresh from guest values even when only the stride changed: offsets must not accumulate.
+ for (size_t index = 0; index < attributes.size(); ++index) {
+ const auto& input = regs.vertex_attrib_format[index];
+ attributes[index].offset.Assign(RepackedVertexOffset(repack_mask, input.buffer, input.offset));
+ }
+ for (u32 binding = 0; binding < vertex_strides.size(); ++binding) {
+ vertex_strides[binding] = static_cast<u16>(RepackedVertexStride(
+ repack_mask, binding, regs.vertex_streams[binding].stride));
+ }
+ }
}
void FixedPipelineState::BlendingAttachment::Refresh(const Maxwell& regs, size_t index) {
diff --git a/src/video_core/renderer_vulkan/vertex_repack.h b/src/video_core/renderer_vulkan/vertex_repack.h
new file mode 100644
index 0000000000..b0849c25c7
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vertex_repack.h
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+#include <optional>
+#include "video_core/engines/maxwell_3d.h"
+
+namespace Vulkan {
+
+constexpr bool VertexRepackInputSupported(bool dynamic_input, bool experimental_static) {
+ return dynamic_input || experimental_static;
+}
+
+inline bool ExperimentalStaticVertexRepackEnabled() {
+ static const bool enabled = [] {
+ const char* value = std::getenv("EGLANTINE_EXPERIMENTAL_STATIC_VERTEX_REPACK");
+ return value && std::strcmp(value, "1") == 0;
+ }();
+ return enabled;
+}
+
+struct VertexRepackParameters {
+ u32 vertices;
+ u32 output_size;
+ u32 source_base;
+ u32 source_bias;
+ u32 source_range;
+ u32 dispatch_groups;
+};
+
+inline std::optional<VertexRepackParameters> MakeVertexRepackParameters(
+ u32 offset, u32 size, u64 alignment, u64 max_storage_range, u32 max_dispatch_groups) {
+ if (size == 0 || alignment == 0) {
+ return std::nullopt;
+ }
+ const u64 vertices = (u64{size} + 68) / 69;
+ const u64 output_size = vertices * 72;
+ const u64 base = u64{offset} / alignment * alignment;
+ const u64 bias = offset - base;
+ const u64 range = (bias + size + 3) / 4 * 4;
+ const u64 groups = (vertices * 18 + 127) / 128;
+ if (output_size > std::numeric_limits<u32>::max() ||
+ range > std::numeric_limits<u32>::max() || output_size > max_storage_range ||
+ range > max_storage_range || groups > max_dispatch_groups) {
+ return std::nullopt;
+ }
+ return VertexRepackParameters{static_cast<u32>(vertices), static_cast<u32>(output_size),
+ static_cast<u32>(base), static_cast<u32>(bias),
+ static_cast<u32>(range), static_cast<u32>(groups)};
+}
+
+inline bool VertexRepackEnabled() {
+ static const bool enabled = [] {
+ const char* value = std::getenv("EGLANTINE_TEST_VERTEX_REPACK");
+ return !value || std::strcmp(value, "0") != 0;
+ }();
+ return enabled;
+}
+
+inline u32 VertexRepackMask(const Tegra::Engines::Maxwell3D::Regs& regs) {
+ using Attribute = Tegra::Engines::Maxwell3D::Regs::VertexAttribute;
+ if (!VertexRepackEnabled()) {
+ return 0;
+ }
+ u32 candidates = 0;
+ u32 excluded = 0;
+ for (const auto& attribute : regs.vertex_attrib_format) {
+ const u32 binding = attribute.buffer;
+ if (attribute.constant || regs.vertex_streams[binding].stride != 69) {
+ continue;
+ }
+ if (attribute.size == Attribute::Size::Invalid) {
+ excluded |= 1U << binding;
+ continue;
+ }
+ const u32 offset = attribute.offset;
+ const u32 size = attribute.SizeInBytes();
+ if (offset == 65 && size == 4) {
+ candidates |= 1U << binding;
+ }
+ if (offset + size > 69 || (offset < 65 && offset + size > 65)) {
+ excluded |= 1U << binding;
+ }
+ }
+ return candidates & ~excluded;
+}
+
+inline u32 RepackedVertexOffset(u32 mask, u32 binding, u32 offset) {
+ return offset + ((mask & (1U << binding)) && offset >= 65 ? 3U : 0U);
+}
+
+inline u32 RepackedVertexStride(u32 mask, u32 binding, u32 stride) {
+ return mask & (1U << binding) ? 72U : stride;
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 493de2cc20..fe0ab3a28e 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -7,11 +7,13 @@
#include <algorithm>
#include <array>
#include <cstring>
+#include <cstdlib>
#include <span>
#include <vector>
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
+#include "video_core/renderer_vulkan/vertex_repack.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -359,6 +361,14 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
compute_pass_descriptor_queue),
multi_range_buffers(device_) {
const VkDriverIdKHR driver_id = device.GetDriverID();
+ const bool enable_vertex_repack = VertexRepackEnabled();
+ LOG_INFO(Render_Vulkan, "69-byte vertex layout repacking {} ({})",
+ enable_vertex_repack ? "enabled" : "disabled",
+ "portable Vulkan layout fallback");
+ if (enable_vertex_repack) {
+ vertex_repack_pass = std::make_unique<VertexRepackPass>(
+ device, scheduler, descriptor_pool, staging_pool, compute_pass_descriptor_queue);
+ }
limit_dynamic_storage_buffers = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
if (limit_dynamic_storage_buffers) {
@@ -374,6 +384,19 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
scheduler_, staging_pool_);
}
+u32 BufferCacheRuntime::SetVertexRepackLayout(const Tegra::Engines::Maxwell3D::Regs& regs,
+ bool dynamic_input) {
+ const u32 old_mask = vertex_repack_mask;
+ const bool input_supported = VertexRepackInputSupported(
+ dynamic_input, ExperimentalStaticVertexRepackEnabled());
+ vertex_repack_mask = vertex_repack_pass && input_supported ? VertexRepackMask(regs) : 0;
+ if (vertex_repack_mask != old_mask) {
+ LOG_DEBUG(Render_Vulkan, "Vertex repack selection: mask={:#x}, dynamic_input={}",
+ vertex_repack_mask, dynamic_input);
+ }
+ return old_mask | vertex_repack_mask;
+}
+
StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
return staging_pool.Request(size, MemoryUsage::Upload);
}
@@ -629,6 +652,16 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
if (index >= device.GetMaxVertexInputBindings()) {
return;
}
+ if (buffer != VK_NULL_HANDLE && size != 0 && IsVertexRepacked(index)) {
+ const auto repacked = vertex_repack_pass->Assemble(buffer, offset, size);
+ if (!repacked) {
+ return;
+ }
+ buffer = repacked->first;
+ offset = static_cast<u32>(repacked->second);
+ size = static_cast<u32>((u64{size} + 68) / 69 * 72);
+ stride = 72;
+ }
if (device.IsExtExtendedDynamicStateSupported()) {
scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) {
const VkDeviceSize vk_offset = buffer != VK_NULL_HANDLE ? offset : 0;
@@ -661,6 +694,24 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
}
}
buffer_handles[i] = handle;
+ const u32 binding = bindings.min_index + i;
+ if (handle != VK_NULL_HANDLE && IsVertexRepacked(binding) && bindings.strides[i] == 69 &&
+ bindings.sizes[i] > 0) {
+ const auto repacked = vertex_repack_pass->Assemble(
+ handle, static_cast<u32>(bindings.offsets[i]), static_cast<u32>(bindings.sizes[i]));
+ if (!repacked) {
+ continue;
+ }
+ buffer_handles[i] = repacked->first;
+ bindings.offsets[i] = repacked->second;
+ bindings.sizes[i] = (bindings.sizes[i] + 68) / 69 * 72;
+ bindings.strides[i] = 72;
+ static bool reported = false;
+ if (!reported) {
+ LOG_WARNING(Render_Vulkan, "Experimental GPU vertex repacking active: 69 -> 72 bytes");
+ reported = true;
+ }
+ }
}
const u32 device_max = device.GetMaxVertexInputBindings();
const u32 min_binding = (std::min)(bindings.min_index, device_max);
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index fff1f8164d..ff854445b5 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -110,6 +110,12 @@ public:
void TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept;
+ u32 SetVertexRepackLayout(const Tegra::Engines::Maxwell3D::Regs& regs, bool dynamic_input);
+
+ bool IsVertexRepacked(u32 binding) const {
+ return (vertex_repack_mask & (1U << binding)) != 0;
+ }
+
u64 CurrentTick();
bool IsFree(u64 tick);
@@ -259,6 +265,8 @@ private:
vk::Buffer null_buffer;
std::unique_ptr<Uint8Pass> uint8_pass;
+ std::unique_ptr<VertexRepackPass> vertex_repack_pass;
+ u32 vertex_repack_mask{};
QuadIndexedPass quad_index_pass;
MultiRangeBufferCache multi_range_buffers;
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index 8cd8abb599..691be728d2 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -26,8 +26,10 @@
#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/vulkan_vertex_repack_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
+#include "video_core/renderer_vulkan/vertex_repack.h"
#include "video_core/surface.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -324,6 +326,62 @@ ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, Descriptor
ComputePass::~ComputePass() = default;
+VertexRepackPass::VertexRepackPass(const Device& device_, Scheduler& scheduler_,
+ DescriptorPool& descriptor_pool, StagingBufferPool& staging_pool_,
+ ComputePassDescriptorQueue& descriptor_queue_)
+ : ComputePass(device_, scheduler_, descriptor_pool, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
+ INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO,
+ COMPUTE_PUSH_CONSTANT_RANGE<12>, VULKAN_VERTEX_REPACK_COMP_SPV),
+ scheduler{scheduler_}, staging_pool{staging_pool_}, descriptor_queue{descriptor_queue_} {}
+
+std::optional<std::pair<VkBuffer, VkDeviceSize>> VertexRepackPass::Assemble(VkBuffer source,
+ u32 offset, u32 size) {
+ const auto parameters = MakeVertexRepackParameters(
+ offset, size, device.GetStorageBufferAlignment(), device.GetMaxStorageBufferRange(),
+ device.GetMaxComputeWorkGroupCount()[0]);
+ if (!parameters) {
+ LOG_ERROR(Render_Vulkan, "Vertex repack exceeds device limits: offset={}, size={}", offset, size);
+ return std::nullopt;
+ }
+ const u32 num_vertices = parameters->vertices;
+ const u32 output_size = parameters->output_size;
+ const auto dest = staging_pool.Request(output_size, MemoryUsage::DeviceLocal);
+ const u32 base_offset = parameters->source_base;
+ const u32 bias = parameters->source_bias;
+ descriptor_queue.Acquire(scheduler, 2);
+ descriptor_queue.AddBuffer(source, base_offset, parameters->source_range);
+ descriptor_queue.AddBuffer(dest.buffer, dest.offset, output_size);
+ const void* data = descriptor_queue.UpdateData();
+ scheduler.RequestOutsideRenderPassOperationContext();
+ LOG_DEBUG(Render_Vulkan, "Vertex repack queued: source_size={}, output_size={}, groups={}",
+ size, output_size, parameters->dispatch_groups);
+ scheduler.Record([this, data, bias, size, num_vertices,
+ groups = parameters->dispatch_groups](vk::CommandBuffer cmdbuf) {
+ const VkMemoryBarrier read_barrier{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
+ };
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
+ const auto set = descriptor_allocator.Commit();
+ device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, data);
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
+ const std::array<u32, 3> constants{bias, size, num_vertices * 18};
+ cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(constants), constants.data());
+ cmdbuf.Dispatch(groups, 1, 1);
+ const VkMemoryBarrier write_barrier{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
+ };
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, write_barrier);
+ });
+ return std::pair<VkBuffer, VkDeviceSize>{dest.buffer, dest.offset};
+}
+
Uint8Pass::Uint8Pass(const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h
index 9f2e8e9f1c..27fffbbbb7 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -56,6 +56,20 @@ private:
vk::ShaderModule module;
};
+class VertexRepackPass final : public ComputePass {
+public:
+ VertexRepackPass(const Device& device, Scheduler& scheduler, DescriptorPool& descriptor_pool,
+ StagingBufferPool& staging_pool, ComputePassDescriptorQueue& descriptor_queue);
+ /// Returns nothing when the range exceeds the device limits; the binding is then left as is.
+ std::optional<std::pair<VkBuffer, VkDeviceSize>> Assemble(VkBuffer source, u32 offset,
+ u32 size);
+
+private:
+ Scheduler& scheduler;
+ StagingBufferPool& staging_pool;
+ ComputePassDescriptorQueue& descriptor_queue;
+};
+
class Uint8Pass final : public ComputePass {
public:
explicit Uint8Pass(const Device& device_, Scheduler& scheduler_,
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index c0e8825bac..bbb49d5432 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -282,6 +282,18 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
// update engine as channel may be different.
pipeline->SetEngine(maxwell3d, gpu_memory);
+ const u32 repack_dirty = buffer_cache_runtime.SetVertexRepackLayout(
+ maxwell3d->regs, pipeline->HasDynamicVertexInput());
+ if (repack_dirty) {
+ auto& flags = maxwell3d->dirty.flags;
+ flags[VideoCommon::Dirty::VertexBuffers] = true;
+ flags[Dirty::VertexInput] = true;
+ for (u32 binding = 0; binding < 32; ++binding) {
+ if (repack_dirty & (1U << binding)) {
+ flags[VideoCommon::Dirty::VertexBuffer0 + binding] = true;
+ }
+ }
+ }
if (Profile::Scope scope{Profile::Zone::Configure}; !pipeline->Configure(is_indexed)) {
report_failure("configure failed");
return;
@@ -2269,7 +2281,8 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
.location = index,
.binding = binding,
.format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size),
- .offset = attribute.offset,
+ .offset = attribute.offset +
+ (buffer_cache_runtime.IsVertexRepacked(binding) && attribute.offset >= 65 ? 3 : 0),
});
}
@@ -2280,7 +2293,7 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT,
.pNext = nullptr,
.binding = binding,
- .stride = input_binding.stride,
+ .stride = buffer_cache_runtime.IsVertexRepacked(binding) ? 72U : input_binding.stride.Value(),
.inputRate = is_instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX,
.divisor = is_instanced ? input_binding.frequency : 1,
});