diff options
| -rw-r--r-- | src/video_core/nx2.h | 15 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_texture_cache.cpp | 231 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_texture_cache.h | 65 | ||||
| -rw-r--r-- | src/video_core/texture_cache/image_base.h | 3 | ||||
| -rw-r--r-- | src/video_core/texture_cache/texture_cache.h | 86 | ||||
| -rw-r--r-- | src/video_core/texture_cache/texture_cache_base.h | 6 | ||||
| -rw-r--r-- | src/video_core/vulkan_common/vulkan_device.h | 5 | ||||
| -rw-r--r-- | src/video_core/vulkan_common/vulkan_memory_allocator.cpp | 29 | ||||
| -rw-r--r-- | src/video_core/vulkan_common/vulkan_memory_allocator.h | 6 | ||||
| -rw-r--r-- | src/video_core/vulkan_common/vulkan_wrapper.cpp | 11 | ||||
| -rw-r--r-- | src/video_core/vulkan_common/vulkan_wrapper.h | 4 |
11 files changed, 453 insertions, 8 deletions
diff --git a/src/video_core/nx2.h b/src/video_core/nx2.h index 1b565e73d4..1a3bae93ec 100644 --- a/src/video_core/nx2.h +++ b/src/video_core/nx2.h @@ -123,6 +123,21 @@ inline void Nx2SparseBlockWritten(u64 region_address, u64 block) { } } +/// True when [gpu_address, +size) lies in one sparse region. +inline bool Nx2InSparseRegion(u64 gpu_address, u64 size) { + if (size == 0 || gpu_address + size <= g_nx2_sparse_low.load(std::memory_order_relaxed) || + gpu_address >= g_nx2_sparse_high.load(std::memory_order_relaxed)) [[likely]] { + return false; + } + std::scoped_lock lk{g_nx2_sparse_state_mutex}; + auto it = g_nx2_sparse_state.upper_bound(gpu_address); + if (it == g_nx2_sparse_state.begin()) { + return false; + } + --it; + return gpu_address + size <= it->first + it->second.size; +} + /// The GPU renders into [gpu_address, +size): count its blocks as written, so images made there /// later get memory (their contents live in the host image, not in guest memory). inline void Nx2SparseMarkWritten(u64 gpu_address, u64 size) { diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 08a471cfd6..b77f9af5f8 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -16,7 +16,9 @@ #include <boost/container/small_vector.hpp> #include <bit> #include <numeric> +#include "common/alignment.h" #include "common/bit_util.h" +#include "common/div_ceil.h" #include "common/settings.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" @@ -176,6 +178,31 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { info.type != ImageType::Linear && Tegra::Nx2SparseUnwritten(gpu_addr, size); } +// A large color array in a Switch 2 sparse region (title 1's terrain arrays: 13-26% of their +// blocks written) gets memory by layer, as its layers are written: unbacked at first, then +// Image::BindLayers. Only formats uploaded layer by layer: huge ASTC arrays (GPU decode) and +// formats the host samples as they are. +[[nodiscard]] bool UsesSparseLayers(const Device& device, const ImageInfo& info, GPUVAddr gpu_addr) { + if (!device.IsSparseResidencyImage2DSupported() || info.type != ImageType::e2D || + info.resources.layers <= 1 || info.num_samples != 1 || + VideoCore::Surface::GetFormatType(info.format) != VideoCore::Surface::SurfaceType::ColorTexture) { + return false; + } + const bool layer_uploads = + IsHugeAstcArray(device, info) || + (!IsPixelFormatASTC(info.format) && + (!IsPixelFormatBCn(info.format) || device.IsOptimalBcnSupported())); + const u32 size = VideoCommon::CalculateGuestSizeInBytes(info); + return layer_uploads && size >= (32U << 20) && Tegra::Nx2InSparseRegion(gpu_addr, size); +} + +// The most device memory one SparseLayers image binds: 768 MiB, 384 MiB on GPUs with less than +// 7 GiB (title 1's BC6H arrays are 85 MB a layer). +[[nodiscard]] VkDeviceSize SparseLayersCap(const Device& device) { + return device.GetDeviceLocalMemory() < (u64{7} << 30) ? (VkDeviceSize{384} << 20) + : (VkDeviceSize{768} << 20); +} + // The host format of a huge ASTC array: the GPU decoder's RGBA8 (sRGB kept for sampling). [[nodiscard]] std::optional<VkFormat> HugeAstcHostFormat(const Device& device, const ImageInfo& info) { if (!IsHugeAstcArray(device, info)) { @@ -2037,16 +2064,19 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu ? std::span<const VkFormat>{OpaqueAstcViewFormats} : runtime->ViewFormats(info.format), AstcHostFormat(runtime_.device, info), - IsUnwrittenSparse(info, gpu_addr_))), + UsesSparseLayers(runtime_.device, info, gpu_addr_) || + IsUnwrittenSparse(info, gpu_addr_))), aspect_mask(ImageAspectMask(info.format)) { if (original_image.IsUnbacked()) { flags |= VideoCommon::ImageFlagBits::Unbacked; - // Not too big to back (see MemoryAllocator::CreateImage): unbacked because nothing was - // written there yet, so the first write gets it replaced. const ImageInfo host_info = TrimmedHostInfo(runtime_.device, info, host_level_offset); const u64 texels = u64{host_info.size.width} * host_info.size.height * host_info.size.depth * static_cast<u64>(host_info.resources.layers); - if (texels <= (u64{1} << 28)) { + if (UsesSparseLayers(runtime_.device, info, gpu_addr_) && InitSparseLayers()) { + flags |= VideoCommon::ImageFlagBits::SparseLayers; + } else if (texels <= (u64{1} << 28)) { + // Not too big to back (see MemoryAllocator::CreateImage): unbacked because nothing + // was written there yet, so the first write gets it replaced. flags |= VideoCommon::ImageFlagBits::UnwrittenSparse; } } @@ -2520,6 +2550,199 @@ VkImageView Image::StorageImageView(s32 level) noexcept { return *view; } +bool Image::InitSparseLayers() { + const Device& device = runtime->device; + const VkImage handle = *original_image; + const auto requirements = device.GetLogical().GetImageSparseMemoryRequirements(handle); + const auto it = std::ranges::find_if(requirements, [](const auto& r) { + return (r.formatProperties.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0; + }); + if (it == requirements.end()) { + return false; + } + SparseLayerMemory& s = sparse_layers; + s.allocator = &runtime->memory_allocator; + s.requirements = device.GetLogical().GetImageMemoryRequirements(handle); + s.sparse = *it; + u32 offset = host_level_offset; + const ImageInfo host = TrimmedHostInfo(device, info, offset); + s.width = host.size.width; + s.height = host.size.height; + s.levels = static_cast<u32>(host.resources.levels); + // Sparse block size in texels (drivers give it in texels or in compressed blocks). + const bool huge_astc = IsHugeAstcArray(device, info); + const u32 block_width = huge_astc ? 1 : VideoCore::Surface::DefaultBlockWidth(info.format); + const u32 block_height = huge_astc ? 1 : VideoCore::Surface::DefaultBlockHeight(info.format); + const u64 block_bytes = huge_astc ? 4 : VideoCore::Surface::BytesPerBlock(info.format); + const VkExtent3D granularity = it->formatProperties.imageGranularity; + s.tile_width = granularity.width; + s.tile_height = granularity.height; + const u64 tile = s.requirements.alignment; + if (u64{granularity.width / block_width} * (granularity.height / block_height) * block_bytes != + tile && + u64{granularity.width} * granularity.height * block_bytes == tile) { + s.tile_width *= block_width; + s.tile_height *= block_height; + } + const u32 tail_lod = (std::min)(it->imageMipTailFirstLod, s.levels); + VkDeviceSize bytes = 0; + for (u32 level = 0; level < tail_lod; ++level) { + const u32 width = (std::max)(s.width >> level, 1U); + const u32 height = (std::max)(s.height >> level, 1U); + bytes += u64{Common::DivCeil(width, s.tile_width)} * Common::DivCeil(height, s.tile_height) * + tile; + } + if (tail_lod < s.levels && + (it->formatProperties.flags & VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT) == 0) { + bytes += it->imageMipTailSize; + } + s.layer_bytes = Common::AlignUp(bytes, tile); + s.layer_memory.assign(static_cast<size_t>(info.resources.layers), VK_NULL_HANDLE); + LOG_INFO(Render_Vulkan, + "Sparse layered image {:#x}: {}x{} {} levels {} layers, {} KiB a layer, block {}x{}", + gpu_addr, s.width, s.height, s.levels, info.resources.layers, s.layer_bytes >> 10, + s.tile_width, s.tile_height); + return true; +} + +std::vector<u32> Image::BindLayers(std::span<const u32> layers) { + SparseLayerMemory& s = sparse_layers; + std::vector<u32> bound; + if (s.layer_memory.empty()) { + return bound; + } + const Device& device = runtime->device; + const u64 tile = s.requirements.alignment; + const u32 tail_lod = (std::min)(s.sparse.imageMipTailFirstLod, s.levels); + const bool has_tail = tail_lod < s.levels; + const bool single_tail = + (s.sparse.formatProperties.flags & VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT) != 0; + const VkDeviceSize cap = SparseLayersCap(device); + std::vector<VkSparseImageMemoryBind> image_binds; + std::vector<VkSparseMemoryBind> opaque_binds; + for (const u32 layer : layers) { + if (s.layer_memory[layer] != VK_NULL_HANDLE) { + bound.push_back(layer); + continue; + } + if (s.bound_bytes + s.layer_bytes > cap) { + if (!std::exchange(s.warned, true)) { + LOG_WARNING(Render_Vulkan, "Sparse layered image {:#x}: {} MiB bound, more layers " + "stay without memory", + gpu_addr, s.bound_bytes >> 20); + } + continue; + } + const VkMemoryRequirements requirements{ + .size = s.layer_bytes, + .alignment = tile, + .memoryTypeBits = s.requirements.memoryTypeBits, + }; + VkDeviceMemory memory{}; + VkDeviceSize offset{}; + const VmaAllocation allocation = s.allocator->AllocateSparseMemory(requirements, memory, offset); + if (allocation == VK_NULL_HANDLE) { + LOG_WARNING(Render_Vulkan, "Sparse layered image {:#x}: out of device memory", gpu_addr); + break; + } + s.layer_memory[layer] = allocation; + s.bound_bytes += s.layer_bytes; + for (u32 level = 0; level < tail_lod; ++level) { + const u32 width = (std::max)(s.width >> level, 1U); + const u32 height = (std::max)(s.height >> level, 1U); + image_binds.push_back(VkSparseImageMemoryBind{ + .subresource{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = level, + .arrayLayer = layer, + }, + .offset{0, 0, 0}, + .extent{width, height, 1}, + .memory = memory, + .memoryOffset = offset, + .flags = 0, + }); + offset += u64{Common::DivCeil(width, s.tile_width)} * + Common::DivCeil(height, s.tile_height) * tile; + } + if (has_tail && !single_tail) { + opaque_binds.push_back(VkSparseMemoryBind{ + .resourceOffset = s.sparse.imageMipTailOffset + layer * s.sparse.imageMipTailStride, + .size = s.sparse.imageMipTailSize, + .memory = memory, + .memoryOffset = offset, + .flags = 0, + }); + } + bound.push_back(layer); + } + if (has_tail && single_tail && s.tail_memory == VK_NULL_HANDLE && !bound.empty()) { + const VkMemoryRequirements requirements{ + .size = Common::AlignUp(s.sparse.imageMipTailSize, tile), + .alignment = tile, + .memoryTypeBits = s.requirements.memoryTypeBits, + }; + VkDeviceMemory memory{}; + VkDeviceSize offset{}; + s.tail_memory = s.allocator->AllocateSparseMemory(requirements, memory, offset); + if (s.tail_memory != VK_NULL_HANDLE) { + opaque_binds.push_back(VkSparseMemoryBind{ + .resourceOffset = s.sparse.imageMipTailOffset, + .size = s.sparse.imageMipTailSize, + .memory = memory, + .memoryOffset = offset, + .flags = 0, + }); + } + } + if (image_binds.empty() && opaque_binds.empty()) { + return bound; + } + const VkSparseImageMemoryBindInfo image_bind_info{ + .image = *original_image, + .bindCount = static_cast<u32>(image_binds.size()), + .pBinds = image_binds.data(), + }; + const VkSparseImageOpaqueMemoryBindInfo opaque_bind_info{ + .image = *original_image, + .bindCount = static_cast<u32>(opaque_binds.size()), + .pBinds = opaque_binds.data(), + }; + const VkBindSparseInfo bind_info{ + .sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO, + .pNext = nullptr, + .waitSemaphoreCount = 0, + .pWaitSemaphores = nullptr, + .bufferBindCount = 0, + .pBufferBinds = nullptr, + .imageOpaqueBindCount = opaque_binds.empty() ? 0U : 1U, + .pImageOpaqueBinds = opaque_binds.empty() ? nullptr : &opaque_bind_info, + .imageBindCount = image_binds.empty() ? 0U : 1U, + .pImageBinds = image_binds.empty() ? nullptr : &image_bind_info, + .signalSemaphoreCount = 0, + .pSignalSemaphores = nullptr, + }; + // Bound before the command buffer that uploads the layers is submitted (as the multi-range + // buffer cache does). + vk::Fence fence = device.GetLogical().CreateFence(VkFenceCreateInfo{ + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + }); + VkResult result; + { + std::scoped_lock lock{scheduler->submit_mutex}; + result = device.GetGraphicsQueue().BindSparse(bind_info, *fence); + } + if (result != VK_SUCCESS) { + LOG_ERROR(Render_Vulkan, "Sparse layered image {:#x}: binding failed ({})", gpu_addr, + static_cast<int>(result)); + return {}; + } + fence.Wait(); + return bound; +} + VkImageView Image::StorageImageLayerView(s32 level, u32 layer) { const u32 layers = static_cast<u32>(info.resources.layers); if (storage_layer_views.empty()) { diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 59f2936e06..13919f1270 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -385,6 +385,10 @@ public: /// A storage view of one layer of a host level (decoding only some layers of an array). VkImageView StorageImageLayerView(s32 level, u32 layer); + /// SparseLayers images: gives `layers` device memory where they have none yet. Returns the + /// layers that have memory afterwards (a cap per image can leave some out). + std::vector<u32> BindLayers(std::span<const u32> layers); + bool IsRescaled() const noexcept; bool ScaleUp(bool ignore = false); @@ -403,6 +407,67 @@ private: Scheduler* scheduler{}; TextureCacheRuntime* runtime{}; + /// Memory bound to the layers of a SparseLayers image. Declared before the images, so it is + /// released after them. + struct SparseLayerMemory { + SparseLayerMemory() = default; + SparseLayerMemory(const SparseLayerMemory&) = delete; + SparseLayerMemory& operator=(const SparseLayerMemory&) = delete; + SparseLayerMemory(SparseLayerMemory&& other) noexcept { + *this = std::move(other); + } + SparseLayerMemory& operator=(SparseLayerMemory&& other) noexcept { + Release(); + allocator = std::exchange(other.allocator, nullptr); + requirements = other.requirements; + sparse = other.sparse; + width = other.width; + height = other.height; + levels = other.levels; + tile_width = other.tile_width; + tile_height = other.tile_height; + layer_bytes = other.layer_bytes; + layer_memory = std::move(other.layer_memory); + other.layer_memory.clear(); + tail_memory = std::exchange(other.tail_memory, VK_NULL_HANDLE); + bound_bytes = other.bound_bytes; + warned = other.warned; + return *this; + } + ~SparseLayerMemory() { + Release(); + } + void Release() noexcept { + if (allocator == nullptr) { + return; + } + for (const VmaAllocation allocation : layer_memory) { + allocator->FreeSparseMemory(allocation); + } + allocator->FreeSparseMemory(tail_memory); + layer_memory.clear(); + tail_memory = VK_NULL_HANDLE; + } + + const MemoryAllocator* allocator{}; + VkMemoryRequirements requirements{}; + VkSparseImageMemoryRequirements sparse{}; + u32 width{}; ///< Host level 0 + u32 height{}; + u32 levels{}; ///< Host levels + u32 tile_width{}; ///< Sparse block, in texels + u32 tile_height{}; + VkDeviceSize layer_bytes{}; ///< One layer's levels before the mip tail, and its own tail + std::vector<VmaAllocation> layer_memory; ///< Per layer, null until bound + VmaAllocation tail_memory{}; ///< The mip tail all layers share, if one + VkDeviceSize bound_bytes{}; + bool warned{}; + }; + SparseLayerMemory sparse_layers; + + /// Reads the image's sparse requirements; false when it can't be bound by layer. + bool InitSparseLayers(); + vk::Image original_image; vk::Image scaled_image; diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h index cfb839d17e..04254fb417 100644 --- a/src/video_core/texture_cache/image_base.h +++ b/src/video_core/texture_cache/image_base.h @@ -53,6 +53,9 @@ enum class ImageFlagBits : u32 { /// Over Switch 2 sparse blocks nobody wrote: all zeros, so the host image has no memory /// (Unbacked) until the first write, which makes a backed image replace it. UnwrittenSparse = 1 << 21, + /// A large array over Switch 2 sparse blocks: unbacked, each layer gets device memory when + /// something is written to it (Image::BindLayers) and only those layers are uploaded. + SparseLayers = 1 << 22, }; DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 168df50c8b..3de82d2eb4 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -7,6 +7,7 @@ #pragma once #include <algorithm> +#include <numeric> #include <cstdlib> #include <limits> #include <map> @@ -1306,6 +1307,10 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) { TrackImage(image, image_id); } + if (True(image.flags & ImageFlagBits::SparseLayers)) { + RefreshSparseLayers(image); + return; + } if (!image.dirty_layers.empty() && UploadWrittenLayers(image)) { runtime.InsertUploadMemoryBarrier(); return; @@ -1354,6 +1359,9 @@ bool TextureCache<P>::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)) { + return true; + } return True(image.flags & ImageFlagBits::AcceleratedUpload) && image.info.type == ImageType::e2D && image.info.resources.layers > 1 && image.guest_size_bytes >= LayerUploadMinSize && @@ -1401,6 +1409,71 @@ bool TextureCache<P>::UploadWrittenLayers(Image& image) { if (layers.empty() || layers.size() == num_layers) { return false; } + UploadLayers(image, layers); + return true; + } else { + return false; + } +} + +template <class P> +void TextureCache<P>::RefreshSparseLayers(Image& image) { + if constexpr (requires { &Image::BindLayers; }) { + // The layers written since the last upload, or, for a new image, every layer any of whose + // blocks has been written. + const u32 num_layers = static_cast<u32>(image.info.resources.layers); + const u64 stride = image.info.layer_stride; + std::vector<u32> layers; + for (u32 layer = 0; layer < num_layers; ++layer) { + const bool written = + image.dirty_layers.empty() + ? !Tegra::Nx2SparseUnwritten(image.gpu_addr + layer * stride, stride) + : ((image.dirty_layers[layer / 64] >> (layer % 64)) & 1) != 0; + if (written) { + layers.push_back(layer); + } + } + image.dirty_layers.clear(); + const std::vector<u32> bound = layers.empty() ? std::vector<u32>{} : image.BindLayers(layers); + if (bound.empty()) { + runtime.TransitionImageLayout(image); + return; + } + UploadLayers(image, bound); + runtime.InsertUploadMemoryBarrier(); + } +} + +template <class P> +void TextureCache<P>::UploadLayers(Image& image, std::span<const u32> 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. + ImageInfo layer_info = image.info; + layer_info.resources.layers = 1; + const size_t layer_size = CalculateUnswizzledSizeBytes(layer_info); + auto staging = runtime.UploadStagingBuffer(layer_size * layers.size()); + std::vector<BufferImageCopy> copies; + for (size_t i = 0; i < layers.size(); ++i) { + const GPUVAddr layer_addr = image.gpu_addr + u64{layers[i]} * image.info.layer_stride; + Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::UnsafeRead> + swizzle_data(*gpu_memory, layer_addr, image.info.layer_stride, + &swizzle_data_buffer); + auto layer_copies = + UnswizzleImage(*gpu_memory, layer_addr, layer_info, swizzle_data, + staging.mapped_span.subspan(i * layer_size, layer_size)); + for (BufferImageCopy copy : layer_copies) { + copy.buffer_offset += i * layer_size; + copy.image_subresource.base_layer = static_cast<s32>(layers[i]); + copies.push_back(copy); + } + } + image.UploadMemory(staging, copies); + ++images_uploaded; + uploaded_bytes += u64{image.info.layer_stride} * layers.size(); + ++layer_uploads; + return; + } // Each layer's mip chain from the first level the host keeps on (as UploadImageContents). auto uploads = FullUploadSwizzles(image.info); const size_t level_offset = uploads[image.host_level_offset].buffer_offset; @@ -1420,9 +1493,6 @@ bool TextureCache<P>::UploadWrittenLayers(Image& image) { ++images_uploaded; uploaded_bytes += tail * layers.size(); ++layer_uploads; - return true; - } else { - return false; } } @@ -2524,7 +2594,8 @@ 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)) { + if (True(image.flags & ImageFlagBits::Unbacked) && + False(image.flags & ImageFlagBits::SparseLayers)) { // 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), @@ -2924,6 +2995,13 @@ void TextureCache<P>::PrepareImage(ImageId image_id, bool is_modification, bool } } if (is_modification) { + if constexpr (requires { &Image::BindLayers; }) { + if (True(image.flags & ImageFlagBits::SparseLayers)) { + std::vector<u32> layers(static_cast<size_t>(image.info.resources.layers)); + std::iota(layers.begin(), layers.end(), 0U); + image.BindLayers(layers); + } + } if (True(image.flags & ImageFlagBits::UnwrittenSparse) && False(image.flags & ImageFlagBits::Remapped)) { // The GPU renders into it: these writes are lost, later uses get a backed image. diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index f4cc89f2ba..11e95f98b3 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -167,6 +167,12 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI /// Uploads the image's dirty layers. False when it needs a full upload instead. bool UploadWrittenLayers(Image& image); + /// Gives the written layers of a SparseLayers image memory and uploads them. + void RefreshSparseLayers(Image& image); + + /// Uploads `layers` of the image from guest memory. + void UploadLayers(Image& image, std::span<const u32> layers); + struct PendingUnswizzle { ImageId image_id; VideoCommon::ImageInfo info; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index b1fa8db183..39ae25f43a 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -321,6 +321,11 @@ public: return features.features.sparseBinding && graphics_family_sparse_binding; } + /// 2D images whose memory can be bound in parts, on the graphics queue. + bool IsSparseResidencyImage2DSupported() const { + return IsSparseBindingSupported() && features.features.sparseResidencyImage2D; + } + /// Returns true for tile-based deferred renderers. bool IsTiler() const { switch (GetDriverID()) { diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 92f537aa3c..a0c96d1b45 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -295,6 +295,35 @@ vk::Image MemoryAllocator::CreateUnbackedImage(const VkImageCreateInfo &ci) cons device.GetDispatchLoader()); } +VmaAllocation MemoryAllocator::AllocateSparseMemory(const VkMemoryRequirements &requirements, + VkDeviceMemory &memory, + VkDeviceSize &offset) const { + const VmaAllocationCreateInfo alloc_ci{ + .flags = 0, + .usage = VMA_MEMORY_USAGE_UNKNOWN, + .requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + .preferredFlags = 0, + .memoryTypeBits = requirements.memoryTypeBits, + .pool = VK_NULL_HANDLE, + .pUserData = nullptr, + .priority = 0.f, + }; + VmaAllocation allocation{}; + VmaAllocationInfo info{}; + if (vmaAllocateMemory(allocator, &requirements, &alloc_ci, &allocation, &info) != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + memory = info.deviceMemory; + offset = info.offset; + return allocation; +} + +void MemoryAllocator::FreeSparseMemory(VmaAllocation allocation) const { + if (allocation != VK_NULL_HANDLE) { + vmaFreeMemory(allocator, allocation); + } +} + vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const { // MESA will do memcpy() if not marked as host cached, so just force mark it for most buffers auto const anv_flags = (usage == MemoryUsage::Stream diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h index 99649b9aef..f3c6c177a2 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.h +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h @@ -111,6 +111,12 @@ namespace Vulkan { /// a null image when the device can't make one. vk::Image CreateUnbackedImage(const VkImageCreateInfo &ci) const; + /// Device memory to bind to part of a sparse resource. Null when there is none left. + VmaAllocation AllocateSparseMemory(const VkMemoryRequirements &requirements, + VkDeviceMemory &memory, VkDeviceSize &offset) const; + + void FreeSparseMemory(VmaAllocation allocation) const; + vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const; vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage, diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index bb43fe399f..dbaaf450a0 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -219,6 +219,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkGetEventStatus); X(vkGetFenceStatus); X(vkGetImageMemoryRequirements); + X(vkGetImageSparseMemoryRequirements); X(vkGetPipelineCacheData); X(vkGetMemoryFdKHR); #ifdef _WIN32 @@ -866,6 +867,16 @@ VkMemoryRequirements Device::GetImageMemoryRequirements(VkImage image) const noe return requirements; } +std::vector<VkSparseImageMemoryRequirements> Device::GetImageSparseMemoryRequirements( + VkImage image) const { + u32 count = 0; + dld->vkGetImageSparseMemoryRequirements(handle, image, &count, nullptr); + std::vector<VkSparseImageMemoryRequirements> requirements(count); + dld->vkGetImageSparseMemoryRequirements(handle, image, &count, requirements.data()); + requirements.resize(count); + return requirements; +} + std::vector<VkPipelineExecutablePropertiesKHR> Device::GetPipelineExecutablePropertiesKHR( VkPipeline pipeline) const { const VkPipelineInfoKHR info{ diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 947150648d..3010368973 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -335,6 +335,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkGetEventStatus vkGetEventStatus{}; PFN_vkGetFenceStatus vkGetFenceStatus{}; PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements{}; + PFN_vkGetImageSparseMemoryRequirements vkGetImageSparseMemoryRequirements{}; PFN_vkGetPipelineCacheData vkGetPipelineCacheData{}; PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR{}; #ifdef _WIN32 @@ -1111,6 +1112,9 @@ public: VkMemoryRequirements GetImageMemoryRequirements(VkImage image) const noexcept; + std::vector<VkSparseImageMemoryRequirements> GetImageSparseMemoryRequirements( + VkImage image) const; + std::vector<VkPipelineExecutablePropertiesKHR> GetPipelineExecutablePropertiesKHR( VkPipeline pipeline) const; |
