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).
This commit is contained in:
parent
6d7acfb990
commit
b8a07d6d5b
5 changed files with 132 additions and 9 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ enum class ImageFlagBits : u32 {
|
|||
AsynchronousDecode = 1 << 16,
|
||||
IsDecoding = 1 << 17, ///< Is currently being decoded asynchronously.
|
||||
Unbacked = 1 << 18, ///< Host image has no memory (too big to back); never upload/download.
|
||||
/// The host image keeps only mip levels >= host_level_offset (its decoded copy would not fit
|
||||
/// in VRAM); uploads skip the rest, downloads are never done.
|
||||
TrimmedLevels = 1 << 19,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
|
||||
|
||||
|
|
@ -94,6 +97,7 @@ struct ImageBase {
|
|||
u32 unswizzled_size_bytes = 0;
|
||||
u32 converted_size_bytes = 0;
|
||||
u32 scale_rating = 0;
|
||||
u32 host_level_offset = 0; ///< Guest mip level held by host mip 0 (see TrimmedLevels).
|
||||
u64 scale_tick = 0;
|
||||
bool has_scaled = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -597,7 +597,7 @@ void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) {
|
|||
|
||||
template <class P>
|
||||
bool TextureCache<P>::IsDownloadable(const ImageBase& image) const noexcept {
|
||||
if (!image.IsSafeGpuCopy() || True(image.flags & ImageFlagBits::Unbacked)) {
|
||||
if (!image.IsSafeGpuCopy() || True(image.flags & (ImageFlagBits::Unbacked | ImageFlagBits::TrimmedLevels))) {
|
||||
return false;
|
||||
}
|
||||
if (image.info.num_samples == 1) {
|
||||
|
|
@ -714,6 +714,14 @@ bool TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
|
|||
|
||||
Image& dst_image = slot_images[dst_id];
|
||||
Image& src_image = slot_images[src_id];
|
||||
if (True(dst_image.flags & ImageFlagBits::Unbacked)) {
|
||||
static u32 logged{};
|
||||
if (logged++ < 64) {
|
||||
LOG_INFO(HW_GPU, "2D blit into unbacked image {:#x}: dst {:#x} {}x{} from {:#x} {}x{}",
|
||||
dst_image.gpu_addr, dst.Address(), dst.width, dst.height, src.Address(),
|
||||
src.width, src.height);
|
||||
}
|
||||
}
|
||||
bool is_src_rescaled = True(src_image.flags & ImageFlagBits::Rescaled);
|
||||
bool is_dst_rescaled = True(dst_image.flags & ImageFlagBits::Rescaled);
|
||||
|
||||
|
|
@ -1186,6 +1194,24 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
|
|||
const std::span<u8> mapped_span = staging.mapped_span;
|
||||
const GPUVAddr gpu_addr = image.gpu_addr;
|
||||
|
||||
if (True(image.flags & ImageFlagBits::AcceleratedUpload) && image.host_level_offset != 0) {
|
||||
// Only levels >= host_level_offset exist on the host: read each layer's tail of the mip
|
||||
// chain into a compact buffer (layer stride = that tail's size) and decode those levels.
|
||||
auto uploads = FullUploadSwizzles(image.info);
|
||||
const size_t level_offset = uploads[image.host_level_offset].buffer_offset;
|
||||
const size_t tail = image.info.layer_stride - level_offset;
|
||||
for (s32 layer = 0; layer < image.info.resources.layers; ++layer) {
|
||||
gpu_memory->ReadBlock(gpu_addr + layer * image.info.layer_stride + level_offset,
|
||||
mapped_span.data() + layer * tail, tail,
|
||||
VideoCommon::CacheType::NoTextureCache);
|
||||
}
|
||||
uploads.erase(uploads.begin(), uploads.begin() + image.host_level_offset);
|
||||
for (auto& upload : uploads) {
|
||||
upload.buffer_offset -= level_offset;
|
||||
}
|
||||
runtime.AccelerateImageUpload(image, staging, uploads, 0, 0);
|
||||
return;
|
||||
}
|
||||
if (True(image.flags & ImageFlagBits::AcceleratedUpload)) {
|
||||
gpu_memory->ReadBlock(gpu_addr, mapped_span.data(), mapped_span.size_bytes(),
|
||||
VideoCommon::CacheType::NoTextureCache);
|
||||
|
|
@ -1908,6 +1934,13 @@ ImageId TextureCache<P>::FindDMAImage(const ImageInfo& info, GPUVAddr gpu_addr)
|
|||
return true;
|
||||
}
|
||||
} else if (IsSubCopy(info, existing_image, gpu_addr)) {
|
||||
if (True(existing_image.flags & ImageFlagBits::Unbacked)) {
|
||||
static u32 logged{};
|
||||
if (logged++ < 64) {
|
||||
LOG_INFO(HW_GPU, "DMA copy at {:#x} ({}x{}) into unbacked image {:#x}", gpu_addr,
|
||||
info.size.width, info.size.height, existing_image.gpu_addr);
|
||||
}
|
||||
}
|
||||
image_id = existing_image_id;
|
||||
image_ids.push_back(existing_image_id);
|
||||
return true;
|
||||
|
|
@ -2003,6 +2036,13 @@ ImageViewId TextureCache<P>::FindRenderTargetView(const ImageInfo& info, GPUVAdd
|
|||
base = image.TryFindBase(gpu_addr).value();
|
||||
}
|
||||
const s32 layers = image.info.type == ImageType::e3D ? info.size.depth : info.resources.layers;
|
||||
if (True(image.flags & ImageFlagBits::Unbacked)) {
|
||||
static u32 logged{};
|
||||
if (logged++ < 64) {
|
||||
LOG_INFO(HW_GPU, "Render target in unbacked image {:#x} at {:#x}: {}x{} level {} layer {}",
|
||||
image.gpu_addr, gpu_addr, info.size.width, info.size.height, base.level, base.layer);
|
||||
}
|
||||
}
|
||||
const SubresourceRange range{
|
||||
.base = base,
|
||||
.extent = {.levels = 1, .layers = layers},
|
||||
|
|
@ -2185,6 +2225,13 @@ ImageViewId TextureCache<P>::FindOrEmplaceImageView(ImageId image_id, const Imag
|
|||
if (const ImageViewId image_view_id = image.FindView(info); image_view_id) {
|
||||
return image_view_id;
|
||||
}
|
||||
if (True(image.flags & ImageFlagBits::Unbacked)) {
|
||||
// How the game uses an image too large to back (e.g. a sparse virtual texture).
|
||||
LOG_INFO(HW_GPU, "View of unbacked image {:#x}: type {} format {} levels {}+{} layers {}+{}",
|
||||
image.gpu_addr, static_cast<u32>(info.type), static_cast<u32>(info.format),
|
||||
info.range.base.level, info.range.extent.levels, info.range.base.layer,
|
||||
info.range.extent.layers);
|
||||
}
|
||||
const ImageViewId image_view_id = slot_image_views.insert(runtime, info, image_id, image, slot_images);
|
||||
image.InsertView(info, image_view_id);
|
||||
return image_view_id;
|
||||
|
|
@ -2682,7 +2729,7 @@ void TextureCache<P>::BindRenderTarget(ImageViewId* old_id, ImageViewId new_id)
|
|||
if (new_id) {
|
||||
const ImageViewBase& old_view = slot_image_views[new_id];
|
||||
if (True(old_view.flags & ImageViewFlagBits::PreemtiveDownload) &&
|
||||
False(slot_images[old_view.image_id].flags & ImageFlagBits::Unbacked)) {
|
||||
False(slot_images[old_view.image_id].flags & (ImageFlagBits::Unbacked | ImageFlagBits::TrimmedLevels))) {
|
||||
const PendingDownload new_download{true, 0, old_view.image_id};
|
||||
uncommitted_downloads.emplace_back(new_download);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1298,6 +1298,13 @@ void DeduceBlitImages(ImageInfo& dst_info, ImageInfo& src_info, const ImageBase*
|
|||
}
|
||||
|
||||
u32 MapSizeBytes(const ImageBase& image) {
|
||||
if (True(image.flags & ImageFlagBits::AcceleratedUpload) && image.host_level_offset != 0) {
|
||||
// Each layer's mip chain from host_level_offset on (see UploadImageContents).
|
||||
const auto uploads = FullUploadSwizzles(image.info);
|
||||
const u32 tail = image.info.layer_stride -
|
||||
static_cast<u32>(uploads[image.host_level_offset].buffer_offset);
|
||||
return tail * image.info.resources.layers;
|
||||
}
|
||||
if (True(image.flags & ImageFlagBits::AcceleratedUpload)) {
|
||||
return image.guest_size_bytes;
|
||||
} else if (True(image.flags & ImageFlagBits::Converted)) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue