diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 15:37:34 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 15:37:34 -0400 |
| commit | 934ad3b97c12e1d40cf4e1f6094c756873b6af82 (patch) | |
| tree | 5e867981c552d4b74898db22a90230dbd4fda546 /src/video_core/renderer_vulkan | |
| parent | 8393e2912616ce475619900be8cb29e551ff2997 (diff) | |
Keep huge ASTC arrays from a lower mip on the host (title 1 terrain)
Title 1's terrain virtual textures are 8192x8192x68 ASTC 8x8 sRGB arrays with
12 levels: 1.5 GB in guest memory (fully backed) but 23 GB each as the RGBA8
Eden decodes ASTC to, so they were created unbacked and the near terrain
sampled black. A GPU-decoded ASTC 2D array over 1 GiB decoded now keeps only
the levels from the first one that fits in 512 MiB (here 3: 1024x1024, 380
MiB); host mip 0 is that guest level. Uploads read each layer's tail of the
mip chain into a compact buffer and decode only those levels, views shift
their level range, and these images are never downloaded. Normalized
coordinates and derivative LOD address the smaller image correctly; the
nearest terrain is lower resolution than on the console.
Also logs how unbacked images are used (views, render targets, blits, DMA).
Diffstat (limited to 'src/video_core/renderer_vulkan')
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_compute_pass.cpp | 18 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_texture_cache.cpp | 61 |
2 files changed, 72 insertions, 7 deletions
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 2eac00103e..65e0d5f145 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -31,6 +31,7 @@ #include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/texture_cache/accelerated_swizzle.h" #include "video_core/texture_cache/types.h" +#include "video_core/texture_cache/util.h" #include "video_core/textures/decoders.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -558,6 +559,15 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline); }); + // With trimmed host levels the input holds each layer's mip chain from host_level_offset on + // (see TextureCache::UploadImageContents), so its layer stride is that tail's size. + const u32 skip = image.host_level_offset; + const u32 input_layer_stride = + skip != 0 ? image.info.layer_stride - + static_cast<u32>(VideoCommon::FullUploadSwizzles(image.info)[skip].buffer_offset) + : image.info.layer_stride; + const size_t input_size = skip != 0 ? size_t{input_layer_stride} * image.info.resources.layers + : image.guest_size_bytes; 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, 8U); @@ -566,12 +576,14 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, 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)); + input_size - swizzle.buffer_offset); + compute_pass_descriptor_queue.AddImage( + image.StorageImageView(swizzle.level - static_cast<s32>(skip))); const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; // To unswizzle the ASTC data - const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info); + auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info); + params.layer_stride = input_layer_stride; ASSERT(params.origin == (std::array<u32, 3>{0, 0, 0})); ASSERT(params.destination == (std::array<s32, 3>{0, 0, 0})); ASSERT(params.bytes_per_block_log2 == 4); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 7e2420200e..d3f255f05d 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -160,6 +160,40 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { info.size.depth == 1; } +// The host shape of an image whose decoded copy would not fit in VRAM: a GPU-decoded ASTC array +// over 1 GiB as RGBA8 (title 1's 8192x8192x68 ASTC 8x8 virtual textures are 24 GB each) keeps only +// its mip levels from `level_offset` on, so host mip 0 is guest mip `level_offset`. Normalized +// coordinates and derivative LOD still address it correctly; explicit LODs sample one level lower. +[[nodiscard]] ImageInfo TrimmedHostInfo(const Device& device, const ImageInfo& info, u32& level_offset) { + level_offset = 0; + if (!WillUseAcceleratedAstcDecode(device, info) || info.type != ImageType::e2D) { + return info; + } + const auto decoded_bytes = [&](u32 skip) { + u64 bytes = 0; + for (s32 level = static_cast<s32>(skip); level < info.resources.levels; ++level) { + bytes += u64{(std::max)(info.size.width >> level, 1U)} * + (std::max)(info.size.height >> level, 1U) * 4; + } + return bytes * info.resources.layers; + }; + if (decoded_bytes(0) <= (u64{1} << 30)) { + return info; + } + while (level_offset + 1 < static_cast<u32>(info.resources.levels) && + decoded_bytes(level_offset) > (u64{512} << 20)) { + ++level_offset; + } + ImageInfo host = info; + host.size.width = (std::max)(info.size.width >> level_offset, 1U); + host.size.height = (std::max)(info.size.height >> level_offset, 1U); + host.resources.levels -= static_cast<s32>(level_offset); + LOG_WARNING(Render_Vulkan, "{}x{}x{} ASTC image would be {} MiB decoded; keeping mip levels {}+ ({}x{})", + info.size.width, info.size.height, info.resources.layers, decoded_bytes(0) >> 20, + level_offset, host.size.width, host.size.height); + return host; +} + [[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info, std::optional<VkFormat> format_override = {}) { auto format_info = @@ -527,6 +561,20 @@ TransformBufferCopies(std::span<const VideoCommon::BufferCopy> copies, size_t bu }; } +// A guest view's levels on a host image that keeps only levels >= host_level_offset. +[[nodiscard]] SubresourceRange HostRange(const Image& image, SubresourceRange range) { + const s32 skip = static_cast<s32>(image.host_level_offset); + if (skip == 0) { + return range; + } + const s32 host_levels = image.info.resources.levels - skip; + const s32 base = std::clamp(range.base.level - skip, 0, host_levels - 1); + const s32 end = std::clamp(range.base.level + range.extent.levels - skip, base + 1, host_levels); + range.base.level = base; + range.extent.levels = end - base; + return range; +} + [[nodiscard]] VkImageSubresourceRange MakeSubresourceRange(const ImageView* image_view) { SubresourceRange range = image_view->range; if (True(image_view->flags & VideoCommon::ImageViewFlagBits::Slice)) { @@ -1876,12 +1924,16 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu VAddr cpu_addr_) : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler}, runtime{&runtime_}, - original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info, + original_image(MakeImage(runtime_.device, runtime_.memory_allocator, + TrimmedHostInfo(runtime_.device, info, host_level_offset), runtime->ViewFormats(info.format))), aspect_mask(ImageAspectMask(info.format)) { if (original_image.IsUnbacked()) { flags |= VideoCommon::ImageFlagBits::Unbacked; } + if (host_level_offset != 0) { + flags |= VideoCommon::ImageFlagBits::TrimmedLevels; + } if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) { switch (Settings::values.accelerate_astc.GetValue()) { case Settings::AstcDecodeMode::Gpu: @@ -1906,11 +1958,12 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); } current_image = &Image::original_image; - storage_image_views.resize(info.resources.levels); + const s32 host_levels = info.resources.levels - static_cast<s32>(host_level_offset); + storage_image_views.resize(host_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) { + for (s32 level = 0; level < host_levels; ++level) { storage_image_views[level] = MakeStorageView(device, level, *original_image, storage_format); } @@ -2575,7 +2628,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI .viewType = VkImageViewType{}, .format = format_info.format, .components = swizzle_mapping, - .subresourceRange = MakeSubresourceRange(aspect_mask, info.range), + .subresourceRange = MakeSubresourceRange(aspect_mask, HostRange(image, info.range)), }; const auto create = [&](TextureType tex_type, std::optional<u32> num_layers) { VkImageViewCreateInfo ci{create_info}; |
