From ca52cf679acd930ddcf0a0cd5ee59350e25181b5 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 27 Aug 2026 22:48:53 -0400 Subject: [PATCH 01/26] Another try on unswizzling --- src/video_core/host_shaders/CMakeLists.txt | 33 +++ .../block_linear_unswizzle_2d.comp | 6 +- .../host_shaders/pitch_unswizzle.comp | 6 +- .../renderer_vulkan/vk_compute_pass.cpp | 239 ++++++++++++++++++ .../renderer_vulkan/vk_compute_pass.h | 30 +++ .../renderer_vulkan/vk_scheduler.cpp | 6 +- .../renderer_vulkan/vk_texture_cache.cpp | 119 ++++++++- .../renderer_vulkan/vk_texture_cache.h | 5 + src/video_core/vulkan_common/vulkan_device.h | 1 + 9 files changed, 427 insertions(+), 18 deletions(-) diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index c4e7ac3f6f..33d2bdbe16 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -206,6 +206,39 @@ foreach(VARIANT IN ITEMS ${SHADER_TYPE_VARIANTS}) set(SHADER_HEADERS ${SHADER_HEADERS} ${VARIANT_HEADER_FILE}) endforeach() +set(SHADER_DEFINE_VARIANTS + "block_linear_unswizzle_2d.comp|nonarrow|HAS_EXTENDED_TYPES=0" + "pitch_unswizzle.comp|nonarrow|HAS_EXTENDED_TYPES=0" +) + +foreach(VARIANT IN ITEMS ${SHADER_DEFINE_VARIANTS}) + string(REPLACE "|" ";" VARIANT_PARTS ${VARIANT}) + list(GET VARIANT_PARTS 0 VARIANT_FILENAME) + list(GET VARIANT_PARTS 1 VARIANT_SUFFIX) + list(GET VARIANT_PARTS 2 VARIANT_DEFINE) + + set(VARIANT_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${VARIANT_FILENAME}) + get_filename_component(VARIANT_STEM ${VARIANT_FILENAME} NAME_WE) + get_filename_component(VARIANT_EXT ${VARIANT_FILENAME} EXT) + string(REPLACE "." "" VARIANT_EXT ${VARIANT_EXT}) + set(VARIANT_NAME ${VARIANT_STEM}_${VARIANT_SUFFIX}_${VARIANT_EXT}) + + string(TOUPPER ${VARIANT_NAME}_SPV VARIANT_VARIABLE_NAME) + set(VARIANT_HEADER_FILE ${SHADER_DIR}/${VARIANT_NAME}_spv.h) + add_custom_command( + OUTPUT + ${VARIANT_HEADER_FILE} + COMMAND + ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} + -D${VARIANT_DEFINE} + --variable-name ${VARIANT_VARIABLE_NAME} -o ${VARIANT_HEADER_FILE} ${VARIANT_SOURCE} + --target-env ${SPIR_V_VERSION} + MAIN_DEPENDENCY + ${VARIANT_SOURCE} + ) + set(SHADER_HEADERS ${SHADER_HEADERS} ${VARIANT_HEADER_FILE}) +endforeach() + foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES}) get_filename_component(FILENAME ${FILEPATH} NAME) string(REPLACE "." "_" HEADER_NAME ${FILENAME}) diff --git a/src/video_core/host_shaders/block_linear_unswizzle_2d.comp b/src/video_core/host_shaders/block_linear_unswizzle_2d.comp index 2a3446ca66..df4bf16136 100644 --- a/src/video_core/host_shaders/block_linear_unswizzle_2d.comp +++ b/src/video_core/host_shaders/block_linear_unswizzle_2d.comp @@ -5,9 +5,13 @@ #ifdef VULKAN +#ifndef HAS_EXTENDED_TYPES +#define HAS_EXTENDED_TYPES 1 +#endif +#if HAS_EXTENDED_TYPES #extension GL_EXT_shader_16bit_storage : require #extension GL_EXT_shader_8bit_storage : require -#define HAS_EXTENDED_TYPES 1 +#endif #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define END_PUSH_CONSTANTS }; #define UNIFORM(n) diff --git a/src/video_core/host_shaders/pitch_unswizzle.comp b/src/video_core/host_shaders/pitch_unswizzle.comp index 7d23f594c7..8f07704fa9 100644 --- a/src/video_core/host_shaders/pitch_unswizzle.comp +++ b/src/video_core/host_shaders/pitch_unswizzle.comp @@ -5,9 +5,13 @@ #ifdef VULKAN +#ifndef HAS_EXTENDED_TYPES +#define HAS_EXTENDED_TYPES 1 +#endif +#if HAS_EXTENDED_TYPES #extension GL_EXT_shader_16bit_storage : require #extension GL_EXT_shader_8bit_storage : require -#define HAS_EXTENDED_TYPES 1 +#endif #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define END_PUSH_CONSTANTS }; #define UNIFORM(n) diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index ab58560170..1fdefa1010 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -22,7 +22,11 @@ #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_comp_spv.h" +#include "video_core/host_shaders/block_linear_unswizzle_2d_nonarrow_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h" +#include "video_core/host_shaders/pitch_unswizzle_comp_spv.h" +#include "video_core/host_shaders/pitch_unswizzle_nonarrow_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" @@ -872,4 +876,239 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk( }); } +constexpr u32 UNSWIZZLE_BINDING_INPUT_BUFFER = 0; +constexpr u32 UNSWIZZLE_BINDING_OUTPUT_IMAGE = 1; +constexpr size_t UNSWIZZLE_NUM_BINDINGS = 2; + +constexpr std::array + UNSWIZZLE_DESCRIPTOR_SET_BINDINGS{{ + { + .binding = UNSWIZZLE_BINDING_INPUT_BUFFER, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = nullptr, + }, + { + .binding = UNSWIZZLE_BINDING_OUTPUT_IMAGE, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = nullptr, + }, + }}; + +constexpr std::array + UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE{{ + { + .dstBinding = UNSWIZZLE_BINDING_INPUT_BUFFER, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .offset = UNSWIZZLE_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry), + .stride = sizeof(DescriptorUpdateEntry), + }, + { + .dstBinding = UNSWIZZLE_BINDING_OUTPUT_IMAGE, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .offset = UNSWIZZLE_BINDING_OUTPUT_IMAGE * sizeof(DescriptorUpdateEntry), + .stride = sizeof(DescriptorUpdateEntry), + }, + }}; + +constexpr DescriptorBankInfo UNSWIZZLE_BANK_INFO{ + .uniform_buffers = 0, + .storage_buffers = 1, + .texture_buffers = 0, + .image_buffers = 0, + .textures = 0, + .images = 1, + .score = 2, +}; + +[[nodiscard]] std::span UnswizzleSpv(const Device& device, + std::span extended, + std::span narrow) { + if (device.IsStorageBuffer8BitAccessSupported() && + device.IsStorageBuffer16BitAccessSupported()) { + return extended; + } + return narrow; +} + +struct PitchUnswizzlePushConstants { + alignas(8) std::array origin; + alignas(8) std::array destination; + u32 bytes_per_block; + u32 pitch; +}; + +void RecordUnswizzleEntryBarrier(Scheduler& scheduler, VkPipeline vk_pipeline, VkImage vk_image, + VkImageAspectFlags aspect_mask, bool is_initialized) { + scheduler.Record([vk_pipeline, vk_image, aspect_mask, + is_initialized](vk::CommandBuffer cmdbuf) { + VkAccessFlags src_access = VK_ACCESS_NONE; + VkImageLayout old_layout = VK_IMAGE_LAYOUT_UNDEFINED; + if (is_initialized) { + src_access = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + old_layout = VK_IMAGE_LAYOUT_GENERAL; + } + const VkImageMemoryBarrier image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = src_access, + .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = old_layout, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + VkPipelineStageFlags src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + if (is_initialized) { + src_stage = vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER; + } + cmdbuf.PipelineBarrier(src_stage, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline); + }); +} + +void RecordUnswizzleExitBarrier(Scheduler& scheduler, VkImage vk_image, + VkImageAspectFlags aspect_mask) { + scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, image_barrier); + }); +} + +BlockLinearUnswizzle2DPass::BlockLinearUnswizzle2DPass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_) + : ComputePass(device_, scheduler_, descriptor_pool_, UNSWIZZLE_DESCRIPTOR_SET_BINDINGS, + UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE, UNSWIZZLE_BANK_INFO, + COMPUTE_PUSH_CONSTANT_RANGE, + UnswizzleSpv(device_, BLOCK_LINEAR_UNSWIZZLE_2D_COMP_SPV, + BLOCK_LINEAR_UNSWIZZLE_2D_NONARROW_COMP_SPV)), + scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} + +BlockLinearUnswizzle2DPass::~BlockLinearUnswizzle2DPass() = default; + +void BlockLinearUnswizzle2DPass::Unswizzle( + Image& image, const StagingBufferRef& map, + std::span swizzles) { + using namespace VideoCommon::Accelerated; + scheduler.RequestOutsideRenderPassOperationContext(); + const VkPipeline vk_pipeline = *pipeline; + const VkImageAspectFlags aspect_mask = image.AspectMask(); + const VkImage vk_image = image.Handle(); + const bool is_initialized = image.ExchangeInitialization(); + RecordUnswizzleEntryBarrier(scheduler, vk_pipeline, vk_image, aspect_mask, is_initialized); + + const u32 num_layers = static_cast(image.info.resources.layers); + for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { + const size_t input_offset = swizzle.buffer_offset + map.offset; + const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); + const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); + + compute_pass_descriptor_queue.Acquire(scheduler, 2); + compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); + const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; + + const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info); + scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_layers, params, + descriptor_data](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet set = descriptor_allocator.Commit(); + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); + cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); + cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_layers); + }); + } + RecordUnswizzleExitBarrier(scheduler, vk_image, aspect_mask); +} + +PitchUnswizzlePass::PitchUnswizzlePass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_) + : ComputePass(device_, scheduler_, descriptor_pool_, UNSWIZZLE_DESCRIPTOR_SET_BINDINGS, + UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE, UNSWIZZLE_BANK_INFO, + COMPUTE_PUSH_CONSTANT_RANGE, + UnswizzleSpv(device_, PITCH_UNSWIZZLE_COMP_SPV, + PITCH_UNSWIZZLE_NONARROW_COMP_SPV)), + scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} + +PitchUnswizzlePass::~PitchUnswizzlePass() = default; + +void PitchUnswizzlePass::Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles) { + scheduler.RequestOutsideRenderPassOperationContext(); + const VkPipeline vk_pipeline = *pipeline; + const VkImageAspectFlags aspect_mask = image.AspectMask(); + const VkImage vk_image = image.Handle(); + const bool is_initialized = image.ExchangeInitialization(); + RecordUnswizzleEntryBarrier(scheduler, vk_pipeline, vk_image, aspect_mask, is_initialized); + + const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image.info.format); + const u32 pitch = image.info.pitch; + for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { + const size_t input_offset = swizzle.buffer_offset + map.offset; + const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); + const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); + + compute_pass_descriptor_queue.Acquire(scheduler, 2); + compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); + const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; + + const PitchUnswizzlePushConstants params{ + .origin{0, 0}, + .destination{0, 0}, + .bytes_per_block = bytes_per_block, + .pitch = pitch, + }; + scheduler.Record([this, num_dispatches_x, num_dispatches_y, params, + descriptor_data](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet set = descriptor_allocator.Commit(); + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); + cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); + cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, 1); + }); + } + RecordUnswizzleExitBarrier(scheduler, vk_image, aspect_mask); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index 3d30aa6bdc..117f228bee 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -164,4 +164,34 @@ private: ComputePassDescriptorQueue& compute_pass_descriptor_queue; }; +class BlockLinearUnswizzle2DPass final : public ComputePass { +public: + explicit BlockLinearUnswizzle2DPass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_); + ~BlockLinearUnswizzle2DPass(); + + void Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles); + +private: + Scheduler& scheduler; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; +}; + +class PitchUnswizzlePass final : public ComputePass { +public: + explicit PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_); + ~PitchUnswizzlePass(); + + void Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles); + +private: + Scheduler& scheduler; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; +}; + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 92dcd20111..33887f2f84 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -449,7 +449,9 @@ void Scheduler::EndRenderPass() | 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, + | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT + | VK_ACCESS_TRANSFER_READ_BIT + | VK_ACCESS_TRANSFER_WRITE_BIT, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, @@ -460,7 +462,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, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images)); if (has_transform_feedback) { static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{ diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index c3f56408b5..e92f6e5e18 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -160,6 +160,54 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { info.size.depth == 1; } +[[nodiscard]] PixelFormat UnswizzleViewFormat(u32 bytes_per_block) { + switch (bytes_per_block) { + case 1: + return PixelFormat::R8_UINT; + case 2: + return PixelFormat::R16_UINT; + case 4: + return PixelFormat::R32_UINT; + case 8: + return PixelFormat::R32G32_UINT; + case 16: + return PixelFormat::R32G32B32A32_UINT; + default: + return PixelFormat::Invalid; + } +} + +constexpr u32 UNSWIZZLE_WORKGROUP_INVOCATIONS = 32 * 32; + +[[nodiscard]] bool SupportsAcceleratedUnswizzleDevice(const Device& device) { + return device.IsKhrImageFormatListSupported() && + device.GetMaxComputeWorkGroupInvocations() >= UNSWIZZLE_WORKGROUP_INVOCATIONS; +} + +[[nodiscard]] bool SupportsAcceleratedUnswizzle(const Device& device, const ImageInfo& info) { + if (!SupportsAcceleratedUnswizzleDevice(device)) { + return false; + } + if (info.num_samples > 1) { + return false; + } + if (info.type != ImageType::e2D && info.type != ImageType::Linear) { + return false; + } + const PixelFormat view_format = + UnswizzleViewFormat(VideoCore::Surface::BytesPerBlock(info.format)); + if (view_format == PixelFormat::Invalid) { + return false; + } + if (!VideoCore::Surface::IsViewCompatible(info.format, view_format, false, true)) { + return false; + } + const auto host_format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, view_format); + return device.IsFormatSupported(host_format.format, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT, + FormatType::Optimal); +} + [[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info, std::optional format_override = {}) { auto format_info = @@ -248,8 +296,18 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { return allocator.CreateImage(image_ci); } +[[nodiscard]] VkImageViewType StorageViewType(ImageType type) { + if (type == ImageType::e3D) { + return VK_IMAGE_VIEW_TYPE_3D; + } + if (type == ImageType::Linear) { + return VK_IMAGE_VIEW_TYPE_2D; + } + return VK_IMAGE_VIEW_TYPE_2D_ARRAY; +} + [[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image, - VkFormat format) { + VkFormat format, VkImageViewType view_type) { static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, .pNext = nullptr, @@ -260,7 +318,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { .pNext = &storage_image_view_usage_create_info, .flags = 0, .image = image, - .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY, + .viewType = view_type, .format = format, .components{ .r = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -968,6 +1026,12 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue); } + if (SupportsAcceleratedUnswizzleDevice(device)) { + bl_unswizzle_2d_pass.emplace(device, scheduler, descriptor_pool, + compute_pass_descriptor_queue); + pitch_unswizzle_pass.emplace(device, scheduler, descriptor_pool, + compute_pass_descriptor_queue); + } } void TextureCacheRuntime::Finish() { @@ -1902,16 +1966,12 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu if (runtime->device.HasDebuggingToolAttached()) { original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); } + if (False(flags & VideoCommon::ImageFlagBits::Converted) && + SupportsAcceleratedUnswizzle(runtime->device, info)) { + flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; + } current_image = &Image::original_image; storage_image_views.resize(info.resources.levels); - if (WillUseAcceleratedAstcDecode(runtime->device, info)) { - const auto& device = runtime->device.GetLogical(); - const VkFormat storage_format = VK_FORMAT_A8B8G8R8_UNORM_PACK32; - for (s32 level = 0; level < info.resources.levels; ++level) { - storage_image_views[level] = - MakeStorageView(device, level, *original_image, storage_format); - } - } } Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBase{params} {} @@ -2321,16 +2381,39 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span& Image::StorageViewsFor(vk::Image Image::*image) { + if (image == &Image::scaled_image) { + if (scaled_storage_image_views.empty()) { + scaled_storage_image_views.resize(info.resources.levels); + } + return scaled_storage_image_views; + } + return storage_image_views; +} + VkImageView Image::StorageImageView(s32 level) noexcept { - auto& view = storage_image_views[level]; + const bool astc_decode = WillUseAcceleratedAstcDecode(runtime->device, info); + const bool unswizzle_upload = + !astc_decode && True(flags & ImageFlagBits::AcceleratedUpload); + vk::Image Image::*target = current_image; + if (astc_decode || unswizzle_upload) { + target = &Image::original_image; + } + auto& view = StorageViewsFor(target)[level]; if (!view) { auto format_info = MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format); - if (WillUseAcceleratedAstcDecode(runtime->device, info)) { + if (astc_decode) { format_info.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32; } - view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image), - format_info.format); + if (unswizzle_upload) { + const PixelFormat view_format = + UnswizzleViewFormat(VideoCore::Surface::BytesPerBlock(info.format)); + format_info = MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, false, + view_format); + } + view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*target), + format_info.format, StorageViewType(info.type)); } return *view; } @@ -3141,6 +3224,14 @@ void TextureCacheRuntime::AccelerateImageUpload( return astc_decoder_pass->Assemble(image, map, swizzles); } + if (bl_unswizzle_2d_pass && image.info.type == ImageType::e2D) { + return bl_unswizzle_2d_pass->Unswizzle(image, map, swizzles); + } + + if (pitch_unswizzle_pass && image.info.type == ImageType::Linear) { + return pitch_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"); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 68bf2af991..271c7d5caf 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -159,6 +159,8 @@ public: std::optional astc_decoder_pass; std::optional bl3d_unswizzle_pass; + std::optional bl_unswizzle_2d_pass; + std::optional pitch_unswizzle_pass; const Settings::ResolutionScalingInfo& resolution; std::array, VideoCore::Surface::MaxPixelFormat> view_formats; @@ -370,6 +372,8 @@ private: bool NeedsScaleHelper() const; + std::vector& StorageViewsFor(vk::Image Image::*image); + Scheduler* scheduler{}; TextureCacheRuntime* runtime{}; @@ -387,6 +391,7 @@ private: vk::Image Image::*current_image{}; std::vector storage_image_views; + std::vector scaled_storage_image_views; VkImageAspectFlags aspect_mask = 0; bool initialized = false; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index c53bd6443e..da82c7afb8 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -359,6 +359,7 @@ public: #define FN_MAX_LIMIT_LIST \ FN_MAX_LIMIT_ELEM(ComputeSharedMemorySize) \ + FN_MAX_LIMIT_ELEM(ComputeWorkGroupInvocations) \ FN_MAX_LIMIT_ELEM(PerStageDescriptorSampledImages) \ FN_MAX_LIMIT_ELEM(PerStageResources) \ FN_MAX_LIMIT_ELEM(DescriptorSetSamplers) \ From db922e50e36f5ea641fbb88e5b2668a7a2ab1694 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 27 Aug 2026 23:37:56 -0400 Subject: [PATCH 02/26] Another check on feedback loop --- src/video_core/renderer_vulkan/vk_compute_pass.cpp | 4 ++++ src/video_core/texture_cache/texture_cache.h | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 1fdefa1010..45e57e771a 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -876,6 +876,8 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk( }); } +namespace { + constexpr u32 UNSWIZZLE_BINDING_INPUT_BUFFER = 0; constexpr u32 UNSWIZZLE_BINDING_OUTPUT_IMAGE = 1; constexpr size_t UNSWIZZLE_NUM_BINDINGS = 2; @@ -1010,6 +1012,8 @@ void RecordUnswizzleExitBarrier(Scheduler& scheduler, VkImage vk_image, }); } +} // Anonymous namespace + BlockLinearUnswizzle2DPass::BlockLinearUnswizzle2DPass( const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue_) diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 640bd38407..c9e1af6951 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -279,11 +279,11 @@ void TextureCache

::CheckFeedbackLoop(std::span views) { const ImageId view_image_id = slot_image_views[view.id].image_id; { - bool is_continue = false; + bool is_feedback = false; for (size_t i = 0; i < 8; ++i) - is_continue |= (rt_active_mask & (1u << i)) && view_image_id == rt_image_id[i]; - if (is_continue) - continue; + is_feedback |= (rt_active_mask & (1u << i)) && view_image_id == rt_image_id[i]; + if (is_feedback) + return true; } if (depth_active && view_image_id == rt_depth_image_id) { return true; From ce0b15dcf74273c4d2148178528f46a40d0e6203 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 00:09:30 -0400 Subject: [PATCH 03/26] Just another change --- .../renderer_vulkan/vk_texture_cache.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index e92f6e5e18..58362a65f4 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1020,6 +1020,20 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched view_formats[index_a].push_back(view_info.format); } } + if (IsPixelFormatASTC(image_format) || IsPixelFormatBCn(image_format)) { + continue; + } + const PixelFormat unswizzle_format = + UnswizzleViewFormat(VideoCore::Surface::BytesPerBlock(image_format)); + if (unswizzle_format == PixelFormat::Invalid) { + continue; + } + const auto unswizzle_info = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, unswizzle_format); + auto& formats = view_formats[index_a]; + if (std::ranges::find(formats, unswizzle_info.format) == formats.end()) { + formats.push_back(unswizzle_info.format); + } } if (Settings::values.gpu_unswizzle_enabled.GetValue()) { From db2bdde9b32d78d9991261fb56291458f1481f2a Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 00:34:35 -0400 Subject: [PATCH 04/26] Revert "Just another change" This reverts commit 2ea449f71af4cdd887f4fad6be44ba624c1a40fc. --- .../renderer_vulkan/vk_texture_cache.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 58362a65f4..e92f6e5e18 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1020,20 +1020,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched view_formats[index_a].push_back(view_info.format); } } - if (IsPixelFormatASTC(image_format) || IsPixelFormatBCn(image_format)) { - continue; - } - const PixelFormat unswizzle_format = - UnswizzleViewFormat(VideoCore::Surface::BytesPerBlock(image_format)); - if (unswizzle_format == PixelFormat::Invalid) { - continue; - } - const auto unswizzle_info = - MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, unswizzle_format); - auto& formats = view_formats[index_a]; - if (std::ranges::find(formats, unswizzle_info.format) == formats.end()) { - formats.push_back(unswizzle_info.format); - } } if (Settings::values.gpu_unswizzle_enabled.GetValue()) { From 92c52342b8ec9556549699f2a0c3d05efaff382f Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 00:40:23 -0400 Subject: [PATCH 05/26] Another try on the 3D pass --- src/video_core/host_shaders/CMakeLists.txt | 1 + .../block_linear_unswizzle_3d.comp | 6 ++- .../renderer_vulkan/vk_compute_pass.cpp | 50 +++++++++++++++++++ .../renderer_vulkan/vk_compute_pass.h | 15 ++++++ .../renderer_vulkan/vk_texture_cache.cpp | 10 +++- .../renderer_vulkan/vk_texture_cache.h | 1 + 6 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index 33d2bdbe16..ed48ed7973 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -209,6 +209,7 @@ endforeach() set(SHADER_DEFINE_VARIANTS "block_linear_unswizzle_2d.comp|nonarrow|HAS_EXTENDED_TYPES=0" "pitch_unswizzle.comp|nonarrow|HAS_EXTENDED_TYPES=0" + "block_linear_unswizzle_3d.comp|nonarrow|HAS_EXTENDED_TYPES=0" ) foreach(VARIANT IN ITEMS ${SHADER_DEFINE_VARIANTS}) diff --git a/src/video_core/host_shaders/block_linear_unswizzle_3d.comp b/src/video_core/host_shaders/block_linear_unswizzle_3d.comp index 34e4f93307..f89ed10858 100644 --- a/src/video_core/host_shaders/block_linear_unswizzle_3d.comp +++ b/src/video_core/host_shaders/block_linear_unswizzle_3d.comp @@ -5,9 +5,13 @@ #ifdef VULKAN +#ifndef HAS_EXTENDED_TYPES +#define HAS_EXTENDED_TYPES 1 +#endif +#if HAS_EXTENDED_TYPES #extension GL_EXT_shader_16bit_storage : require #extension GL_EXT_shader_8bit_storage : require -#define HAS_EXTENDED_TYPES 1 +#endif #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define END_PUSH_CONSTANTS }; #define UNIFORM(n) diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 45e57e771a..adc1d1f2e3 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -25,6 +25,8 @@ #include "video_core/host_shaders/block_linear_unswizzle_2d_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_2d_nonarrow_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_comp_spv.h" +#include "video_core/host_shaders/block_linear_unswizzle_3d_nonarrow_comp_spv.h" #include "video_core/host_shaders/pitch_unswizzle_comp_spv.h" #include "video_core/host_shaders/pitch_unswizzle_nonarrow_comp_spv.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" @@ -1063,6 +1065,54 @@ void BlockLinearUnswizzle2DPass::Unswizzle( RecordUnswizzleExitBarrier(scheduler, vk_image, aspect_mask); } +BlockLinearUnswizzleImage3DPass::BlockLinearUnswizzleImage3DPass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_) + : ComputePass(device_, scheduler_, descriptor_pool_, UNSWIZZLE_DESCRIPTOR_SET_BINDINGS, + UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE, UNSWIZZLE_BANK_INFO, + COMPUTE_PUSH_CONSTANT_RANGE, + UnswizzleSpv(device_, BLOCK_LINEAR_UNSWIZZLE_3D_COMP_SPV, + BLOCK_LINEAR_UNSWIZZLE_3D_NONARROW_COMP_SPV)), + scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} + +BlockLinearUnswizzleImage3DPass::~BlockLinearUnswizzleImage3DPass() = default; + +void BlockLinearUnswizzleImage3DPass::Unswizzle( + Image& image, const StagingBufferRef& map, + std::span swizzles) { + using namespace VideoCommon::Accelerated; + scheduler.RequestOutsideRenderPassOperationContext(); + const VkPipeline vk_pipeline = *pipeline; + const VkImageAspectFlags aspect_mask = image.AspectMask(); + const VkImage vk_image = image.Handle(); + const bool is_initialized = image.ExchangeInitialization(); + RecordUnswizzleEntryBarrier(scheduler, vk_pipeline, vk_image, aspect_mask, is_initialized); + + for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { + const size_t input_offset = swizzle.buffer_offset + map.offset; + const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 16U); + const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U); + const u32 num_dispatches_z = Common::DivCeil(swizzle.num_tiles.depth, 8U); + + compute_pass_descriptor_queue.Acquire(scheduler, 2); + compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); + const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; + + const auto params = MakeBlockLinearSwizzle3DParams(swizzle, image.info); + scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params, + descriptor_data](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet set = descriptor_allocator.Commit(); + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); + cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); + cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z); + }); + } + RecordUnswizzleExitBarrier(scheduler, vk_image, aspect_mask); +} + PitchUnswizzlePass::PitchUnswizzlePass( const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_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 117f228bee..519ca078ab 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -179,6 +179,21 @@ private: ComputePassDescriptorQueue& compute_pass_descriptor_queue; }; +class BlockLinearUnswizzleImage3DPass final : public ComputePass { +public: + explicit BlockLinearUnswizzleImage3DPass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_); + ~BlockLinearUnswizzleImage3DPass(); + + void Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles); + +private: + Scheduler& scheduler; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; +}; + class PitchUnswizzlePass final : public ComputePass { public: explicit PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_, diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index e92f6e5e18..0d740a87be 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -191,7 +191,8 @@ constexpr u32 UNSWIZZLE_WORKGROUP_INVOCATIONS = 32 * 32; if (info.num_samples > 1) { return false; } - if (info.type != ImageType::e2D && info.type != ImageType::Linear) { + if (info.type != ImageType::e2D && info.type != ImageType::e3D && + info.type != ImageType::Linear) { return false; } const PixelFormat view_format = @@ -1029,6 +1030,8 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched if (SupportsAcceleratedUnswizzleDevice(device)) { bl_unswizzle_2d_pass.emplace(device, scheduler, descriptor_pool, compute_pass_descriptor_queue); + bl_unswizzle_image_3d_pass.emplace(device, scheduler, descriptor_pool, + compute_pass_descriptor_queue); pitch_unswizzle_pass.emplace(device, scheduler, descriptor_pool, compute_pass_descriptor_queue); } @@ -3228,6 +3231,11 @@ void TextureCacheRuntime::AccelerateImageUpload( return bl_unswizzle_2d_pass->Unswizzle(image, map, swizzles); } + if (bl_unswizzle_image_3d_pass && image.info.type == ImageType::e3D && + !IsPixelFormatBCn(image.info.format)) { + return bl_unswizzle_image_3d_pass->Unswizzle(image, map, swizzles); + } + if (pitch_unswizzle_pass && image.info.type == ImageType::Linear) { return pitch_unswizzle_pass->Unswizzle(image, map, swizzles); } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 271c7d5caf..1afb225eaa 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -160,6 +160,7 @@ public: std::optional bl3d_unswizzle_pass; std::optional bl_unswizzle_2d_pass; + std::optional bl_unswizzle_image_3d_pass; std::optional pitch_unswizzle_pass; const Settings::ResolutionScalingInfo& resolution; std::array, VideoCore::Surface::MaxPixelFormat> view_formats; From 3cfac4abcde6e50a4df9b8657fbde24f7725a118 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 01:19:16 -0400 Subject: [PATCH 06/26] Just a small touch on the 3d swizzle params --- src/video_core/texture_cache/accelerated_swizzle.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/video_core/texture_cache/accelerated_swizzle.h b/src/video_core/texture_cache/accelerated_swizzle.h index d4250da689..11e46aa531 100644 --- a/src/video_core/texture_cache/accelerated_swizzle.h +++ b/src/video_core/texture_cache/accelerated_swizzle.h @@ -23,8 +23,8 @@ struct BlockLinearSwizzle2DParams { }; struct BlockLinearSwizzle3DParams { - std::array origin; - std::array destination; + alignas(16) std::array origin; + alignas(16) std::array destination; u32 bytes_per_block_log2; u32 slice_size; u32 block_size; From 47b310266e20c4439642ac39b855502ca27e710b Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 02:44:21 -0400 Subject: [PATCH 07/26] Another try to fix QCOM driver, dear god. --- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 13 +++---------- src/video_core/texture_cache/accelerated_swizzle.h | 3 +++ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 0d740a87be..d9c9a807cc 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -717,18 +717,11 @@ 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, diff --git a/src/video_core/texture_cache/accelerated_swizzle.h b/src/video_core/texture_cache/accelerated_swizzle.h index 11e46aa531..97ce7b8a18 100644 --- a/src/video_core/texture_cache/accelerated_swizzle.h +++ b/src/video_core/texture_cache/accelerated_swizzle.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later From 8242e54e42bdb43e14c42abc7d2dff194725ad01 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 03:28:10 -0400 Subject: [PATCH 08/26] This is so fucking tiring --- src/common/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/settings.cpp b/src/common/settings.cpp index bc46b61bb4..8fd97a7367 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -369,7 +369,7 @@ void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info) } info.up_factor = static_cast(info.up_scale) / (1U << info.down_shift); info.down_factor = static_cast(1U << info.down_shift) / info.up_scale; - info.active = info.up_scale != 1 || info.down_shift != 0; + info.active = true; } void UpdateRescalingInfo() { From 3e61e6da872ee984820b5d72a6e4b2e0b21ab945 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 04:00:11 -0400 Subject: [PATCH 09/26] AQUI LLEGO TU LOBITO, MI LOBA. --- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index d9c9a807cc..a3babccb3c 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2440,6 +2440,7 @@ bool Image::ScaleUp(bool ignore) { runtime->ViewFormats(info.format)); ignore = false; } + ignore = true; current_image = &Image::scaled_image; if (ignore) { return true; @@ -2468,6 +2469,7 @@ bool Image::ScaleDown(bool ignore) { } ASSERT(info.type != ImageType::Linear); flags &= ~ImageFlagBits::Rescaled; + ignore = true; current_image = &Image::original_image; if (ignore) { return true; From 75519010d2866f20025a90269b9a13e65db505bd Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 14:28:09 -0400 Subject: [PATCH 10/26] Refining fix --- src/common/settings.cpp | 2 +- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 2 -- src/video_core/texture_cache/texture_cache.h | 5 +++++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 8fd97a7367..bc46b61bb4 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -369,7 +369,7 @@ void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info) } info.up_factor = static_cast(info.up_scale) / (1U << info.down_shift); info.down_factor = static_cast(1U << info.down_shift) / info.up_scale; - info.active = true; + info.active = info.up_scale != 1 || info.down_shift != 0; } void UpdateRescalingInfo() { diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index a3babccb3c..d9c9a807cc 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2440,7 +2440,6 @@ bool Image::ScaleUp(bool ignore) { runtime->ViewFormats(info.format)); ignore = false; } - ignore = true; current_image = &Image::scaled_image; if (ignore) { return true; @@ -2469,7 +2468,6 @@ bool Image::ScaleDown(bool ignore) { } ASSERT(info.type != ImageType::Linear); flags &= ~ImageFlagBits::Rescaled; - ignore = true; current_image = &Image::original_image; if (ignore) { return true; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index c9e1af6951..0cea976d85 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1122,6 +1122,11 @@ void TextureCache

::RefreshContents(Image& image, ImageId image_id) { TrackImage(image, image_id); + if (True(image.flags & ImageFlagBits::GpuModified) && image.info.rescaleable) { + runtime.TransitionImageLayout(image); + return; + } + if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); runtime.TransitionImageLayout(image); From 15de6e0e08d4fdbb70be47207b1ac2d129f0501e Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 15:29:59 -0400 Subject: [PATCH 11/26] AAAAAAH --- src/video_core/texture_cache/texture_cache.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 0cea976d85..2c3eab74c6 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1122,11 +1122,6 @@ void TextureCache

::RefreshContents(Image& image, ImageId image_id) { TrackImage(image, image_id); - if (True(image.flags & ImageFlagBits::GpuModified) && image.info.rescaleable) { - runtime.TransitionImageLayout(image); - return; - } - if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); runtime.TransitionImageLayout(image); @@ -1270,6 +1265,9 @@ ImageId TextureCache

::FindImage(const ImageInfo& info, GPUVAddr gpu_addr, template bool TextureCache

::ImageCanRescale(ImageBase& image) { + if (!Settings::values.resolution_info.active) { + return false; + } if (!image.info.rescaleable) { return false; } From ee5049f0d3a8c5a214932101fbc516ab413fcd9e Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 15:59:53 -0400 Subject: [PATCH 12/26] Mercy please --- src/common/settings.cpp | 2 +- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/common/settings.cpp b/src/common/settings.cpp index bc46b61bb4..8fd97a7367 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -369,7 +369,7 @@ void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info) } info.up_factor = static_cast(info.up_scale) / (1U << info.down_shift); info.down_factor = static_cast(1U << info.down_shift) / info.up_scale; - info.active = info.up_scale != 1 || info.down_shift != 0; + info.active = true; } void UpdateRescalingInfo() { diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index d9c9a807cc..1184eaf74c 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2440,7 +2440,7 @@ bool Image::ScaleUp(bool ignore) { runtime->ViewFormats(info.format)); ignore = false; } - current_image = &Image::scaled_image; + ignore = true; if (ignore) { return true; } @@ -2468,6 +2468,7 @@ bool Image::ScaleDown(bool ignore) { } ASSERT(info.type != ImageType::Linear); flags &= ~ImageFlagBits::Rescaled; + ignore = true; current_image = &Image::original_image; if (ignore) { return true; From 7e55b4b5639ddb9b3621874afc19256b2c304f66 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 17:17:37 -0400 Subject: [PATCH 13/26] =?UTF-8?q?Dale=20a=20tu=20cuerpo=20alegr=C3=ADa=20m?= =?UTF-8?q?acarena?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/settings.cpp | 2 +- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 3 +-- src/video_core/texture_cache/texture_cache.h | 5 +++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 8fd97a7367..bc46b61bb4 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -369,7 +369,7 @@ void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info) } info.up_factor = static_cast(info.up_scale) / (1U << info.down_shift); info.down_factor = static_cast(1U << info.down_shift) / info.up_scale; - info.active = true; + info.active = info.up_scale != 1 || info.down_shift != 0; } void UpdateRescalingInfo() { diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 1184eaf74c..d9c9a807cc 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2440,7 +2440,7 @@ bool Image::ScaleUp(bool ignore) { runtime->ViewFormats(info.format)); ignore = false; } - ignore = true; + current_image = &Image::scaled_image; if (ignore) { return true; } @@ -2468,7 +2468,6 @@ bool Image::ScaleDown(bool ignore) { } ASSERT(info.type != ImageType::Linear); flags &= ~ImageFlagBits::Rescaled; - ignore = true; current_image = &Image::original_image; if (ignore) { return true; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 2c3eab74c6..f11507876f 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1122,6 +1122,11 @@ void TextureCache

::RefreshContents(Image& image, ImageId image_id) { TrackImage(image, image_id); + if (image.info.rescaleable) { + runtime.TransitionImageLayout(image); + return; + } + if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); runtime.TransitionImageLayout(image); From f93e4031ba732cce17b54de5acbe5a43e3912a60 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 17:48:39 -0400 Subject: [PATCH 14/26] tie gpu modified regions to rescalable textures --- src/video_core/texture_cache/texture_cache.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index f11507876f..b78cfd7da0 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1122,7 +1122,8 @@ void TextureCache

::RefreshContents(Image& image, ImageId image_id) { TrackImage(image, image_id); - if (image.info.rescaleable) { + if (image.info.rescaleable && + IsRegionGpuModified(image.cpu_addr, image.guest_size_bytes)) { runtime.TransitionImageLayout(image); return; } From 7e5e326842b0933f1f8aa3eba54e3fd9257c5771 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 20:36:02 -0400 Subject: [PATCH 15/26] Another meow --- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index d9c9a807cc..526293663a 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2449,6 +2449,7 @@ bool Image::ScaleUp(bool ignore) { } if (NeedsScaleHelper()) { if (!BlitScaleHelper(true)) { + flags &= ~ImageFlagBits::Rescaled; current_image = &Image::original_image; return false; } From f29a23738a63db3aa8017f97555e15fe9aa513c2 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 22:12:34 -0400 Subject: [PATCH 16/26] A quick change on the e3D pass --- .../renderer_vulkan/vk_texture_cache.cpp | 6 +- src/video_core/texture_cache/texture_cache.h | 60 ++++++++++--------- .../texture_cache/texture_cache_base.h | 2 + 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 526293663a..99e3f93b57 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2639,6 +2639,10 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI if (device->IsExtAstcDecodeModeSupported() && IsLdrAstcFormat(format_info.format)) { view_next = &astc_decode_mode; } + auto subresource_range = MakeSubresourceRange(aspect_mask, info.range); + if (True(flags & VideoCommon::ImageViewFlagBits::Slice)) { + subresource_range.levelCount = 1; + } const VkImageViewCreateInfo create_info{ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = view_next, @@ -2647,7 +2651,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI .viewType = VkImageViewType{}, .format = format_info.format, .components = swizzle_mapping, - .subresourceRange = MakeSubresourceRange(aspect_mask, info.range), + .subresourceRange = subresource_range, }; const auto create = [&](TextureType tex_type, std::optional num_layers) { VkImageViewCreateInfo ci{create_info}; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index b78cfd7da0..c988e27bda 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -20,6 +20,7 @@ #include "video_core/engines/kepler_compute.h" #include "video_core/guest_memory.h" #include "video_core/host1x/gpu_device_memory_manager.h" +#include "video_core/texture_cache/accelerated_swizzle.h" #include "video_core/texture_cache/image_view_base.h" #include "video_core/texture_cache/samples_helper.h" #include "video_core/texture_cache/texture_cache_base.h" @@ -1163,8 +1164,7 @@ void TextureCache

::UploadImageContents(Image& image, StagingBuffer& staging) const GPUVAddr gpu_addr = image.gpu_addr; if (True(image.flags & ImageFlagBits::AcceleratedUpload)) { - gpu_memory->ReadBlock(gpu_addr, mapped_span.data(), mapped_span.size_bytes(), - VideoCommon::CacheType::NoTextureCache); + gpu_memory->ReadBlockUnsafe(gpu_addr, mapped_span.data(), image.guest_size_bytes); const auto uploads = FullUploadSwizzles(image.info); runtime.AccelerateImageUpload(image, staging, FixSmallVectorADL(uploads), 0, 0); return; @@ -1435,18 +1435,16 @@ void TextureCache

::TickAsyncUnswizzle() { Image& image = slot_images[task.image_id]; if (!task.initialized) { - task.total_size = MapSizeBytes(image); + task.total_size = image.guest_size_bytes; task.staging_buffer = runtime.UploadStagingBuffer(task.total_size, true); - const auto& info = image.info; - const u32 bytes_per_block = BytesPerBlock(info.format); - const u32 width_blocks = Common::DivCeil(info.size.width, 4u); - const u32 height_blocks = Common::DivCeil(info.size.height, 4u); - - const u32 stride = width_blocks * bytes_per_block; - const u32 aligned_height = height_blocks; - task.bytes_per_slice = static_cast(stride) * aligned_height; + const auto layout = FullUploadSwizzles(task.info); + const auto params = + VideoCommon::Accelerated::MakeBlockLinearSwizzle3DParams(layout.front(), task.info); + task.bytes_per_slice = params.slice_size; + task.chunked = task.info.block.depth == 0; task.last_submitted_offset = 0; + task.slices_submitted = 0; task.initialized = true; } @@ -1461,31 +1459,39 @@ void TextureCache

::TickAsyncUnswizzle() { if (copy_amount == 0) copy_amount = task.bytes_per_slice; } - gpu_memory->ReadBlock(image.gpu_addr + task.current_offset, - task.staging_buffer.mapped_span.data() + task.current_offset, - copy_amount); + gpu_memory->ReadBlockUnsafe(image.gpu_addr + task.current_offset, + task.staging_buffer.mapped_span.data() + task.current_offset, + copy_amount); task.current_offset += copy_amount; } const bool is_final_batch = task.current_offset >= task.total_size; - const size_t bytes_ready = task.current_offset - task.last_submitted_offset; - const u32 complete_slices = static_cast(bytes_ready / task.bytes_per_slice); - if (complete_slices >= swizzle_slices_per_batch || (is_final_batch && complete_slices > 0)) { - const u32 z_start = static_cast(task.last_submitted_offset / task.bytes_per_slice); - const u32 slices_to_process = (std::min)(complete_slices, swizzle_slices_per_batch); - const u32 z_count = (std::min)(slices_to_process, image.info.size.depth - z_start); + if (task.chunked) { + const size_t bytes_ready = task.current_offset - task.last_submitted_offset; + const u32 complete_slices = static_cast(bytes_ready / task.bytes_per_slice); - if (z_count > 0) { - const auto uploads = FullUploadSwizzles(task.info); - runtime.AccelerateImageUpload(image, task.staging_buffer, FixSmallVectorADL(uploads), z_start, z_count); - task.last_submitted_offset += (static_cast(z_count) * task.bytes_per_slice); + if (complete_slices >= swizzle_slices_per_batch || (is_final_batch && complete_slices > 0)) { + const u32 z_start = task.slices_submitted; + const u32 slices_to_process = (std::min)(complete_slices, swizzle_slices_per_batch); + const u32 z_count = (std::min)(slices_to_process, image.info.size.depth - z_start); + + if (z_count > 0) { + const auto uploads = FullUploadSwizzles(task.info); + runtime.AccelerateImageUpload(image, task.staging_buffer, + FixSmallVectorADL(uploads), z_start, z_count); + task.last_submitted_offset += static_cast(z_count) * task.bytes_per_slice; + task.slices_submitted += z_count; + } } + } else if (is_final_batch && task.slices_submitted == 0) { + const auto uploads = FullUploadSwizzles(task.info); + runtime.AccelerateImageUpload(image, task.staging_buffer, FixSmallVectorADL(uploads), 0, + image.info.size.depth); + task.slices_submitted = image.info.size.depth; } - // Check if complete - const u32 slices_submitted = static_cast(task.last_submitted_offset / task.bytes_per_slice); - const bool all_slices_submitted = slices_submitted >= image.info.size.depth; + const bool all_slices_submitted = task.slices_submitted >= image.info.size.depth; if (is_final_batch && all_slices_submitted) { runtime.FreeDeferredStagingBuffer(task.staging_buffer); diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 8a2318cc72..a5e5bd4918 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -139,6 +139,8 @@ class TextureCache : public VideoCommon::ChannelSetupCaches Date: Fri, 28 Aug 2026 22:43:21 -0400 Subject: [PATCH 17/26] Blit corrections within the image initialization --- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 11 +++++++++-- src/video_core/renderer_vulkan/vk_texture_cache.h | 7 +++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 99e3f93b57..7b31c6e76d 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2091,7 +2091,7 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset, temp_vk_image, info.format, info.num_samples, {image_copies.data(), image_copies.size()}, false); } - initialized = true; + InitializationFor(current_image) = true; runtime->ReleaseMsaaScratchImage(temp_vk_image); if (is_rescaled) { @@ -2112,7 +2112,7 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset, const VkBuffer src_buffer = buffer; const VkImage vk_image = *original_image; const VkImageAspectFlags vk_aspect_mask = aspect_mask; - const bool was_initialized = std::exchange(initialized, true); + const bool was_initialized = std::exchange(InitializationFor(&Image::original_image), true); scheduler->Record([src_buffer, vk_image, vk_aspect_mask, was_initialized, vk_copies](vk::CommandBuffer cmdbuf) { @@ -2387,6 +2387,13 @@ std::vector& Image::StorageViewsFor(vk::Image Image::*image) { return storage_image_views; } +bool& Image::InitializationFor(vk::Image Image::*image) noexcept { + if (image == &Image::scaled_image) { + return scaled_initialized; + } + return original_initialized; +} + VkImageView Image::StorageImageView(s32 level) noexcept { const bool astc_decode = WillUseAcceleratedAstcDecode(runtime->device, info); const bool unswizzle_upload = diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 1afb225eaa..29133d6bfb 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -353,7 +353,7 @@ public: /// Returns true when the image is already initialized and mark it as initialized [[nodiscard]] bool ExchangeInitialization() noexcept { - return std::exchange(initialized, true); + return std::exchange(InitializationFor(current_image), true); } VkImageView StorageImageView(s32 level) noexcept; @@ -375,6 +375,8 @@ private: std::vector& StorageViewsFor(vk::Image Image::*image); + bool& InitializationFor(vk::Image Image::*image) noexcept; + Scheduler* scheduler{}; TextureCacheRuntime* runtime{}; @@ -394,7 +396,8 @@ private: std::vector storage_image_views; std::vector scaled_storage_image_views; VkImageAspectFlags aspect_mask = 0; - bool initialized = false; + bool original_initialized = false; + bool scaled_initialized = false; std::optional scale_framebuffer; std::optional normal_framebuffer; From eb18c5b8bd59bd2077f0ddd3aab9c52d4e3ecd26 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 28 Aug 2026 23:16:49 -0400 Subject: [PATCH 18/26] Removing redundant stagging per downloaded and swizzled image --- src/video_core/texture_cache/texture_cache.h | 23 +++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index c988e27bda..821e0a0f56 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -627,14 +627,25 @@ void TextureCache

::DownloadMemory(DAddr cpu_addr, size_t size) { std::ranges::sort(images, [this](ImageId lhs, ImageId rhs) { return slot_images[lhs].modification_tick < slot_images[rhs].modification_tick; }); + size_t total_size_bytes = 0; + for (const ImageId image_id : images) { + total_size_bytes += slot_images[image_id].unswizzled_size_bytes; + } + auto download_map = runtime.DownloadStagingBuffer(total_size_bytes); for (const ImageId image_id : images) { Image& image = slot_images[image_id]; - auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes); const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); - image.DownloadMemory(map, copies); - runtime.Finish(); - SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span, + image.DownloadMemory(download_map, copies); + download_map.offset += image.unswizzled_size_bytes; + } + runtime.Finish(); + std::span download_span = download_map.mapped_span; + for (const ImageId image_id : images) { + const ImageBase& image = slot_images[image_id]; + const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); + SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, download_span, swizzle_data_buffer); + download_span = download_span.subspan(image.unswizzled_size_bytes); } } @@ -1361,12 +1372,12 @@ void TextureCache

::QueueAsyncDecode(Image& image, ImageId image_id) { decode->image_id = image_id; async_decodes.push_back(std::move(decode)); - std::vector local_unswizzle_data_buffer(image.unswizzled_size_bytes, 0); + Common::ScratchBuffer local_unswizzle_data_buffer(image.unswizzled_size_bytes); Tegra::Memory::GpuGuestMemory swizzle_data(*gpu_memory, image.gpu_addr, image.guest_size_bytes, &swizzle_data_buffer); auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data, local_unswizzle_data_buffer); const size_t out_size = MapSizeBytes(image); - auto func = [out_size, copies, info = image.info, + auto func = [out_size, copies = std::move(copies), info = image.info, input = std::move(local_unswizzle_data_buffer), async_decode = decode_ptr]() mutable { async_decode->decoded_data.resize_destructive(out_size); From f229172d3b4ed9feed6da391ca7ae60606e5180b Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 29 Aug 2026 01:37:56 -0400 Subject: [PATCH 19/26] Some cleanups on the vertex buffer path --- src/video_core/buffer_cache/buffer_base.h | 20 ------ src/video_core/buffer_cache/buffer_cache.h | 68 +++++++------------ .../buffer_cache/buffer_cache_base.h | 6 +- .../renderer_opengl/gl_buffer_cache.cpp | 16 ----- .../renderer_opengl/gl_buffer_cache.h | 2 - .../renderer_vulkan/vk_buffer_cache.cpp | 23 ------- .../renderer_vulkan/vk_buffer_cache.h | 2 - .../renderer_vulkan/vk_graphics_pipeline.cpp | 10 +-- .../renderer_vulkan/vk_rasterizer.cpp | 10 ++- .../vulkan_common/vulkan_device.cpp | 5 ++ src/video_core/vulkan_common/vulkan_device.h | 29 +++++++- 11 files changed, 74 insertions(+), 117 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index d7eb21612f..a5d7774dc3 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h @@ -20,8 +20,6 @@ namespace VideoCommon { enum class BufferFlagBits { Picked = 1 << 0, - CachedWrites = 1 << 1, - PreemtiveDownload = 1 << 2, }; DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits) @@ -58,15 +56,6 @@ public: flags |= BufferFlagBits::Picked; } - void MarkPreemtiveDownload() noexcept { - flags |= BufferFlagBits::PreemtiveDownload; - } - - /// Unmark buffer as picked - void Unpick() noexcept { - flags &= ~BufferFlagBits::Picked; - } - /// Increases the likeliness of this being a stream buffer void IncreaseStreamScore(int score) noexcept { stream_score += score; @@ -87,15 +76,6 @@ public: return True(flags & BufferFlagBits::Picked); } - /// Returns true when the buffer has pending cached writes - [[nodiscard]] bool HasCachedWrites() const noexcept { - return True(flags & BufferFlagBits::CachedWrites); - } - - bool IsPreemtiveDownload() const noexcept { - return True(flags & BufferFlagBits::PreemtiveDownload); - } - /// Returns the base CPU address of the buffer [[nodiscard]] VAddr CpuAddr() const noexcept { return cpu_addr; diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index e40aad1fb5..979b2aefe5 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include @@ -121,25 +122,6 @@ void BufferCache

::WriteMemory(DAddr device_addr, u64 size) { memory_tracker.MarkRegionAsCpuModified(device_addr, size); } -template -void BufferCache

::CachedWriteMemory(DAddr device_addr, u64 size) { - const bool is_dirty = IsRegionRegistered(device_addr, size); - if (!is_dirty) { - return; - } - DAddr aligned_start = Common::AlignDown(device_addr, DEVICE_PAGESIZE); - DAddr aligned_end = Common::AlignUp(device_addr + size, DEVICE_PAGESIZE); - if (!IsRegionGpuModified(aligned_start, aligned_end - aligned_start)) { - WriteMemory(device_addr, size); - return; - } - - tmp_buffer.resize_destructive(size); - device_memory.ReadBlockUnsafe(device_addr, tmp_buffer.data(), size); - - InlineMemoryImplementation(device_addr, size, tmp_buffer); -} - template bool BufferCache

::OnCPUWrite(DAddr device_addr, u64 size) { const bool is_dirty = IsRegionRegistered(device_addr, size); @@ -422,7 +404,7 @@ void BufferCache

::UnbindGraphicsStorageBuffers(size_t stage) { } template -bool BufferCache

::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, +void BufferCache

::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset, bool is_written) { const bool already_enabled = ((channel_state->enabled_storage_buffers[stage] >> ssbo_index) & 1U) != 0; @@ -433,7 +415,7 @@ bool BufferCache

::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, LOG_WARNING(HW_GPU, "Skipping graphics storage buffer {} due to driver limit {}", ssbo_index, max_bindings); - return false; + return; } } } @@ -449,7 +431,6 @@ bool BufferCache

::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset; channel_state->storage_buffers[stage][ssbo_index] = StorageBufferBinding(ssbo_addr, cbuf_index, is_written); - return (channel_state->storage_buffers[stage][ssbo_index].buffer_id != NULL_BUFFER_ID); } template @@ -762,16 +743,6 @@ void BufferCache

::BindHostIndexBuffer() { } } -template -void BufferCache

::BindHostVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, - u32 stride) { - if constexpr (IS_OPENGL) { - runtime.BindVertexBuffer(index, buffer, offset, size, stride); - } else { - runtime.BindVertexBuffer(index, buffer.Handle(), offset, size, stride); - } -} - template Binding& BufferCache

::VertexBufferSlot(u32 index) { ASSERT(index < NUM_VERTEX_BUFFERS); @@ -1251,9 +1222,14 @@ void BufferCache

::UpdateIndexBuffer() { const GPUVAddr gpu_addr_begin = index_buffer_ref.StartAddress(); const GPUVAddr gpu_addr_end = index_buffer_ref.EndAddress(); const std::optional device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin); - const u32 address_size = static_cast(gpu_addr_end - gpu_addr_begin); - const u32 draw_size = (index_buffer_ref.count + index_buffer_ref.first) * u32(index_buffer_ref.FormatSizeInBytes()); - const u32 size = (std::min)(address_size, draw_size); + u64 address_size = 0; + if (gpu_addr_end > gpu_addr_begin) { + address_size = (std::min)(gpu_addr_end - gpu_addr_begin, + u64{(std::numeric_limits::max)()}); + } + const u64 draw_size = (u64{index_buffer_ref.count} + u64{index_buffer_ref.first}) * + u64{index_buffer_ref.FormatSizeInBytes()}; + const u32 size = static_cast((std::min)(address_size, draw_size)); if (size == 0 || !device_addr) { channel_state->index_buffer = NULL_BINDING; return; @@ -1288,15 +1264,22 @@ void BufferCache

::UpdateVertexBuffer(u32 index) { const GPUVAddr gpu_addr_begin = array.Address(); const GPUVAddr gpu_addr_end = limit.Address() + 1; const std::optional device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin); - const u32 address_size = static_cast(gpu_addr_end - gpu_addr_begin); - u32 size = address_size; // TODO: Analyze stride and number of vertices - if (array.enable == 0 || size == 0 || !device_addr) { + if (array.enable == 0 || !device_addr || gpu_addr_end <= gpu_addr_begin) { channel_state->vertex_buffers[index] = NULL_BINDING; UpdateVertexBufferSlot(index, NULL_BINDING); return; } - if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) { - size = static_cast(gpu_memory->MaxContinuousRange(gpu_addr_begin, size)); + // TODO: Analyze stride and number of vertices + u64 address_size = (std::min)(gpu_addr_end - gpu_addr_begin, + u64{(std::numeric_limits::max)()}); + if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || address_size >= 64_MiB) { + address_size = gpu_memory->MaxContinuousRange(gpu_addr_begin, address_size); + } + const u32 size = static_cast(address_size); + if (size == 0) { + channel_state->vertex_buffers[index] = NULL_BINDING; + UpdateVertexBufferSlot(index, NULL_BINDING); + return; } const BufferId buffer_id = FindBuffer(*device_addr, size); const Binding binding{ @@ -1578,9 +1561,10 @@ template BufferId BufferCache

::CreateBuffer(DAddr device_addr, u32 wanted_size) { DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE); device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE); - wanted_size = static_cast(device_addr_end - device_addr); + constexpr u64 max_buffer_size = u64{(std::numeric_limits::max)()}; + wanted_size = static_cast((std::min)(device_addr_end - device_addr, max_buffer_size)); const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size); - const u32 size = static_cast(overlap.end - overlap.begin); + const u32 size = static_cast((std::min)(overlap.end - overlap.begin, max_buffer_size)); const BufferId new_buffer_id = slot_buffers.insert(runtime, overlap.begin, size); auto& new_buffer = slot_buffers[new_buffer_id]; const size_t size_bytes = new_buffer.SizeBytes(); diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 0a521192c6..112c81bff5 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -217,8 +217,6 @@ public: void WriteMemory(DAddr device_addr, u64 size); - void CachedWriteMemory(DAddr device_addr, u64 size); - bool OnCPUWrite(DAddr device_addr, u64 size); void DownloadMemory(DAddr device_addr, u64 size); @@ -248,7 +246,7 @@ public: void UnbindGraphicsStorageBuffers(size_t stage); - bool BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset, + void BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset, bool is_written); void UnbindGraphicsTextureBuffers(size_t stage); @@ -376,8 +374,6 @@ private: void BindHostTransformFeedbackBuffers(); - void BindHostVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride); - void BindHostComputeUniformBuffers(); void BindHostComputeStorageBuffers(); diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index daff57bd00..ada65ae756 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -226,22 +226,6 @@ void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) { } } -void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, - u32 stride) { - if (index >= max_attributes) { - return; - } - if (has_unified_vertex_buffers) { - buffer.MakeResident(GL_READ_ONLY); - glBindVertexBuffer(index, 0, 0, static_cast(stride)); - glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, index, - buffer.HostGpuAddr() + offset, static_cast(size)); - } else { - glBindVertexBuffer(index, buffer.Handle(), static_cast(offset), - static_cast(stride)); - } -} - void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { // TODO: Should HostBindings provide the correct runtime types to avoid these transforms? std::array buffer_handles; diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index 09fa3a59af..e23dc33f74 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -99,8 +99,6 @@ public: void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); - void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride); - void BindVertexBuffers(VideoCommon::HostBindings& bindings); void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size); diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index a734bd049c..168083d23c 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -585,29 +585,6 @@ void BufferCacheRuntime::BindQuadIndexBuffer(PrimitiveTopology topology, u32 fir } } -void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride) { - if (index >= device.GetMaxVertexInputBindings()) { - return; - } - if (device.IsExtExtendedDynamicStateSupported()) { - scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) { - const VkDeviceSize vk_offset = buffer != VK_NULL_HANDLE ? offset : 0; - const VkDeviceSize vk_size = buffer != VK_NULL_HANDLE ? size : VK_WHOLE_SIZE; - const VkDeviceSize vk_stride = stride; - cmdbuf.BindVertexBuffers2EXT(index, 1, &buffer, &vk_offset, &vk_size, &vk_stride); - }); - } else { - if (!device.HasNullDescriptor() && buffer == VK_NULL_HANDLE) { - ReserveNullBuffer(); - buffer = *null_buffer; - offset = 0; - } - scheduler.Record([index, buffer, offset](vk::CommandBuffer cmdbuf) { - cmdbuf.BindVertexBuffer(index, buffer, offset); - }); - } -} - void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { boost::container::static_vector buffer_handles(bindings.buffers.size()); for (u32 i = 0; i < bindings.buffers.size(); ++i) { diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 9b3dd50eaa..d23ebe2357 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -138,8 +138,6 @@ public: void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count); - void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride); - void BindVertexBuffers(VideoCommon::HostBindings& bindings); void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 8b5b0bc9c5..b2c6cffa82 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -694,9 +694,11 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { const size_t num_vertex_arrays = (std::min)( Maxwell::NumVertexArrays, static_cast(device.GetMaxVertexInputBindings())); for (size_t index = 0; index < num_vertex_arrays; ++index) { - const bool instanced = key.state.binding_divisors[index] != 0; - const auto rate = - instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; + const bool instanced = ((key.state.enabled_divisors >> index) & 1) != 0; + auto rate = VK_VERTEX_INPUT_RATE_VERTEX; + if (instanced) { + rate = VK_VERTEX_INPUT_RATE_INSTANCE; + } vertex_bindings.push_back({ .binding = static_cast(index), .stride = key.state.vertex_strides[index], @@ -705,7 +707,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { if (instanced) { vertex_binding_divisors.push_back({ .binding = static_cast(index), - .divisor = key.state.binding_divisors[index], + .divisor = device.GetVertexAttribDivisor(key.state.binding_divisors[index]), }); } } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 3b0fea2fec..f0a045f1a8 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1917,13 +1917,19 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs) for (u32 binding = 0; binding < max_bindings; ++binding) { const auto& input_binding{regs.vertex_streams[binding]}; const bool is_instanced{regs.vertex_stream_instances.IsInstancingEnabled(binding)}; + auto input_rate = VK_VERTEX_INPUT_RATE_VERTEX; + u32 divisor = 1; + if (is_instanced) { + input_rate = VK_VERTEX_INPUT_RATE_INSTANCE; + divisor = device.GetVertexAttribDivisor(input_binding.frequency); + } bindings.push_back({ .sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT, .pNext = nullptr, .binding = binding, .stride = input_binding.stride, - .inputRate = is_instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX, - .divisor = is_instanced ? input_binding.frequency : 1, + .inputRate = input_rate, + .divisor = divisor, }); } diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index fb3a9c4e7a..0e1896faa6 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1219,6 +1219,11 @@ bool Device::GetSuitability(bool requires_swapchain) { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT; SetNext(next, properties.transform_feedback); } + if (extensions.vertex_attribute_divisor) { + properties.vertex_attribute_divisor.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES_EXT; + SetNext(next, properties.vertex_attribute_divisor); + } if (extensions.maintenance5) { properties.maintenance5.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_PROPERTIES_KHR; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index da82c7afb8..b29fafd838 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -70,6 +70,7 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \ FEATURE(EXT, Robustness2, ROBUSTNESS_2, robustness2) \ FEATURE(EXT, TransformFeedback, TRANSFORM_FEEDBACK, transform_feedback) \ + FEATURE(EXT, VertexAttributeDivisor, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \ FEATURE(EXT, VertexInputDynamicState, VERTEX_INPUT_DYNAMIC_STATE, vertex_input_dynamic_state) \ FEATURE(KHR, Maintenance5, MAINTENANCE_5, maintenance5) \ FEATURE(KHR, Maintenance6, MAINTENANCE_6, maintenance6) \ @@ -92,7 +93,6 @@ VK_DEFINE_HANDLE(VmaAllocator) EXTENSION(EXT, SHADER_STENCIL_EXPORT, shader_stencil_export) \ 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) \ @@ -690,6 +690,32 @@ FN_MAX_LIMIT_LIST return features.host_query_reset.hostQueryReset != VK_FALSE; } + u32 GetMaxVertexAttribDivisor() const { + const u32 reported = properties.vertex_attribute_divisor.maxVertexAttribDivisor; + if (reported == 0) { + return 1; + } + return reported; + } + + bool IsVertexAttributeInstanceRateZeroDivisorSupported() const { + return features.vertex_attribute_divisor.vertexAttributeInstanceRateZeroDivisor == VK_TRUE; + } + + u32 GetVertexAttribDivisor(u32 frequency) const { + const u32 max_divisor = GetMaxVertexAttribDivisor(); + if (frequency == 0) { + if (IsVertexAttributeInstanceRateZeroDivisorSupported()) { + return 0; + } + return max_divisor; + } + if (frequency > max_divisor) { + return max_divisor; + } + return frequency; + } + /// Returns true if the device supports VK_EXT_transform_feedback. bool IsExtTransformFeedbackSupported() const { return extensions.transform_feedback; @@ -1190,6 +1216,7 @@ private: VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer{}; VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{}; VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{}; + VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT vertex_attribute_divisor{}; VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{}; VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{}; VkPhysicalDeviceCustomBorderColorPropertiesEXT custom_border_color{}; From b9239c49449aaacc40702d8ea106383b7274aeaf Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 29 Aug 2026 02:53:00 -0400 Subject: [PATCH 20/26] Add instance draw counts to vertex buffers --- src/video_core/buffer_cache/buffer_cache.h | 35 +++++++++++++++++-- .../buffer_cache/buffer_cache_base.h | 8 +++++ .../renderer_opengl/gl_rasterizer.cpp | 2 ++ .../renderer_vulkan/vk_rasterizer.cpp | 2 ++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 979b2aefe5..cd50970b7a 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1244,6 +1244,20 @@ void BufferCache

::UpdateIndexBuffer() { template void BufferCache

::UpdateVertexBuffers() { auto& flags = maxwell3d->dirty.flags; + const u32 base_instance = maxwell3d->draw_manager.draw_state.base_instance; + if (draw_instance_count != last_draw_instance_count || + base_instance != last_draw_base_instance) { + last_draw_instance_count = draw_instance_count; + last_draw_base_instance = base_instance; + const auto& instances = maxwell3d->regs.vertex_stream_instances; + for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { + if (!instances.IsInstancingEnabled(index)) { + continue; + } + flags[Dirty::VertexBuffer0 + index] = true; + flags[Dirty::VertexBuffers] = true; + } + } if (!maxwell3d->dirty.flags[Dirty::VertexBuffers]) { return; } @@ -1270,9 +1284,24 @@ void BufferCache

::UpdateVertexBuffer(u32 index) { return; } // TODO: Analyze stride and number of vertices - u64 address_size = (std::min)(gpu_addr_end - gpu_addr_begin, - u64{(std::numeric_limits::max)()}); - if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || address_size >= 64_MiB) { + constexpr u64 implausible_size = 64_MiB; + u64 address_size = gpu_addr_end - gpu_addr_begin; + if (address_size > u64{(std::numeric_limits::max)()}) { + address_size = implausible_size; + } + const bool is_instanced = maxwell3d->regs.vertex_stream_instances.IsInstancingEnabled(index); + const u64 stride = static_cast(array.stride); + if (is_instanced && stride != 0 && draw_instance_count != 0) { + const u64 base_instance = static_cast(maxwell3d->draw_manager.draw_state.base_instance); + u64 elements = base_instance + 1; + if (array.frequency != 0) { + elements = (base_instance + static_cast(draw_instance_count) - 1) / + static_cast(array.frequency) + + 1; + } + address_size = (std::min)(address_size, (elements + 1) * stride); + } + if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || address_size >= implausible_size) { address_size = gpu_memory->MaxContinuousRange(gpu_addr_begin, address_size); } const u32 size = static_cast(address_size); diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 112c81bff5..db2a2e1969 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -307,6 +307,10 @@ public: current_draw_indirect = current_draw_indirect_; } + void SetDrawInstanceCount(u32 draw_instance_count_) { + draw_instance_count = draw_instance_count_; + } + [[nodiscard]] std::pair GetDrawIndirectCount(); [[nodiscard]] std::pair GetDrawIndirectBuffer(); @@ -480,6 +484,10 @@ private: const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{}; + u32 draw_instance_count = 0; + u32 last_draw_instance_count = 0; + u32 last_draw_base_instance = 0; + u32 last_index_count = 0; u32 enabled_vertex_buffers_mask = 0; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 1d77d28c46..0e2dc63a56 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -259,6 +259,7 @@ void RasterizerOpenGL::PrepareDraw(bool is_indexed, Func&& draw_func) { } void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) { + buffer_cache.SetDrawInstanceCount(instance_count); PrepareDraw(is_indexed, [this, is_indexed, instance_count](GLenum primitive_mode) { const auto& draw_state = maxwell3d->draw_manager.draw_state; const GLuint base_instance = GLuint(draw_state.base_instance); @@ -304,6 +305,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) { void RasterizerOpenGL::DrawIndirect() { const auto& params = maxwell3d->draw_manager.indirect_state; buffer_cache.SetDrawIndirect(¶ms); + buffer_cache.SetDrawInstanceCount(0); PrepareDraw(params.is_indexed, [this, ¶ms](GLenum primitive_mode) { if (params.is_byte_count) { const GPUVAddr tfb_object_base_addr = params.indirect_start_address - 4U; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index f0a045f1a8..4f766d6104 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -260,6 +260,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { } void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { + buffer_cache.SetDrawInstanceCount(instance_count); PrepareDraw(is_indexed, [this, is_indexed, instance_count] { const auto& draw_state = maxwell3d->draw_manager.draw_state; const u32 num_instances{instance_count}; @@ -295,6 +296,7 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { void RasterizerVulkan::DrawIndirect() { const auto& params = maxwell3d->draw_manager.indirect_state; buffer_cache.SetDrawIndirect(¶ms); + buffer_cache.SetDrawInstanceCount(0); PrepareDraw(params.is_indexed, [this, ¶ms] { const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer(); const auto& buffer = indirect_buffer.first; From 023c4758e90d21b3123e3034021cff07e18ddc68 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 29 Aug 2026 12:03:57 -0400 Subject: [PATCH 21/26] Unify vertex entries from the indexed and non-indexed resolves into a single path --- src/video_core/buffer_cache/buffer_cache.h | 85 +++++++++++++------ .../buffer_cache/buffer_cache_base.h | 9 +- 2 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index cd50970b7a..970f635313 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1168,7 +1168,7 @@ void BufferCache

::DoUpdateGraphicsBuffers(bool is_indexed) { if (is_indexed) { UpdateIndexBuffer(); } - UpdateVertexBuffers(); + UpdateVertexBuffers(is_indexed); UpdateTransformFeedbackBuffers(); for (size_t stage = 0; stage < NUM_STAGES; ++stage) { UpdateUniformBuffers(stage); @@ -1242,21 +1242,56 @@ void BufferCache

::UpdateIndexBuffer() { } template -void BufferCache

::UpdateVertexBuffers() { - auto& flags = maxwell3d->dirty.flags; - const u32 base_instance = maxwell3d->draw_manager.draw_state.base_instance; - if (draw_instance_count != last_draw_instance_count || - base_instance != last_draw_base_instance) { - last_draw_instance_count = draw_instance_count; - last_draw_base_instance = base_instance; - const auto& instances = maxwell3d->regs.vertex_stream_instances; - for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { - if (!instances.IsInstancingEnabled(index)) { - continue; - } - flags[Dirty::VertexBuffer0 + index] = true; - flags[Dirty::VertexBuffers] = true; +u64 BufferCache

::DrawVertexBound(u32 index, bool is_indexed) const { + const auto& array = maxwell3d->regs.vertex_streams[index]; + const u64 stride = static_cast(array.stride); + if (array.enable == 0 || stride == 0) { + return 0; + } + const auto& draw_state = maxwell3d->draw_manager.draw_state; + if (maxwell3d->regs.vertex_stream_instances.IsInstancingEnabled(index)) { + if (draw_instance_count == 0) { + return 0; } + const u64 base_instance = static_cast(draw_state.base_instance); + u64 elements = base_instance + 1; + if (array.frequency != 0) { + elements = (base_instance + static_cast(draw_instance_count) - 1) / + static_cast(array.frequency) + + 1; + } + return (elements + 1) * stride; + } + if (!is_indexed) { + const u64 elements = static_cast(draw_state.vertex_buffer.first) + + static_cast(draw_state.vertex_buffer.count); + return (elements + 1) * stride; + } + u64 max_index = 0; + switch (draw_state.index_buffer.format) { + case Maxwell::IndexFormat::UnsignedByte: + max_index = 0xFF; + break; + case Maxwell::IndexFormat::UnsignedShort: + max_index = 0xFFFF; + break; + default: + return 0; + } + const u64 elements = static_cast(draw_state.base_index) + max_index + 1; + return (elements + 1) * stride; +} + +template +void BufferCache

::UpdateVertexBuffers(bool is_indexed) { + auto& flags = maxwell3d->dirty.flags; + for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { + const u64 bound = DrawVertexBound(index, is_indexed); + if (bound <= last_draw_bounds[index]) { + continue; + } + flags[Dirty::VertexBuffer0 + index] = true; + flags[Dirty::VertexBuffers] = true; } if (!maxwell3d->dirty.flags[Dirty::VertexBuffers]) { return; @@ -1264,12 +1299,12 @@ void BufferCache

::UpdateVertexBuffers() { flags[Dirty::VertexBuffers] = false; for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { - UpdateVertexBuffer(index); + UpdateVertexBuffer(index, is_indexed); } } template -void BufferCache

::UpdateVertexBuffer(u32 index) { +void BufferCache

::UpdateVertexBuffer(u32 index, bool is_indexed) { if (!maxwell3d->dirty.flags[Dirty::VertexBuffer0 + index]) { return; } @@ -1289,17 +1324,11 @@ void BufferCache

::UpdateVertexBuffer(u32 index) { if (address_size > u64{(std::numeric_limits::max)()}) { address_size = implausible_size; } - const bool is_instanced = maxwell3d->regs.vertex_stream_instances.IsInstancingEnabled(index); - const u64 stride = static_cast(array.stride); - if (is_instanced && stride != 0 && draw_instance_count != 0) { - const u64 base_instance = static_cast(maxwell3d->draw_manager.draw_state.base_instance); - u64 elements = base_instance + 1; - if (array.frequency != 0) { - elements = (base_instance + static_cast(draw_instance_count) - 1) / - static_cast(array.frequency) + - 1; - } - address_size = (std::min)(address_size, (elements + 1) * stride); + const u64 draw_bound = DrawVertexBound(index, is_indexed); + last_draw_bounds[index] = (std::numeric_limits::max)(); + if (draw_bound != 0) { + last_draw_bounds[index] = draw_bound; + address_size = (std::min)(address_size, draw_bound); } if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || address_size >= implausible_size) { address_size = gpu_memory->MaxContinuousRange(gpu_addr_begin, address_size); diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index db2a2e1969..5ae193f01c 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -390,9 +390,11 @@ private: void UpdateIndexBuffer(); - void UpdateVertexBuffers(); + void UpdateVertexBuffers(bool is_indexed); - void UpdateVertexBuffer(u32 index); + void UpdateVertexBuffer(u32 index, bool is_indexed); + + [[nodiscard]] u64 DrawVertexBound(u32 index, bool is_indexed) const; void UpdateDrawIndirect(); @@ -485,8 +487,7 @@ private: const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{}; u32 draw_instance_count = 0; - u32 last_draw_instance_count = 0; - u32 last_draw_base_instance = 0; + std::array last_draw_bounds{}; u32 last_index_count = 0; From 104bfa3a8f99570ef399a8d1db278a0bd935cb6c Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 29 Aug 2026 14:20:05 -0400 Subject: [PATCH 22/26] Another take for indexing guards --- src/video_core/buffer_cache/buffer_cache.h | 61 +++++++++++++++---- .../buffer_cache/buffer_cache_base.h | 8 ++- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 970f635313..91c5b56213 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1242,7 +1242,38 @@ void BufferCache

::UpdateIndexBuffer() { } template -u64 BufferCache

::DrawVertexBound(u32 index, bool is_indexed) const { +u64 BufferCache

::DrawMaxIndex() { + if (max_index_scanned) { + return cached_max_index; + } + max_index_scanned = true; + cached_max_index = 0; + const auto& index_buffer_ref = maxwell3d->draw_manager.draw_state.index_buffer; + const u32 count = index_buffer_ref.count; + if (count == 0) { + return 0; + } + index_scan_buffer.resize_destructive(count); + gpu_memory->ReadBlockUnsafe(index_buffer_ref.IndexStart(), index_scan_buffer.data(), + size_t{count} * sizeof(u32)); + u32 restart_index = (std::numeric_limits::max)(); + if (maxwell3d->regs.primitive_restart.enabled != 0) { + restart_index = maxwell3d->regs.primitive_restart.index; + } + u32 max_index = 0; + for (u32 i = 0; i < count; ++i) { + const u32 value = index_scan_buffer[i]; + if (value == restart_index) { + continue; + } + max_index = (std::max)(max_index, value); + } + cached_max_index = max_index; + return cached_max_index; +} + +template +u64 BufferCache

::DrawVertexBound(u32 index, bool is_indexed) { const auto& array = maxwell3d->regs.vertex_streams[index]; const u64 stride = static_cast(array.stride); if (array.enable == 0 || stride == 0) { @@ -1267,16 +1298,23 @@ u64 BufferCache

::DrawVertexBound(u32 index, bool is_indexed) const { static_cast(draw_state.vertex_buffer.count); return (elements + 1) * stride; } - u64 max_index = 0; - switch (draw_state.index_buffer.format) { - case Maxwell::IndexFormat::UnsignedByte: - max_index = 0xFF; - break; - case Maxwell::IndexFormat::UnsignedShort: + const auto format = draw_state.index_buffer.format; + u64 max_index = 0xFF; + if (format == Maxwell::IndexFormat::UnsignedShort) { max_index = 0xFFFF; - break; - default: - return 0; + } else if (format != Maxwell::IndexFormat::UnsignedByte) { + const auto& limit = maxwell3d->regs.vertex_stream_limits[index]; + const GPUVAddr gpu_addr_begin = array.Address(); + const GPUVAddr gpu_addr_end = limit.Address() + 1; + if (gpu_addr_end <= gpu_addr_begin) { + return 0; + } + const bool walks = gpu_addr_end - gpu_addr_begin >= IMPLAUSIBLE_VERTEX_SIZE || + !gpu_memory->IsWithinGPUAddressRange(gpu_addr_end); + if (!walks) { + return 0; + } + max_index = DrawMaxIndex(); } const u64 elements = static_cast(draw_state.base_index) + max_index + 1; return (elements + 1) * stride; @@ -1285,6 +1323,7 @@ u64 BufferCache

::DrawVertexBound(u32 index, bool is_indexed) const { template void BufferCache

::UpdateVertexBuffers(bool is_indexed) { auto& flags = maxwell3d->dirty.flags; + max_index_scanned = false; for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { const u64 bound = DrawVertexBound(index, is_indexed); if (bound <= last_draw_bounds[index]) { @@ -1319,7 +1358,7 @@ void BufferCache

::UpdateVertexBuffer(u32 index, bool is_indexed) { return; } // TODO: Analyze stride and number of vertices - constexpr u64 implausible_size = 64_MiB; + constexpr u64 implausible_size = IMPLAUSIBLE_VERTEX_SIZE; u64 address_size = gpu_addr_end - gpu_addr_begin; if (address_size > u64{(std::numeric_limits::max)()}) { address_size = implausible_size; diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 5ae193f01c..c662707d5b 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -51,6 +51,7 @@ constexpr u32 NUM_VERTEX_BUFFERS = 16; #else constexpr u32 NUM_VERTEX_BUFFERS = 32; #endif +constexpr u64 IMPLAUSIBLE_VERTEX_SIZE = 64_MiB; constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4; constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18; constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8; @@ -394,7 +395,9 @@ private: void UpdateVertexBuffer(u32 index, bool is_indexed); - [[nodiscard]] u64 DrawVertexBound(u32 index, bool is_indexed) const; + [[nodiscard]] u64 DrawVertexBound(u32 index, bool is_indexed); + + [[nodiscard]] u64 DrawMaxIndex(); void UpdateDrawIndirect(); @@ -488,6 +491,9 @@ private: u32 draw_instance_count = 0; std::array last_draw_bounds{}; + Common::ScratchBuffer index_scan_buffer; + u64 cached_max_index = 0; + bool max_index_scanned = false; u32 last_index_count = 0; From 883cdead8bac26c64d8f29009677b238140e0949 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 29 Aug 2026 16:36:05 -0400 Subject: [PATCH 23/26] Some sanity on the vertex stream --- src/video_core/buffer_cache/buffer_cache.h | 90 +++++++++++++------ .../buffer_cache/buffer_cache_base.h | 4 + 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 91c5b56213..b23e2ec915 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1272,58 +1272,92 @@ u64 BufferCache

::DrawMaxIndex() { return cached_max_index; } +template +u64 BufferCache

::StreamAttributeExtent(u32 index) { + using VertexAttribute = typename Maxwell::VertexAttribute; + if (stream_extents_valid) { + return stream_extents[index]; + } + stream_extents_valid = true; + stream_extents.fill(0); + for (size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) { + const auto& attribute = maxwell3d->regs.vertex_attrib_format[i]; + if (attribute.constant != 0 || attribute.size == VertexAttribute::Size::Invalid) { + continue; + } + const u32 buffer = attribute.buffer.Value(); + if (buffer >= NUM_VERTEX_BUFFERS) { + continue; + } + const u64 end = static_cast(attribute.offset.Value()) + + static_cast(attribute.SizeInBytes()); + stream_extents[buffer] = (std::max)(stream_extents[buffer], end); + } + return stream_extents[index]; +} + template u64 BufferCache

::DrawVertexBound(u32 index, bool is_indexed) { const auto& array = maxwell3d->regs.vertex_streams[index]; - const u64 stride = static_cast(array.stride); - if (array.enable == 0 || stride == 0) { + if (array.enable == 0) { return 0; } + const u64 extent = StreamAttributeExtent(index); + if (extent == 0) { + return 0; + } + const u64 stride = static_cast(array.stride); + if (stride == 0) { + return extent; + } const auto& draw_state = maxwell3d->draw_manager.draw_state; + u64 elements = 0; if (maxwell3d->regs.vertex_stream_instances.IsInstancingEnabled(index)) { if (draw_instance_count == 0) { return 0; } const u64 base_instance = static_cast(draw_state.base_instance); - u64 elements = base_instance + 1; + elements = base_instance + 1; if (array.frequency != 0) { elements = (base_instance + static_cast(draw_instance_count) - 1) / static_cast(array.frequency) + 1; } - return (elements + 1) * stride; - } - if (!is_indexed) { - const u64 elements = static_cast(draw_state.vertex_buffer.first) + - static_cast(draw_state.vertex_buffer.count); - return (elements + 1) * stride; - } - const auto format = draw_state.index_buffer.format; - u64 max_index = 0xFF; - if (format == Maxwell::IndexFormat::UnsignedShort) { - max_index = 0xFFFF; - } else if (format != Maxwell::IndexFormat::UnsignedByte) { - const auto& limit = maxwell3d->regs.vertex_stream_limits[index]; - const GPUVAddr gpu_addr_begin = array.Address(); - const GPUVAddr gpu_addr_end = limit.Address() + 1; - if (gpu_addr_end <= gpu_addr_begin) { - return 0; + } else if (!is_indexed) { + elements = static_cast(draw_state.vertex_buffer.first) + + static_cast(draw_state.vertex_buffer.count); + } else { + const auto format = draw_state.index_buffer.format; + u64 max_index = 0xFF; + if (format == Maxwell::IndexFormat::UnsignedShort) { + max_index = 0xFFFF; + } else if (format != Maxwell::IndexFormat::UnsignedByte) { + const auto& limit = maxwell3d->regs.vertex_stream_limits[index]; + const GPUVAddr gpu_addr_begin = array.Address(); + const GPUVAddr gpu_addr_end = limit.Address() + 1; + if (gpu_addr_end <= gpu_addr_begin) { + return 0; + } + const bool walks = gpu_addr_end - gpu_addr_begin >= IMPLAUSIBLE_VERTEX_SIZE || + !gpu_memory->IsWithinGPUAddressRange(gpu_addr_end); + if (!walks) { + return 0; + } + max_index = DrawMaxIndex(); } - const bool walks = gpu_addr_end - gpu_addr_begin >= IMPLAUSIBLE_VERTEX_SIZE || - !gpu_memory->IsWithinGPUAddressRange(gpu_addr_end); - if (!walks) { - return 0; - } - max_index = DrawMaxIndex(); + elements = static_cast(draw_state.base_index) + max_index + 1; } - const u64 elements = static_cast(draw_state.base_index) + max_index + 1; - return (elements + 1) * stride; + if (elements == 0) { + return extent; + } + return (elements - 1) * stride + extent; } template void BufferCache

::UpdateVertexBuffers(bool is_indexed) { auto& flags = maxwell3d->dirty.flags; max_index_scanned = false; + stream_extents_valid = false; for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { const u64 bound = DrawVertexBound(index, is_indexed); if (bound <= last_draw_bounds[index]) { diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index c662707d5b..4dba335cc8 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -399,6 +399,8 @@ private: [[nodiscard]] u64 DrawMaxIndex(); + [[nodiscard]] u64 StreamAttributeExtent(u32 index); + void UpdateDrawIndirect(); void UpdateUniformBuffers(size_t stage); @@ -494,6 +496,8 @@ private: Common::ScratchBuffer index_scan_buffer; u64 cached_max_index = 0; bool max_index_scanned = false; + std::array stream_extents{}; + bool stream_extents_valid = false; u32 last_index_count = 0; From 2addf1556e386a3c1b97c36485ae44fc7aeee005 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 29 Aug 2026 17:13:29 -0400 Subject: [PATCH 24/26] Another check --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 4f766d6104..c5191c1a7b 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1292,6 +1292,12 @@ 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 is_float_zeta = regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT || + regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT; + + if (is_float_zeta && !device.IsExtDepthBiasControlSupported()) { + units *= 256.0f; + } if (is_d24 && !device.SupportsD24DepthBuffer()) { static constexpr const size_t length = sizeof(NEEDS_D24) / sizeof(NEEDS_D24[0]); From d43238b9af5334503310ba40753767d3478ce592 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 30 Aug 2026 03:20:51 -0400 Subject: [PATCH 25/26] Another change --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index c5191c1a7b..1be0cc2f19 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -102,6 +102,9 @@ VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t in .maxDepth = src.translate_z + src.scale_z, }; if (!device.IsExtDepthRangeUnrestrictedSupported()) { + if (viewport.minDepth < 0.0f || viewport.maxDepth > 1.0f) { + viewport.maxDepth = 0.5f; + } viewport.minDepth = std::clamp(viewport.minDepth, 0.0f, 1.0f); viewport.maxDepth = std::clamp(viewport.maxDepth, 0.0f, 1.0f); } @@ -1292,12 +1295,6 @@ 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 is_float_zeta = regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT || - regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT; - - if (is_float_zeta && !device.IsExtDepthBiasControlSupported()) { - units *= 256.0f; - } if (is_d24 && !device.SupportsD24DepthBuffer()) { static constexpr const size_t length = sizeof(NEEDS_D24) / sizeof(NEEDS_D24[0]); From 5f0f6e63064aa16df98d3cbceb13991b5dc73ff2 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 1 Sep 2026 21:53:15 -0400 Subject: [PATCH 26/26] Experimenting with scissors offsets --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 1be0cc2f19..96099565a8 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -102,9 +102,6 @@ VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t in .maxDepth = src.translate_z + src.scale_z, }; if (!device.IsExtDepthRangeUnrestrictedSupported()) { - if (viewport.minDepth < 0.0f || viewport.maxDepth > 1.0f) { - viewport.maxDepth = 0.5f; - } viewport.minDepth = std::clamp(viewport.minDepth, 0.0f, 1.0f); viewport.maxDepth = std::clamp(viewport.maxDepth, 0.0f, 1.0f); } @@ -140,10 +137,12 @@ VkRect2D GetScissorState(const Maxwell& regs, size_t index, u32 up_scale = 1, u3 max_y = (std::max)(max_y, 0); if (src.enable) { - scissor.offset.x = scale_up(src.min_x); + const s32 min_x = static_cast(src.min_x.Value()); + const s32 max_x = static_cast(src.max_x.Value()); + scissor.offset.x = scale_up(min_x); scissor.offset.y = scale_up(min_y); - scissor.extent.width = scale_up(src.max_x - src.min_x); - scissor.extent.height = scale_up(max_y - min_y); + scissor.extent.width = scale_up((std::max)(max_x - min_x, 0)); + scissor.extent.height = scale_up((std::max)(max_y - min_y, 0)); } else { scissor.offset.x = 0; scissor.offset.y = 0;