aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp3
-rw-r--r--src/video_core/texture_cache/image_base.h1
-rw-r--r--src/video_core/texture_cache/texture_cache.h11
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp48
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h5
5 files changed, 65 insertions, 3 deletions
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 312617475d..7e2420200e 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1879,6 +1879,9 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info,
runtime->ViewFormats(info.format))),
aspect_mask(ImageAspectMask(info.format)) {
+ if (original_image.IsUnbacked()) {
+ flags |= VideoCommon::ImageFlagBits::Unbacked;
+ }
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
switch (Settings::values.accelerate_astc.GetValue()) {
case Settings::AstcDecodeMode::Gpu:
diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h
index 79fc8505d6..846a6f7fc1 100644
--- a/src/video_core/texture_cache/image_base.h
+++ b/src/video_core/texture_cache/image_base.h
@@ -45,6 +45,7 @@ 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.
};
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 cd4ef82dbe..f3da5684a5 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -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()) {
+ if (!image.IsSafeGpuCopy() || True(image.flags & ImageFlagBits::Unbacked)) {
return false;
}
if (image.info.num_samples == 1) {
@@ -1146,6 +1146,12 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
TrackImage(image, image_id);
+ if (True(image.flags & ImageFlagBits::Unbacked)) {
+ // Nothing to upload into (and staging it could take tens of GB).
+ runtime.TransitionImageLayout(image);
+ return;
+ }
+
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
runtime.TransitionImageLayout(image);
@@ -2669,7 +2675,8 @@ 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)) {
+ if (True(old_view.flags & ImageViewFlagBits::PreemtiveDownload) &&
+ False(slot_images[old_view.image_id].flags & ImageFlagBits::Unbacked)) {
const PendingDownload new_download{true, 0, old_view.image_id};
uncommitted_downloads.emplace_back(new_download);
}
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index f4721543ae..aafb3183b6 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -221,7 +221,53 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci) const
VkImage handle{};
VmaAllocation allocation{};
VmaAllocationInfo alloc_info{};
- vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &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);
+ 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) {
+ // Over the device memory budget (Switch 2 titles fill 8 GB cards): let the driver page
+ // it out rather than failing the draw and aborting.
+ static u32 logged{};
+ if (logged++ < 32) {
+ LOG_WARNING(Render_Vulkan,
+ "Image {}x{}x{} ({} levels, {} layers, format {}) is over the memory "
+ "budget; allocating it anyway",
+ ci.extent.width, ci.extent.height, ci.extent.depth, ci.mipLevels,
+ ci.arrayLayers, static_cast<u32>(ci.format));
+ }
+ VmaAllocationCreateInfo relaxed = alloc_ci;
+ relaxed.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
+ result = vmaCreateImage(allocator, &ci, &relaxed, &handle, &allocation, &alloc_info);
+ }
+ if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY && ci.imageType != VK_IMAGE_TYPE_1D) {
+ // Too big to back at all, e.g. title 1's sparse virtual texture arrays (8192x8192,
+ // 68 layers, ~23 GB): on the console only resident tiles have memory. Create a sparse
+ // resident image with nothing bound, so views and copies still work; reads of it return
+ // zero and writes are dropped, instead of aborting.
+ VkImageCreateInfo sparse_ci = ci;
+ sparse_ci.flags |= VK_IMAGE_CREATE_SPARSE_BINDING_BIT | VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT;
+ VkImage sparse{};
+ if (device.GetDispatchLoader().vkCreateImage(*device.GetLogical(), &sparse_ci, nullptr,
+ &sparse) == VK_SUCCESS) {
+ LOG_WARNING(Render_Vulkan,
+ "Image {}x{}x{} ({} levels, {} layers, format {}) does not fit; using an "
+ "unbacked sparse image",
+ ci.extent.width, ci.extent.height, ci.extent.depth, ci.mipLevels,
+ ci.arrayLayers, static_cast<u32>(ci.format));
+ return vk::Image(sparse, ci.usage, *device.GetLogical(), allocator, VK_NULL_HANDLE,
+ device.GetDispatchLoader());
+ }
+ }
+ if (result != VK_SUCCESS) {
+ LOG_CRITICAL(Render_Vulkan, "Image {}x{}x{} ({} levels, {} layers, format {}) failed: {}",
+ ci.extent.width, ci.extent.height, ci.extent.depth, ci.mipLevels,
+ ci.arrayLayers, static_cast<u32>(ci.format), static_cast<int>(result));
+ }
+ vk::Check(result);
// Log GPU memory allocation for images
if (GPU::Logging::IsActive() &&
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 97d2ba3700..947150648d 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -689,6 +689,11 @@ public:
allocation{allocation_}, dld{&dld_} {}
Image() = default;
+ /// True for an image created without memory (an unbacked sparse image).
+ bool IsUnbacked() const noexcept {
+ return handle != VK_NULL_HANDLE && allocation == VK_NULL_HANDLE;
+ }
+
Image(const Image&) = delete;
Image& operator=(const Image&) = delete;