diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 8cca26aee9..0c57b14fad 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -1200,7 +1200,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, const VideoCommon::ImageViewInfo& view_info, GPUVAddr gpu_addr_) : VideoCommon::ImageViewBase{info, view_info, gpu_addr_}, - buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {} + buffer_size{static_cast(VideoCommon::CalculateGuestSizeInBytes(info))} {} ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, const VideoCommon::ImageViewInfo& view_info) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 6501a2c15b..b1306e07f7 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -180,7 +180,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { return false; } constexpr u32 MinSize = 32U << 20; - const u32 size = VideoCommon::CalculateGuestSizeInBytes(info); + const u64 size = VideoCommon::CalculateGuestSizeInBytes(info); return size >= MinSize && info.type != ImageType::e1D && info.type != ImageType::Buffer && info.type != ImageType::Linear && Tegra::Nx2SparseUnwritten(gpu_addr, size); } @@ -200,10 +200,33 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { IsHugeAstcArray(device, info) || (!IsPixelFormatASTC(info.format) && (!IsPixelFormatBCn(info.format) || device.IsOptimalBcnSupported())); - const u32 size = VideoCommon::CalculateGuestSizeInBytes(info); + const u64 size = VideoCommon::CalculateGuestSizeInBytes(info); return layer_uploads && size >= (32U << 20) && Tegra::Nx2InSparseRegion(gpu_addr, size); } +// A huge natively sampled color array in a Switch 2 sparse region on a host that cannot leave +// layers unbound (MoltenVK, AMD): title 1's terrain arrays (8192x8192x68, 1.4 and 5.8 GB) hold +// full detail in a few layers and the low levels (1024x1024 and below) in all of them. Such an +// array is backed with its low levels only (see TrimmedHostInfo, Image::CompactLayers). +[[nodiscard]] bool UsesCompactLayers(const Device& device, const ImageInfo& info, + GPUVAddr gpu_addr) { + // The complement of UsesSparseLayers' device check: AMD has sparse residency but cannot + // sample unbound memory. + if ((device.IsSparseResidencyImage2DSupported() && device.CanUseUnbackedSparseImages()) || + info.type != ImageType::e2D || + info.resources.layers <= 1 || info.resources.levels <= 1 || info.num_samples != 1 || + info.size.depth != 1 || + VideoCore::Surface::GetFormatType(info.format) != + VideoCore::Surface::SurfaceType::ColorTexture || + (IsPixelFormatASTC(info.format) && !device.IsOptimalAstcSupported()) || + (IsPixelFormatBCn(info.format) && !device.IsOptimalBcnSupported())) { + return false; + } + constexpr u32 MinSize = 256U << 20; + const u64 size = VideoCommon::CalculateGuestSizeInBytes(info); + return size >= MinSize && Tegra::Nx2InSparseRegion(gpu_addr, size); +} + // How SparseTiles images leave the blocks without data: unbound (reads give zero), or all bound to // one zeroed block where the driver can't sample unbound memory (AMD) but lets blocks share // memory. EDEN_NX2_SPARSE_ALIASED=1 takes the shared block wherever it is supported, =0 never. @@ -243,7 +266,7 @@ enum class SparseTilesMode { VideoCore::Surface::GetFormatType(info.format) != VideoCore::Surface::SurfaceType::ColorTexture) { return false; } - const u32 size = VideoCommon::CalculateGuestSizeInBytes(info); + const u64 size = VideoCommon::CalculateGuestSizeInBytes(info); return size >= (32U << 20) && Tegra::Nx2InSparseRegion(gpu_addr, size); } @@ -350,12 +373,59 @@ constexpr std::array OpaqueAstcViewFormats{VK_FORMAT_BC1_RGBA_UNORM return device.GetDeviceLocalMemory() < (u64{7} << 30) ? (u64{256} << 20) : (u64{512} << 20); } +// The most a compact layered array keeps on the host: 256 MiB. EDEN_NX2_COMPACT_ARRAY_MIB= +// sets it. +[[nodiscard]] u64 CompactLayersLimit() { + static const u64 limit = [] { + const char* value = std::getenv("EDEN_NX2_COMPACT_ARRAY_MIB"); + const u64 mib = value != nullptr ? std::strtoull(value, nullptr, 10) : 0; + return (mib != 0 ? mib : 256) << 20; + }(); + return limit; +} + +// The host shape of a UsesCompactLayers array: the mip levels from `level_offset` on, the first +// level count that fits CompactLayersLimit. Normalized coordinates and derivative LOD still +// address it correctly. +[[nodiscard]] ImageInfo CompactHostInfo(const ImageInfo& info, u32& level_offset) { + const u64 block_bytes = VideoCore::Surface::BytesPerBlock(info.format); + const u32 block_width = VideoCore::Surface::DefaultBlockWidth(info.format); + const u32 block_height = VideoCore::Surface::DefaultBlockHeight(info.format); + const auto host_bytes = [&](u32 skip) { + u64 bytes = 0; + for (s32 level = static_cast(skip); level < info.resources.levels; ++level) { + bytes += u64{Common::DivCeil((std::max)(info.size.width >> level, 1U), block_width)} * + Common::DivCeil((std::max)(info.size.height >> level, 1U), block_height) * + block_bytes; + } + return bytes * info.resources.layers; + }; + while (level_offset + 1 < static_cast(info.resources.levels) && + host_bytes(level_offset) > CompactLayersLimit()) { + ++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(level_offset); + LOG_WARNING(Render_Vulkan, + "{}x{}x{} array in a sparse region is {} MiB; keeping mip levels {}+ ({}x{}, " + "{} MiB)", + info.size.width, info.size.height, info.resources.layers, host_bytes(0) >> 20, + level_offset, host.size.width, host.size.height, host_bytes(level_offset) >> 20); + return host; +} + // 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) { +[[nodiscard]] ImageInfo TrimmedHostInfo(const Device& device, const ImageInfo& info, + u32& level_offset, GPUVAddr gpu_addr) { level_offset = 0; + if (UsesCompactLayers(device, info, gpu_addr)) { + return CompactHostInfo(info, level_offset); + } if (!WillUseAcceleratedAstcDecode(device, info) || info.type != ImageType::e2D) { return info; } @@ -759,13 +829,15 @@ TransformBufferCopies(std::span 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(image.host_level_offset); +// A guest view's levels on a host image of `levels` guest levels that keeps only levels >= +// host_level_offset. +[[nodiscard]] SubresourceRange HostRange(u32 host_level_offset, s32 levels, + SubresourceRange range) { + const s32 skip = static_cast(host_level_offset); if (skip == 0) { return range; } - const s32 host_levels = image.info.resources.levels - skip; + const s32 host_levels = 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; @@ -773,6 +845,10 @@ TransformBufferCopies(std::span copies, size_t bu return range; } +[[nodiscard]] SubresourceRange HostRange(const Image& image, SubresourceRange range) { + return HostRange(image.host_level_offset, image.info.resources.levels, range); +} + [[nodiscard]] VkImageSubresourceRange MakeSubresourceRange(const ImageView* image_view) { SubresourceRange range = image_view->range; if (True(image_view->flags & VideoCommon::ImageViewFlagBits::Slice)) { @@ -2628,7 +2704,7 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler}, runtime{&runtime_}, original_image(MakeImage(runtime_.device, runtime_.memory_allocator, - TrimmedHostInfo(runtime_.device, info, host_level_offset), + TrimmedHostInfo(runtime_.device, info, host_level_offset, gpu_addr_), IsHugeAstcArray(runtime_.device, info) ? std::span{HugeAstcViewFormats} : IsOpaqueBc1Astc(runtime_.device, info) @@ -2643,7 +2719,8 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu aspect_mask(ImageAspectMask(info.format)) { if (original_image.IsUnbacked()) { flags |= VideoCommon::ImageFlagBits::Unbacked; - const ImageInfo host_info = TrimmedHostInfo(runtime_.device, info, host_level_offset); + const ImageInfo host_info = + TrimmedHostInfo(runtime_.device, info, host_level_offset, gpu_addr_); const u64 texels = u64{host_info.size.width} * host_info.size.height * host_info.size.depth * static_cast(host_info.resources.layers); if (UsesSparseLayers(runtime_.device, info, gpu_addr_) && InitSparseLayers()) { @@ -2658,6 +2735,9 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu } if (host_level_offset != 0) { flags |= VideoCommon::ImageFlagBits::TrimmedLevels; + if (UsesCompactLayers(runtime_.device, info, gpu_addr_) && !original_image.IsUnbacked()) { + flags |= VideoCommon::ImageFlagBits::CompactLayers; + } } if (IsHugeAstcArray(runtime->device, info)) { flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; @@ -3141,7 +3221,7 @@ bool Image::InitSparseLayers() { s.requirements = device.GetLogical().GetImageMemoryRequirements(handle); s.sparse = *it; u32 offset = host_level_offset; - const ImageInfo host = TrimmedHostInfo(device, info, offset); + const ImageInfo host = TrimmedHostInfo(device, info, offset, gpu_addr); s.width = host.size.width; s.height = host.size.height; s.levels = static_cast(host.resources.levels); @@ -3909,6 +3989,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI ImageId image_id_, Image& image) : VideoCommon::ImageViewBase{info, image.info, image_id_, image.gpu_addr}, device{&runtime.device}, image_handle{image.Handle()}, + host_level_offset{image.host_level_offset}, image_levels{image.info.resources.levels}, samples(ConvertSampleCount(image.info.num_samples)) { using Shader::TextureType; @@ -4039,7 +4120,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageInfo& info, const VideoCommon::ImageViewInfo& view_info, GPUVAddr gpu_addr_) : VideoCommon::ImageViewBase{info, view_info, gpu_addr_}, - buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {} + buffer_size{static_cast(VideoCommon::CalculateGuestSizeInBytes(info))} {} ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageViewParams& params) : VideoCommon::ImageViewBase{params}, device{&runtime.device} { @@ -4125,7 +4206,8 @@ bool ImageView::IsRescaled() const noexcept { vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask, std::optional texture_type) { VkImageViewType view_type = ImageViewType(type); - VkImageSubresourceRange subresource_range = MakeSubresourceRange(aspect_mask, range); + VkImageSubresourceRange subresource_range = + MakeSubresourceRange(aspect_mask, HostRange(host_level_offset, image_levels, range)); const auto is_3d = [](VkImageViewType t) { return t == VK_IMAGE_VIEW_TYPE_3D; }; if (texture_type && is_3d(ImageViewType(*texture_type)) != is_3d(view_type)) { // A 3D view of a 2D image (or the reverse) is invalid in Vulkan and aborts on Metal; keep diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 26081a4c72..9852a9f96e 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -730,6 +730,8 @@ class ImageView : public VideoCommon::ImageViewBase { vk::Image null_image; VkImage image_handle = VK_NULL_HANDLE; VkImageView render_target = VK_NULL_HANDLE; + u32 host_level_offset = 0; // the image's (see Image::host_level_offset) + s32 image_levels = 0; VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; u32 buffer_size = 0; diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h index 878c229953..33283790ac 100644 --- a/src/video_core/texture_cache/image_base.h +++ b/src/video_core/texture_cache/image_base.h @@ -60,6 +60,10 @@ enum class ImageFlagBits : u32 { /// device memory when the guest memory under it can hold data (Image::BindTiles), and only /// the parts written are uploaded. SparseTiles = 1 << 23, + /// A large array over Switch 2 sparse blocks on a host that cannot leave it unbound: backed, + /// but only its mip levels from host_level_offset on (the written parts of title 1's terrain + /// arrays are the low levels of every layer), uploaded layer by layer. + CompactLayers = 1 << 24, }; DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) @@ -104,7 +108,7 @@ struct ImageBase { ImageInfo info; - u32 guest_size_bytes = 0; + u64 guest_size_bytes = 0; u32 unswizzled_size_bytes = 0; u32 converted_size_bytes = 0; u32 scale_rating = 0; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 25a313dd22..c5eb12f5da 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1666,6 +1666,10 @@ void TextureCache

::RefreshContents(Image& image, ImageId image_id) { RefreshSparseLayers(image); return; } + if (True(image.flags & ImageFlagBits::CompactLayers)) { + RefreshCompactLayers(image); + return; + } if (True(image.flags & ImageFlagBits::SparseTiles)) { RefreshSparseTiles(image); return; @@ -1769,7 +1773,7 @@ bool TextureCache

::UploadsByLayer(const ImageBase& image) const noexcept { // Title 1's terrain arrays (8192x8192x68 ASTC, 1.45 GB each) get a few layers streamed in at a // time; re-decoding all of them for each took a 2 GiB staging buffer and stalled the GPU. if constexpr (requires { &Runtime::AccelerateImageUploadLayers; }) { - if (True(image.flags & ImageFlagBits::SparseLayers)) { + if (True(image.flags & (ImageFlagBits::SparseLayers | ImageFlagBits::CompactLayers))) { return true; } return True(image.flags & ImageFlagBits::AcceleratedUpload) && @@ -1926,6 +1930,29 @@ void TextureCache

::RefreshSparseLayers(Image& image) { } } +template +void TextureCache

::RefreshCompactLayers(Image& image) { + if constexpr (requires { &Runtime::AccelerateImageUploadLayers; }) { + // A new image takes every layer (what the game never wrote reads as zero from guest + // memory, and the host image starts undefined); later only the layers written since. + const u32 num_layers = static_cast(image.info.resources.layers); + std::vector layers; + for (u32 layer = 0; layer < num_layers; ++layer) { + if (image.dirty_layers.empty() || + ((image.dirty_layers[layer / 64] >> (layer % 64)) & 1) != 0) { + layers.push_back(layer); + } + } + image.dirty_layers.clear(); + constexpr size_t LayersPerUpload = 16; + for (size_t first = 0; first < layers.size(); first += LayersPerUpload) { + const size_t count = (std::min)(LayersPerUpload, layers.size() - first); + UploadLayers(image, std::span{layers.data() + first, count}); + } + runtime.InsertUploadMemoryBarrier(); + } +} + template std::vector TextureCache

::BindSparseLayers(Image& image, std::span layers) { if constexpr (requires { &Image::BindLayers; }) { @@ -1945,20 +1972,32 @@ template void TextureCache

::UploadLayers(Image& image, std::span layers) { if constexpr (requires { &Runtime::AccelerateImageUploadLayers; }) { if (False(image.flags & ImageFlagBits::AcceleratedUpload)) { - // Plain formats: each layer unswizzled on its own into its layer of the image. + // Plain formats: each layer unswizzled on its own into its layer of the image. Of a + // trimmed image only the levels the host keeps are read. ImageInfo layer_info = image.info; layer_info.resources.layers = 1; - const size_t layer_size = CalculateUnswizzledSizeBytes(layer_info); + const s32 first_level = static_cast(image.host_level_offset); + size_t level_offset = 0; + ImageInfo host_info = layer_info; + if (first_level != 0) { + level_offset = FullUploadSwizzles(layer_info)[first_level].buffer_offset; + host_info.size.width = (std::max)(layer_info.size.width >> first_level, 1U); + host_info.size.height = (std::max)(layer_info.size.height >> first_level, 1U); + host_info.resources.levels -= first_level; + } + const size_t layer_size = CalculateUnswizzledSizeBytes(host_info); auto staging = runtime.UploadStagingBuffer(layer_size * layers.size()); std::vector copies; for (size_t i = 0; i < layers.size(); ++i) { - const GPUVAddr layer_addr = image.gpu_addr + u64{layers[i]} * image.info.layer_stride; + const GPUVAddr layer_addr = + image.gpu_addr + u64{layers[i]} * image.info.layer_stride + level_offset; Tegra::Memory::GpuGuestMemory - swizzle_data(*gpu_memory, layer_addr, image.info.layer_stride, + swizzle_data(*gpu_memory, layer_addr, image.info.layer_stride - level_offset, &swizzle_data_buffer); auto layer_copies = UnswizzleImage(*gpu_memory, layer_addr, layer_info, swizzle_data, - staging.mapped_span.subspan(i * layer_size, layer_size)); + staging.mapped_span.subspan(i * layer_size, layer_size), + first_level); for (BufferImageCopy copy : layer_copies) { copy.buffer_offset += i * layer_size; copy.image_subresource.base_layer = static_cast(layers[i]); @@ -1998,7 +2037,6 @@ template void TextureCache

::UploadImageContents(Image& image, StagingBuffer& staging) { const std::span 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. @@ -2225,6 +2263,10 @@ u64 TextureCache

::ImageMemoryBytes(const Image& image) const { return 0; } u64 tentative_size = (std::max)(image.guest_size_bytes, image.unswizzled_size_bytes); + if (True(image.flags & ImageFlagBits::CompactLayers)) { + // Each level is a quarter of the one before. + tentative_size >>= 2 * image.host_level_offset; + } if ((IsPixelFormatASTC(image.info.format) && True(image.flags & ImageFlagBits::AcceleratedUpload)) || True(image.flags & ImageFlagBits::Converted)) { diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 06fcc0de77..cf44c0ac1c 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -192,6 +192,9 @@ class TextureCache : public VideoCommon::ChannelSetupCaches BindSparseLayers(Image& image, std::span layers); diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp index 09e9513a5b..3fa6d6ab2d 100644 --- a/src/video_core/texture_cache/util.cpp +++ b/src/video_core/texture_cache/util.cpp @@ -578,7 +578,7 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr } // Anonymous namespace -u32 CalculateGuestSizeInBytes(const ImageInfo& info) noexcept { +u64 CalculateGuestSizeInBytes(const ImageInfo& info) noexcept { if (info.type == ImageType::Buffer) { return info.size.width * BytesPerBlock(info.format); } @@ -586,8 +586,9 @@ u32 CalculateGuestSizeInBytes(const ImageInfo& info) noexcept { return info.pitch * Common::DivCeil(info.size.height, DefaultBlockHeight(info.format)); } if (info.resources.layers > 1) { + // 64-bit: title 1's 8192x8192x68 BC6H terrain array is 5.8 GB. ASSERT(info.layer_stride != 0); - return info.layer_stride * info.resources.layers; + return u64{info.layer_stride} * static_cast(info.resources.layers); } else { return CalculateLayerSize(info); } @@ -854,7 +855,8 @@ boost::container::small_vector UnswizzleImage(Tegra::Memory GPUVAddr gpu_addr, const ImageInfo& info, std::span input, - std::span output) { + std::span output, + s32 first_level) { const size_t guest_size_bytes = input.size_bytes(); const u32 bpp_log2 = BytesPerBlockLog2(info.format); const Extent2D tile_size = DefaultBlockSize(info.format); @@ -890,20 +892,20 @@ boost::container::small_vector UnswizzleImage(Tegra::Memory info.tile_width_spacing); size_t guest_offset = 0; u32 host_offset = 0; - boost::container::small_vector copies(num_levels); + boost::container::small_vector copies(num_levels - first_level); - for (s32 level = 0; level < num_levels; ++level) { + for (s32 level = first_level; level < num_levels; ++level) { const Extent3D level_size = AdjustMipSize(size, level); const u32 num_blocks_per_layer = NumBlocks(level_size, tile_size); const u32 host_bytes_per_layer = num_blocks_per_layer << bpp_log2; - copies[level] = BufferImageCopy{ + copies[level - first_level] = BufferImageCopy{ .buffer_offset = host_offset, .buffer_size = static_cast(host_bytes_per_layer) * num_layers, .buffer_row_length = Common::AlignUp(level_size.width, tile_size.width), .buffer_image_height = Common::AlignUp(level_size.height, tile_size.height), .image_subresource = { - .base_level = level, + .base_level = level - first_level, .base_layer = 0, .num_layers = info.resources.layers, }, @@ -1683,7 +1685,7 @@ u32 MapSizeBytes(const ImageBase& image) { return tail * image.info.resources.layers; } if (True(image.flags & ImageFlagBits::AcceleratedUpload)) { - return image.guest_size_bytes; + return static_cast(image.guest_size_bytes); } else if (True(image.flags & ImageFlagBits::Converted)) { return image.converted_size_bytes; } else { diff --git a/src/video_core/texture_cache/util.h b/src/video_core/texture_cache/util.h index d91ee74c52..2990ae1741 100644 --- a/src/video_core/texture_cache/util.h +++ b/src/video_core/texture_cache/util.h @@ -32,7 +32,7 @@ struct OverlapResult { SubresourceExtent resources; }; -[[nodiscard]] u32 CalculateGuestSizeInBytes(const ImageInfo& info) noexcept; +[[nodiscard]] u64 CalculateGuestSizeInBytes(const ImageInfo& info) noexcept; [[nodiscard]] u32 CalculateUnswizzledSizeBytes(const ImageInfo& info) noexcept; @@ -99,9 +99,11 @@ struct UnswizzlePlan { void ExecuteUnswizzlePlan(Tegra::MemoryManager& gpu_memory, const UnswizzlePlan& plan, std::span output); +/// With `first_level` != 0, only the levels from there on are unswizzled; `input` then starts at +/// that level of the first layer, and the copies are numbered from 0. [[nodiscard]] boost::container::small_vector UnswizzleImage( Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info, - std::span input, std::span output); + std::span input, std::span output, s32 first_level = 0); /// For a single-layer 2D block-linear image of 1x1-texel blocks: the host sparse blocks /// (tile_width x tile_height texels) that the guest byte ranges (offset, size) touch, merged into diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index a0a39abe39..d99ea78a80 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -10,12 +10,14 @@ #include #include #include +#include #include #include #include "common/alignment.h" #include "common/assert.h" #include "common/common_types.h" +#include "common/div_ceil.h" #include "common/literals.h" #include "common/logging.h" #include @@ -85,6 +87,56 @@ namespace { } return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; } + + // The bytes an image's first level takes over all its layers, for sizing: compressed formats + // by their blocks, anything else at 4 bytes a texel. + [[nodiscard]] u64 LevelZeroBytes(const VkImageCreateInfo& ci) { + // ASTC block shapes, in format order (each shape has a UNORM and an SRGB format). + constexpr std::array, 14> AstcBlocks{{{4, 4}, {5, 4}, {5, 5}, {6, 5}, + {6, 6}, {8, 5}, {8, 6}, {8, 8}, + {10, 5}, {10, 6}, {10, 8}, + {10, 10}, {12, 10}, {12, 12}}}; + u32 block_width = 1; + u32 block_height = 1; + u64 block_bytes = 4; + const u32 value = static_cast(ci.format); + if (value >= VK_FORMAT_ASTC_4x4_UNORM_BLOCK && value <= VK_FORMAT_ASTC_12x12_SRGB_BLOCK) { + std::tie(block_width, block_height) = + AstcBlocks[(value - VK_FORMAT_ASTC_4x4_UNORM_BLOCK) / 2]; + block_bytes = 16; + } else { + switch (ci.format) { + case VK_FORMAT_BC1_RGB_UNORM_BLOCK: + case VK_FORMAT_BC1_RGB_SRGB_BLOCK: + case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: + case VK_FORMAT_BC1_RGBA_SRGB_BLOCK: + case VK_FORMAT_BC4_UNORM_BLOCK: + case VK_FORMAT_BC4_SNORM_BLOCK: + block_width = block_height = 4; + block_bytes = 8; + break; + case VK_FORMAT_BC2_UNORM_BLOCK: + case VK_FORMAT_BC2_SRGB_BLOCK: + case VK_FORMAT_BC3_UNORM_BLOCK: + case VK_FORMAT_BC3_SRGB_BLOCK: + case VK_FORMAT_BC5_UNORM_BLOCK: + case VK_FORMAT_BC5_SNORM_BLOCK: + case VK_FORMAT_BC6H_UFLOAT_BLOCK: + case VK_FORMAT_BC6H_SFLOAT_BLOCK: + case VK_FORMAT_BC7_UNORM_BLOCK: + case VK_FORMAT_BC7_SRGB_BLOCK: + block_width = block_height = 4; + block_bytes = 16; + break; + default: + break; + } + } + return u64{Common::DivCeil(ci.extent.width, block_width)} * + Common::DivCeil(ci.extent.height, block_height) * ci.extent.depth * ci.arrayLayers * + block_bytes; + } + } // namespace //MemoryCommit is now VMA-backed @@ -229,9 +281,9 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci, bool dedicat VmaAllocationInfo alloc_info{}; // Images this large (title 1's sparse texture arrays reach 4.5G texels, ~23 GB) are never // backed: VMA would quietly place them in system memory once VRAM is full. They take the - // unbacked sparse fallback below. - const u64 texels = u64{ci.extent.width} * ci.extent.height * ci.extent.depth * ci.arrayLayers; - const bool huge = texels > (u64{1} << 28); + // unbacked sparse fallback below. 1 GiB, counted by its blocks: a compressed image that fits + // is backed. + const bool huge = LevelZeroBytes(ci) > (u64{1} << 30); VkResult result = huge ? VK_ERROR_OUT_OF_DEVICE_MEMORY : vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info); if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY && !huge) {