diff options
| author | Mako <137927095+Takoroka@users.noreply.github.com> | 2026-11-02 02:38:47 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-11-02 08:38:47 +0100 |
| commit | e38b738b84eb671b7567a37b4932f11a58843dad (patch) | |
| tree | 6b7ceef02e90971a052b79d9972994413d1ef46f /src | |
| parent | e0f348ec127964762c81e1a4f77e290d0bfa8f96 (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.txt | 1 | ||||
| -rw-r--r-- | src/tests/video_core/vertex_repack.cpp | 111 | ||||
| -rw-r--r-- | src/video_core/host_shaders/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/video_core/host_shaders/vulkan_vertex_repack.comp | 26 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/fixed_pipeline_state.cpp | 13 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vertex_repack.h | 99 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_buffer_cache.cpp | 51 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_buffer_cache.h | 8 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_compute_pass.cpp | 58 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_compute_pass.h | 14 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_rasterizer.cpp | 17 |
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, }); |
