aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/renderer_vulkan
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 15:37:34 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 15:37:34 -0400
commit934ad3b97c12e1d40cf4e1f6094c756873b6af82 (patch)
tree5e867981c552d4b74898db22a90230dbd4fda546 /src/video_core/renderer_vulkan
parent8393e2912616ce475619900be8cb29e551ff2997 (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.cpp18
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp61
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};