aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/vulkan_common
diff options
context:
space:
mode:
authornéo Luigi <85024393+neoluigi4123@users.noreply.github.com>2026-11-03 00:49:36 +0100
committernéo Luigi <85024393+neoluigi4123@users.noreply.github.com>2026-11-03 00:49:36 +0100
commitc84e5caf82a786f748c233e27b7f4ed9aaad9f44 (patch)
tree3ec9cab90d74292445d0a076370933bac946fc4d /src/video_core/vulkan_common
parent2b413a46a2820f0a646baaa8dbd683009f096f32 (diff)
parentefa7c19e59839f0d464c2e9bd41f9ca8f867fef0 (diff)
Merge remote-tracking branch 'origin/nx2' into neo.ai/vibration-fix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> # Conflicts: # src/core/hle/service/hid/hid_server.cpp # src/core/hle/service/hid/hid_server.h
Diffstat (limited to 'src/video_core/vulkan_common')
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp72
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h65
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp86
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.h22
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp14
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h14
6 files changed, 263 insertions, 10 deletions
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 623ab7c30d..01e13cb384 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -620,12 +620,16 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
}
sets_per_pool = 64;
+ // AMD GCN4 and older are told apart by their lack of float16, read before float16 is turned
+ // off for newer AMD drivers below: checked after that, every AMD GPU looked like GCN4 and lost
+ // VK_EXT_sampler_filter_minmax (an RDNA3 Radeon 780M logged it).
+ const bool amd_gcn4_or_older = is_amd && !features.shader_float16_int8.shaderFloat16;
if (is_amd_driver) {
// AMD drivers need a higher amount of Sets per Pool in certain circumstances like in XC2.
sets_per_pool = 96;
// Disable VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT on AMD GCN4 and lower as it is broken.
- if (!features.shader_float16_int8.shaderFloat16) {
+ if (amd_gcn4_or_older) {
LOG_WARNING(Render_Vulkan,
"AMD GCN4 and earlier have broken VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT");
has_broken_cube_compatibility = true;
@@ -641,7 +645,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (extensions.sampler_filter_minmax && is_amd) {
// Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken.
- if (!features.shader_float16_int8.shaderFloat16) {
+ if (amd_gcn4_or_older) {
LOG_WARNING(Render_Vulkan,
"AMD GCN4 and earlier have broken VK_EXT_sampler_filter_minmax");
RemoveExtension(extensions.sampler_filter_minmax,
@@ -775,10 +779,14 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
.flags = flags,
.physicalDevice = physical,
.device = *logical,
- // 64 MiB blocks on discrete GPUs too: with 256 MiB ones, Switch 2 titles' mix of
+ // 16 MiB blocks on discrete GPUs too: with 256 MiB ones, Switch 2 titles' mix of
// texture sizes left ~38% of the image blocks empty (2 GB of an 8 GB card in title
- // 1), which the texture cache counted as used and kept evicting textures for.
- .preferredLargeHeapBlockSize = 64u * 1024u * 1024u,
+ // 1), which the texture cache counted as used and kept evicting textures for. With
+ // 64 MiB ones textures created and deleted by the thousand still left ~1.7 GB of holes
+ // after ten minutes, with 32 MiB ones ~0.5 GB: a block drains only once all of its
+ // images are gone, and fewer per block drain sooner. VMA gives allocations over half a
+ // block (8 MiB) their own memory, which goes back to the driver when they are freed.
+ .preferredLargeHeapBlockSize = 16u * 1024u * 1024u,
.pAllocationCallbacks = nullptr,
.pDeviceMemoryCallbacks = nullptr,
.pHeapSizeLimit = heap_size_limit != 0 ? heap_size_limits.data() : nullptr,
@@ -800,9 +808,37 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
Device::~Device() {
SaveStaticPipelineCache();
ShutdownGPULogging();
+ for (auto& pools : separate_pools) {
+ for (VmaPool pool : pools) {
+ if (pool != VK_NULL_HANDLE) {
+ vmaDestroyPool(allocator, pool);
+ }
+ }
+ }
vmaDestroyAllocator(allocator);
}
+VmaPool Device::GetSeparatePool(SeparatePool kind, u32 memory_type) const {
+ std::scoped_lock lk{separate_pools_mutex};
+ VmaPool& pool = separate_pools[static_cast<u32>(kind)][memory_type];
+ if (pool == VK_NULL_HANDLE) {
+ const VmaPoolCreateInfo ci{
+ .memoryTypeIndex = memory_type,
+ .flags = 0,
+ .blockSize = SeparatePoolBlockSize,
+ .minBlockCount = 0,
+ .maxBlockCount = 0,
+ .priority = 0.f,
+ .minAllocationAlignment = 0,
+ .pMemoryAllocateNext = nullptr,
+ };
+ if (vmaCreatePool(allocator, &ci, &pool) != VK_SUCCESS) {
+ pool = VK_NULL_HANDLE;
+ }
+ }
+ return pool;
+}
+
void Device::LoadStaticPipelineCache() {
const auto create = [this](size_t size, const void* data) {
static_pipeline_cache = logical.CreatePipelineCache({
@@ -1511,6 +1547,15 @@ void Device::RemoveUnsuitableExtensions() {
VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
}
+ // VK_EXT_mesh_shader: Switch 2 titles draw some geometry with task and mesh programs. Only
+ // the two stages are used; the multiview, shading rate and query features need more.
+ features.mesh_shader.multiviewMeshShader = VK_FALSE;
+ features.mesh_shader.primitiveFragmentShadingRateMeshShader = VK_FALSE;
+ features.mesh_shader.meshShaderQueries = VK_FALSE;
+ extensions.mesh_shader = features.mesh_shader.taskShader && features.mesh_shader.meshShader;
+ RemoveExtensionFeatureIfUnsuitable(extensions.mesh_shader, features.mesh_shader,
+ VK_EXT_MESH_SHADER_EXTENSION_NAME);
+
// VK_KHR_shader_quad_control
extensions.shader_quad_control = features.shader_quad_control.shaderQuadControl;
RemoveExtensionFeatureIfUnsuitable(extensions.shader_quad_control, features.shader_quad_control,
@@ -1639,6 +1684,16 @@ u64 Device::GetDeviceMemoryUsage() const {
return result;
}
+u64 Device::GetDeviceMemorySlack() const {
+ if (allocator == nullptr) {
+ return 0;
+ }
+ std::array<VmaBudget, VK_MAX_MEMORY_HEAPS> heap_budgets{};
+ vmaGetHeapBudgets(allocator, heap_budgets.data());
+ const VmaStatistics& statistics = heap_budgets[main_heap].statistics;
+ return statistics.blockBytes - (std::min)(statistics.blockBytes, statistics.allocationBytes);
+}
+
u64 Device::GetDeviceMemoryBudget() const {
if (!extensions.memory_budget) {
return 0;
@@ -1663,6 +1718,13 @@ u64 Device::GetDeviceMemoryBudget() const {
result += AllocatedUsage(budget.heapUsage[heap], heap_budgets[heap]);
}
}
+ if (is_integrated) {
+ // An integrated GPU's device-local heap is system memory (a Radeon 780M reported a 21 GB
+ // budget): following it let the texture cache fill that much RAM before collecting, and
+ // kept ASTC uncompressed. Its budget is at most the share CollectPhysicalMemoryInfo
+ // gives it (4 GiB, 6 GiB with aggressive VRAM use).
+ result = (std::min)(result, device_access_memory);
+ }
return result;
}
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index d8524f6575..5f75bcc18e 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -6,7 +6,10 @@
#pragma once
+#include <array>
#include <atomic>
+#include <cstdlib>
+#include <mutex>
#include <optional>
#include <set>
#include <span>
@@ -20,6 +23,7 @@
#include "video_core/vulkan_common/vulkan_wrapper.h"
VK_DEFINE_HANDLE(VmaAllocator)
+VK_DEFINE_HANDLE(VmaPool)
// Define all features which may be used by the implementation here.
// Vulkan version in the macro describes the minimum version required for feature availability.
@@ -65,6 +69,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
+ FEATURE(EXT, MeshShader, MESH_SHADER, mesh_shader) \
FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
primitive_topology_list_restart) \
FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \
@@ -263,6 +268,17 @@ public:
return allocator;
}
+ /// Allocations kept out of the default blocks, which then hold images only: churned buffers
+ /// and long-lived sparse pages left holes the texture cache's collector couldn't drain.
+ enum class SeparatePool : u32 {
+ SparsePages,
+ DeviceBuffers,
+ };
+ static constexpr VkDeviceSize SeparatePoolBlockSize = 16ULL << 20;
+
+ /// The pool of that kind for a memory type, created on first use (null if that failed).
+ VmaPool GetSeparatePool(SeparatePool kind, u32 memory_type) const;
+
/// Returns the logical device.
const vk::Device& GetLogical() const {
return logical;
@@ -326,6 +342,28 @@ public:
return IsSparseBindingSupported() && features.features.sparseResidencyImage2D;
}
+ /// Whether a sparse image may bind the same memory to several of its blocks.
+ bool IsSparseResidencyAliasedSupported() const {
+ return IsSparseResidencyImage2DSupported() && features.features.sparseResidencyAliased;
+ }
+
+ /// Whether images with no memory bound may be sampled and used as attachments. AMD's drivers
+ /// fault on it (the GPU hangs shortly after the first such draw), so large images there are
+ /// backed normally. RADV is counted in until someone shows it doesn't need it.
+ /// EDEN_NX2_SPARSE_UNBACKED=1 allows unbound images on any driver, =0 on none.
+ bool CanUseUnbackedSparseImages() const {
+ static const char forced = [] {
+ const char* value = std::getenv("EDEN_NX2_SPARSE_UNBACKED");
+ return value == nullptr ? '\0' : value[0];
+ }();
+ if (forced == '0' || forced == '1') {
+ return forced == '1';
+ }
+ const VkDriverIdKHR id = GetDriverID();
+ return id != VK_DRIVER_ID_AMD_PROPRIETARY && id != VK_DRIVER_ID_AMD_OPEN_SOURCE &&
+ id != VK_DRIVER_ID_MESA_RADV;
+ }
+
/// Returns true for tile-based deferred renderers.
bool IsTiler() const {
switch (GetDriverID()) {
@@ -610,6 +648,12 @@ FN_MAX_LIMIT_LIST
return extensions.shader_quad_control && features.shader_quad_control.shaderQuadControl;
}
+ /// Returns true if the device supports task and mesh shaders (VK_EXT_mesh_shader).
+ bool IsExtMeshShaderSupported() const {
+ return extensions.mesh_shader && features.mesh_shader.taskShader &&
+ features.mesh_shader.meshShader;
+ }
+
/// Returns true if the device supports VK_KHR_image_format_list.
bool IsKhrImageFormatListSupported() const {
return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2;
@@ -928,6 +972,11 @@ FN_MAX_LIMIT_LIST
return 0x00010300U;
}
+ /// Returns true for an integrated GPU (its device-local memory is system memory).
+ bool IsIntegrated() const {
+ return is_integrated;
+ }
+
/// Returns true when a known debugging tool is attached.
bool HasDebuggingToolAttached() const {
return has_renderdoc || has_nsight_graphics || has_radeon_gpu_profiler;
@@ -970,9 +1019,15 @@ FN_MAX_LIMIT_LIST
/// The driver's current memory budget for this process (VK_EXT_memory_budget), in
/// GetDeviceMemoryUsage() terms: the usage at which the main device-local heap reaches its
- /// budget. 0 without the extension.
+ /// budget. 0 without the extension. The unused part of the allocator's blocks is not
+ /// subtracted: see GetDeviceMemorySlack().
u64 GetDeviceMemoryBudget() const;
+ /// Memory the allocator holds from the driver in the main device-local heap but has no
+ /// allocation in (the unused part of its blocks). GetDeviceMemoryUsage() does not count it,
+ /// yet the driver does: with the heap at its budget it is not available to new images.
+ u64 GetDeviceMemorySlack() const;
+
u32 GetSetsPerPool() const {
return sets_per_pool;
}
@@ -1106,6 +1161,12 @@ FN_MAX_LIMIT_LIST
return properties.driver.driverID == VK_DRIVER_ID_MOLTENVK;
}
+ /// The multiple vertex input binding strides must be. Metal needs 4 bytes, which MoltenVK
+ /// reports as minVertexInputBindingStrideAlignment through VK_KHR_portability_subset.
+ u32 GetMinVertexInputBindingStrideAlignment() const noexcept {
+ return IsMoltenVK() ? 4 : 1;
+ }
+
NvidiaArchitecture GetNvidiaArch() const noexcept {
return nvidia_arch;
}
@@ -1156,6 +1217,8 @@ private:
private:
VkInstance instance; ///< Vulkan instance.
VmaAllocator allocator{}; ///< VMA allocator.
+ mutable std::mutex separate_pools_mutex;
+ mutable std::array<std::array<VmaPool, VK_MAX_MEMORY_TYPES>, 2> separate_pools{};
vk::DeviceDispatch dld; ///< Device function pointers.
vk::PhysicalDevice physical; ///< Physical device.
vk::Device logical; ///< Logical device.
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index a0c96d1b45..a0a39abe39 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -209,7 +209,10 @@ MemoryAllocator::~MemoryAllocator() = default;
vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci, bool dedicated) const
{
const VmaAllocationCreateInfo alloc_ci = {
- .flags = static_cast<VmaAllocationCreateFlags>(VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) |
+ // Best fit: images freed by the thousand leave holes in the blocks, which new ones
+ // should fill before VMA takes another block.
+ .flags = static_cast<VmaAllocationCreateFlags>(VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT |
+ VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT) |
(dedicated ? VmaAllocationCreateFlags{VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT}
: VmaAllocationCreateFlags{0}),
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
@@ -242,11 +245,32 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci, bool dedicat
ci.extent.width, ci.extent.height, ci.extent.depth, ci.mipLevels,
ci.arrayLayers, static_cast<u32>(ci.format));
}
+ pressure_events.fetch_add(1, std::memory_order_relaxed);
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 && !huge) {
+ // Still out of device memory: a real image in host memory (slow, but valid) beats one
+ // with nothing bound, which AMD's drivers do not survive being sampled.
+ VmaAllocationCreateInfo host = alloc_ci;
+ host.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
+ host.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
+ host.preferredFlags = 0;
+ result = vmaCreateImage(allocator, &ci, &host, &handle, &allocation, &alloc_info);
+ static u32 host_logged{};
+ if (result == VK_SUCCESS && host_logged++ < 16) {
+ LOG_WARNING(Render_Vulkan,
+ "Image {}x{}x{} ({} levels, {} layers, format {}) is out of device memory; "
+ "placed in host memory",
+ ci.extent.width, ci.extent.height, ci.extent.depth, ci.mipLevels,
+ ci.arrayLayers, static_cast<u32>(ci.format));
+ }
+ }
if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY && ci.imageType != VK_IMAGE_TYPE_1D) {
+ if (!huge) {
+ pressure_events.fetch_add(1, std::memory_order_relaxed);
+ }
// 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. Use an image with
// nothing bound, so views and copies still work, instead of aborting.
@@ -280,12 +304,15 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci, bool dedicat
device.GetDispatchLoader());
}
-vk::Image MemoryAllocator::CreateUnbackedImage(const VkImageCreateInfo &ci) const {
+vk::Image MemoryAllocator::CreateUnbackedImage(const VkImageCreateInfo &ci, bool aliased) const {
if (ci.imageType == VK_IMAGE_TYPE_1D) {
return vk::Image{};
}
VkImageCreateInfo sparse_ci = ci;
sparse_ci.flags |= VK_IMAGE_CREATE_SPARSE_BINDING_BIT | VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT;
+ if (aliased) {
+ sparse_ci.flags |= VK_IMAGE_CREATE_SPARSE_ALIASED_BIT;
+ }
VkImage sparse{};
if (device.GetDispatchLoader().vkCreateImage(*device.GetLogical(), &sparse_ci, nullptr,
&sparse) != VK_SUCCESS) {
@@ -310,6 +337,24 @@ VmaAllocation MemoryAllocator::AllocateSparseMemory(const VkMemoryRequirements &
};
VmaAllocation allocation{};
VmaAllocationInfo info{};
+ // In a pool of their own (see Device::SeparatePool); anything over half a block, or a pool
+ // allocation that fails, takes the default blocks.
+ u32 memory_type{};
+ if (requirements.size <= Device::SeparatePoolBlockSize / 2 &&
+ vmaFindMemoryTypeIndex(allocator, requirements.memoryTypeBits, &alloc_ci, &memory_type) ==
+ VK_SUCCESS) {
+ if (const VmaPool pool =
+ device.GetSeparatePool(Device::SeparatePool::SparsePages, memory_type)) {
+ VmaAllocationCreateInfo pool_ci = alloc_ci;
+ pool_ci.pool = pool;
+ if (vmaAllocateMemory(allocator, &requirements, &pool_ci, &allocation, &info) ==
+ VK_SUCCESS) {
+ memory = info.deviceMemory;
+ offset = info.offset;
+ return allocation;
+ }
+ }
+ }
if (vmaAllocateMemory(allocator, &requirements, &alloc_ci, &allocation, &info) != VK_SUCCESS) {
return VK_NULL_HANDLE;
}
@@ -324,6 +369,39 @@ void MemoryAllocator::FreeSparseMemory(VmaAllocation allocation) const {
}
}
+bool MemoryAllocator::CreateBufferInSeparatePool(const VkBufferCreateInfo& ci, MemoryUsage usage,
+ const VmaAllocationCreateInfo& alloc_ci,
+ VkBuffer& handle, VmaAllocation& allocation,
+ VmaAllocationInfo& alloc_info) const {
+ // The buffer cache's buffers, churned by its collector: in a pool of their own (see
+ // Device::SeparatePool). Anything over half a block takes the default blocks (dedicated).
+ if (usage != MemoryUsage::DeviceLocal || ci.size > Device::SeparatePoolBlockSize / 2) {
+ return false;
+ }
+ u32 memory_type{};
+ {
+ std::scoped_lock lk{buffer_type_mutex};
+ const auto key = (u64{ci.usage} << 32) | ci.flags;
+ if (const auto it = buffer_memory_types.find(key); it != buffer_memory_types.end()) {
+ memory_type = it->second;
+ } else if (vmaFindMemoryTypeIndexForBufferInfo(allocator, &ci, &alloc_ci, &memory_type) ==
+ VK_SUCCESS) {
+ buffer_memory_types.emplace(key, memory_type);
+ } else {
+ return false;
+ }
+ }
+ const VmaPool pool = device.GetSeparatePool(Device::SeparatePool::DeviceBuffers, memory_type);
+ if (pool == VK_NULL_HANDLE) {
+ return false;
+ }
+ VmaAllocationCreateInfo pool_ci = alloc_ci;
+ pool_ci.pool = pool;
+ pool_ci.memoryTypeBits = 0;
+ return vmaCreateBuffer(allocator, &ci, &pool_ci, &handle, &allocation, &alloc_info) ==
+ VK_SUCCESS;
+}
+
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
@@ -345,7 +423,9 @@ vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsa
VmaAllocation allocation{};
VkMemoryPropertyFlags property_flags{};
- vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
+ if (!CreateBufferInSeparatePool(ci, usage, alloc_ci, handle, allocation, alloc_info)) {
+ vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
+ }
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
// Log GPU memory allocation for buffers
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
index f3c6c177a2..8b9e72b3e3 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.h
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -6,8 +6,11 @@
#pragma once
+#include <atomic>
#include <memory>
+#include <mutex>
#include <span>
+#include <unordered_map>
#include <vector>
#include "common/common_types.h"
@@ -109,14 +112,27 @@ namespace Vulkan {
/// A sparse resident image with nothing bound (reads return zero, writes are dropped), or
/// a null image when the device can't make one.
- vk::Image CreateUnbackedImage(const VkImageCreateInfo &ci) const;
+ /// aliased: its sparse blocks may share memory (VK_IMAGE_CREATE_SPARSE_ALIASED_BIT).
+ vk::Image CreateUnbackedImage(const VkImageCreateInfo &ci, bool aliased = false) const;
/// Device memory to bind to part of a sparse resource. Null when there is none left.
+ /// Device-local buffers in their own VMA pool; false when it doesn't apply or failed.
+ bool CreateBufferInSeparatePool(const VkBufferCreateInfo& ci, MemoryUsage usage,
+ const VmaAllocationCreateInfo& alloc_ci, VkBuffer& handle,
+ VmaAllocation& allocation,
+ VmaAllocationInfo& alloc_info) const;
+
VmaAllocation AllocateSparseMemory(const VkMemoryRequirements &requirements,
VkDeviceMemory &memory, VkDeviceSize &offset) const;
void FreeSparseMemory(VmaAllocation allocation) const;
+ /// Counts the images that did not fit the device memory budget (made anyway, in host
+ /// memory or with nothing bound): the texture cache collects harder when it grows.
+ u32 MemoryPressureEvents() const noexcept {
+ return pressure_events.load(std::memory_order_relaxed);
+ }
+
vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const;
vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage,
@@ -152,6 +168,10 @@ namespace Vulkan {
const VkPhysicalDeviceMemoryProperties properties; ///< Physical device memory properties.
VkDeviceSize buffer_image_granularity; ///< Adjacent buffer/image granularity
u32 valid_memory_types{~0u};
+ mutable std::atomic<u32> pressure_events{0};
+ mutable std::mutex buffer_type_mutex;
+ /// Memory type of device-local buffers by usage and flags (CreateBufferInSeparatePool).
+ mutable std::unordered_map<u64, u32> buffer_memory_types;
};
/// Logs VMA's blocks and allocations per memory type, and each heap's budget.
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index dbaaf450a0..d0bca88125 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -116,6 +116,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkCmdDrawIndirectCount);
X(vkCmdDrawIndexedIndirectCount);
X(vkCmdDrawIndirectByteCountEXT);
+ X(vkCmdDrawMeshTasksEXT);
X(vkCmdEndConditionalRenderingEXT);
X(vkCmdEndQuery);
X(vkCmdEndRenderPass);
@@ -525,6 +526,19 @@ void Image::Release() const noexcept {
}
}
+Image::MemoryBlock Image::GetMemoryBlock() const noexcept {
+ if (allocation == nullptr) {
+ return {};
+ }
+ VmaAllocationInfo2 info{};
+ vmaGetAllocationInfo2(allocator, allocation, &info);
+ if (info.dedicatedMemory) {
+ return {0, info.blockSize, info.allocationInfo.size};
+ }
+ return {reinterpret_cast<u64>(info.allocationInfo.deviceMemory), info.blockSize,
+ info.allocationInfo.size};
+}
+
void Buffer::Flush() const {
if (!is_coherent) {
vmaFlushAllocation(allocator, allocation, 0, VK_WHOLE_SIZE);
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 3010368973..f49e9518bb 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -232,6 +232,7 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{};
PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{};
PFN_vkCmdDrawIndirectByteCountEXT vkCmdDrawIndirectByteCountEXT{};
+ PFN_vkCmdDrawMeshTasksEXT vkCmdDrawMeshTasksEXT{};
PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT{};
PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{};
PFN_vkCmdEndQuery vkCmdEndQuery{};
@@ -736,6 +737,15 @@ public:
return usage;
}
+ /// Where the image's memory lies: the block (0 when the image has no memory or a block of
+ /// its own), the block's size and the image's part of it.
+ struct MemoryBlock {
+ u64 block;
+ u64 block_size;
+ u64 size;
+ };
+ [[nodiscard]] MemoryBlock GetMemoryBlock() const noexcept;
+
private:
void Release() const noexcept;
@@ -1319,6 +1329,10 @@ public:
dld->vkCmdDrawIndexedIndirect(handle, src_buffer, src_offset, draw_count, stride);
}
+ void DrawMeshTasksEXT(u32 group_count_x, u32 group_count_y, u32 group_count_z) const noexcept {
+ dld->vkCmdDrawMeshTasksEXT(handle, group_count_x, group_count_y, group_count_z);
+ }
+
void DrawIndirectCount(VkBuffer src_buffer, VkDeviceSize src_offset, VkBuffer count_buffer,
VkDeviceSize count_offset, u32 draw_count, u32 stride) const noexcept {
dld->vkCmdDrawIndirectCount(handle, src_buffer, src_offset, count_buffer, count_offset,