Port hakuX Vulkan memory budget and surface pool

This commit is contained in:
izzy2lost
2026-04-08 02:34:51 -04:00
parent 6bb9cdb1b7
commit 4f8d079d29
4 changed files with 286 additions and 39 deletions
+97 -7
View File
@@ -23,6 +23,76 @@
#include <android/log.h>
#endif
typedef struct MemoryBudget {
size_t total_heap;
size_t renderer_budget;
size_t vertex_inline_cap;
size_t index_cap;
size_t staging_cap;
size_t shader_module_cache_entries;
} MemoryBudget;
static MemoryBudget compute_memory_budget(PGRAPHVkState *r)
{
const size_t mib = 1024 * 1024;
const size_t gib = 1024 * mib;
VkPhysicalDeviceMemoryProperties const *props;
vmaGetMemoryProperties(r->allocator, &props);
size_t total_heap = 0;
for (uint32_t i = 0; i < props->memoryHeapCount; i++) {
if (props->memoryHeaps[i].size > total_heap) {
total_heap = props->memoryHeaps[i].size;
}
}
MemoryBudget b = { .total_heap = total_heap };
#ifdef __ANDROID__
if (total_heap <= 4 * gib) {
b.renderer_budget = 512 * mib;
} else if (total_heap <= 6 * gib) {
b.renderer_budget = 768 * mib;
} else if (total_heap <= 8 * gib) {
b.renderer_budget = 1024 * mib;
} else if (total_heap <= 12 * gib) {
b.renderer_budget = 1536 * mib;
} else if (total_heap <= 16 * gib) {
b.renderer_budget = 2048 * mib;
} else if (total_heap <= 22 * gib) {
b.renderer_budget = 3072 * mib;
} else {
b.renderer_budget = SIZE_MAX;
}
#else
b.renderer_budget = SIZE_MAX;
#endif
if (b.renderer_budget == SIZE_MAX) {
b.vertex_inline_cap = SIZE_MAX;
b.index_cap = SIZE_MAX;
b.staging_cap = SIZE_MAX;
b.shader_module_cache_entries = 50 * 1024;
} else {
size_t budget = b.renderer_budget;
size_t budget_mib = budget / mib;
b.vertex_inline_cap = MAX(8 * mib, budget / 5);
b.index_cap = MAX(4 * mib, budget / 20);
b.staging_cap = MAX(16 * mib, budget * 12 / 100);
b.shader_module_cache_entries = budget_mib * 4;
if (b.shader_module_cache_entries < 2048) {
b.shader_module_cache_entries = 2048;
}
if (b.shader_module_cache_entries > 50 * 1024) {
b.shader_module_cache_entries = 50 * 1024;
}
}
return b;
}
static const char *const buffer_names[BUFFER_COUNT] = {
"BUFFER_STAGING_DST",
"BUFFER_STAGING_SRC",
@@ -76,14 +146,14 @@ bool pgraph_vk_init_buffers(NV2AState *d, Error **errp)
PGRAPHState *pg = &d->pgraph;
PGRAPHVkState *r = pg->vk_renderer_state;
// FIXME: Profile buffer sizes
const size_t mib = 1024 * 1024;
size_t vram_size = memory_region_size(d->vram);
MemoryBudget mb = compute_memory_budget(r);
size_t staging_size = vram_size;
if (staging_size < (16 * mib)) {
staging_size = 16 * mib;
}
staging_size = MIN(staging_size, mb.staging_cap);
size_t compute_size = vram_size * 2;
if (compute_size < (64 * mib)) {
compute_size = 64 * mib;
@@ -98,7 +168,28 @@ bool pgraph_vk_init_buffers(NV2AState *d, Error **errp)
}
#endif
size_t index_size = sizeof(pg->inline_elements) * 100;
index_size = MIN(index_size, mb.index_cap);
size_t vertex_inline_size = NV2A_VERTEXSHADER_ATTRIBUTES *
NV2A_MAX_BATCH_LENGTH *
4 * sizeof(float) * 10;
vertex_inline_size = MIN(vertex_inline_size, mb.vertex_inline_cap);
r->shader_module_cache_target = mb.shader_module_cache_entries;
#ifdef __ANDROID__
__android_log_print(ANDROID_LOG_INFO, "xemu-android",
"vk memory budget: heap=%zuMB budget=%s%zuMB staging_cap=%zuMB index_cap=%zuMB vtx_inline_cap=%zuMB shader_cache=%zu",
mb.total_heap >> 20,
mb.renderer_budget == SIZE_MAX ? "uncapped/" : "",
mb.renderer_budget == SIZE_MAX ? 0 :
mb.renderer_budget >> 20,
mb.staging_cap == SIZE_MAX ? 0 : mb.staging_cap >> 20,
mb.index_cap == SIZE_MAX ? 0 : mb.index_cap >> 20,
mb.vertex_inline_cap == SIZE_MAX ? 0 :
mb.vertex_inline_cap >> 20,
mb.shader_module_cache_entries);
__android_log_print(ANDROID_LOG_INFO, "xemu-android",
"vk buffer init: vram=%zu staging=%zu compute=%zu",
vram_size, staging_size, compute_size);
@@ -143,13 +234,13 @@ bool pgraph_vk_init_buffers(NV2AState *d, Error **errp)
.alloc_info = device_alloc_create_info,
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
.buffer_size = sizeof(pg->inline_elements) * 100,
.buffer_size = index_size,
};
r->storage_buffers[BUFFER_INDEX_STAGING] = (StorageBuffer){
.alloc_info = host_alloc_create_info,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.buffer_size = r->storage_buffers[BUFFER_INDEX].buffer_size,
.buffer_size = index_size,
};
// FIXME: Don't assume that we can render with host mapped buffer
@@ -171,14 +262,13 @@ bool pgraph_vk_init_buffers(NV2AState *d, Error **errp)
.alloc_info = device_alloc_create_info,
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
.buffer_size = NV2A_VERTEXSHADER_ATTRIBUTES * NV2A_MAX_BATCH_LENGTH *
4 * sizeof(float) * 10,
.buffer_size = vertex_inline_size,
};
r->storage_buffers[BUFFER_VERTEX_INLINE_STAGING] = (StorageBuffer){
.alloc_info = host_alloc_create_info,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.buffer_size = r->storage_buffers[BUFFER_VERTEX_INLINE].buffer_size,
.buffer_size = vertex_inline_size,
};
r->storage_buffers[BUFFER_UNIFORM] = (StorageBuffer){
+20
View File
@@ -148,6 +148,21 @@ typedef struct SurfaceBinding {
bool initialized;
} SurfaceBinding;
typedef struct SurfaceImageConfig {
VkFormat format;
uint32_t width, height;
VkImageUsageFlags usage;
} SurfaceImageConfig;
typedef struct PooledSurfaceImage {
QTAILQ_ENTRY(PooledSurfaceImage) entry;
SurfaceImageConfig config;
VkImage image;
VmaAllocation allocation;
VkImage image_scratch;
VmaAllocation allocation_scratch;
} PooledSurfaceImage;
typedef struct ShaderModuleInfo {
int refcnt;
char *glsl;
@@ -393,6 +408,8 @@ typedef struct PGRAPHVkState {
QTAILQ_HEAD(, SurfaceBinding) surfaces;
QTAILQ_HEAD(, SurfaceBinding) invalid_surfaces;
QTAILQ_HEAD(, PooledSurfaceImage) surface_image_pool;
int surface_image_pool_count;
SurfaceBinding *color_binding, *zeta_binding;
bool downloads_pending;
QemuEvent downloads_complete;
@@ -415,6 +432,7 @@ typedef struct PGRAPHVkState {
Lru shader_module_cache;
ShaderModuleCacheEntry *shader_module_cache_entries;
size_t shader_module_cache_target;
// FIXME: Merge these into a structure
uint64_t uniform_buffer_hashes[2];
@@ -516,6 +534,8 @@ VkDeviceSize pgraph_vk_update_vertex_inline_buffer(PGRAPHState *pg, void **data,
void pgraph_vk_init_surfaces(PGRAPHState *pg);
void pgraph_vk_finalize_surfaces(PGRAPHState *pg);
void pgraph_vk_surface_flush(NV2AState *d);
void pgraph_vk_surface_image_pool_init(PGRAPHVkState *r);
void pgraph_vk_surface_image_pool_drain(PGRAPHVkState *r);
void pgraph_vk_process_pending_downloads(NV2AState *d);
void pgraph_vk_surface_download_if_dirty(NV2AState *d, SurfaceBinding *surface);
SurfaceBinding *pgraph_vk_surface_get_within(NV2AState *d, hwaddr addr);
+11 -3
View File
@@ -385,9 +385,17 @@ static void shader_cache_init(PGRAPHState *pg)
r->shader_cache.compare_nodes = shader_cache_entry_compare;
r->shader_cache.post_node_evict = shader_cache_entry_post_evict;
/* FIXME: Make this configurable */
const size_t shader_module_cache_size = 50 * 1024;
lru_init(&r->shader_module_cache, 1 << 16);
const size_t shader_module_cache_size =
r->shader_module_cache_target ? r->shader_module_cache_target
: 50 * 1024;
size_t shader_module_hash_buckets = shader_module_cache_size * 2;
if (shader_module_hash_buckets < 4096) {
shader_module_hash_buckets = 4096;
}
if (shader_module_hash_buckets > (1 << 16)) {
shader_module_hash_buckets = 1 << 16;
}
lru_init(&r->shader_module_cache, shader_module_hash_buckets);
r->shader_module_cache_entries =
g_malloc_n(shader_module_cache_size, sizeof(ShaderModuleCacheEntry));
assert(r->shader_module_cache_entries != NULL);
+158 -29
View File
@@ -775,6 +775,87 @@ static void set_surface_label(PGRAPHState *pg, SurfaceBinding const *surface)
}
}
#define SURFACE_IMAGE_POOL_MAX_SIZE 64
void pgraph_vk_surface_image_pool_init(PGRAPHVkState *r)
{
QTAILQ_INIT(&r->surface_image_pool);
r->surface_image_pool_count = 0;
}
static bool surface_image_pool_config_match(const SurfaceImageConfig *a,
const SurfaceImageConfig *b)
{
return a->format == b->format &&
a->width == b->width &&
a->height == b->height &&
a->usage == b->usage;
}
static bool surface_image_pool_acquire(PGRAPHVkState *r,
const SurfaceImageConfig *config,
VkImage *out_image,
VmaAllocation *out_alloc,
VkImage *out_scratch,
VmaAllocation *out_scratch_alloc)
{
PooledSurfaceImage *entry;
QTAILQ_FOREACH(entry, &r->surface_image_pool, entry) {
if (surface_image_pool_config_match(&entry->config, config)) {
*out_image = entry->image;
*out_alloc = entry->allocation;
*out_scratch = entry->image_scratch;
*out_scratch_alloc = entry->allocation_scratch;
QTAILQ_REMOVE(&r->surface_image_pool, entry, entry);
g_free(entry);
r->surface_image_pool_count--;
return true;
}
}
return false;
}
static void surface_image_pool_release(PGRAPHVkState *r,
const SurfaceImageConfig *config,
VkImage image,
VmaAllocation allocation,
VkImage image_scratch,
VmaAllocation allocation_scratch)
{
if (r->surface_image_pool_count >= SURFACE_IMAGE_POOL_MAX_SIZE) {
PooledSurfaceImage *oldest = QTAILQ_FIRST(&r->surface_image_pool);
assert(oldest != NULL);
QTAILQ_REMOVE(&r->surface_image_pool, oldest, entry);
vmaDestroyImage(r->allocator, oldest->image, oldest->allocation);
vmaDestroyImage(r->allocator, oldest->image_scratch,
oldest->allocation_scratch);
g_free(oldest);
r->surface_image_pool_count--;
}
PooledSurfaceImage *entry = g_malloc(sizeof(PooledSurfaceImage));
entry->config = *config;
entry->image = image;
entry->allocation = allocation;
entry->image_scratch = image_scratch;
entry->allocation_scratch = allocation_scratch;
QTAILQ_INSERT_TAIL(&r->surface_image_pool, entry, entry);
r->surface_image_pool_count++;
}
void pgraph_vk_surface_image_pool_drain(PGRAPHVkState *r)
{
PooledSurfaceImage *entry, *next;
QTAILQ_FOREACH_SAFE(entry, &r->surface_image_pool, entry, next) {
QTAILQ_REMOVE(&r->surface_image_pool, entry, entry);
vmaDestroyImage(r->allocator, entry->image, entry->allocation);
vmaDestroyImage(r->allocator, entry->image_scratch,
entry->allocation_scratch);
g_free(entry);
}
r->surface_image_pool_count = 0;
}
static void create_surface_image(PGRAPHState *pg, SurfaceBinding *surface)
{
PGRAPHVkState *r = pg->vk_renderer_state;
@@ -790,36 +871,66 @@ static void create_surface_image(PGRAPHState *pg, SurfaceBinding *surface)
"Creating new surface image width=%d height=%d @ %08" HWADDR_PRIx,
width, height, surface->vram_addr);
VkImageCreateInfo image_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.extent.width = width,
.extent.height = height,
.extent.depth = 1,
.mipLevels = 1,
.arrayLayers = 1,
VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
surface->host_fmt.usage;
SurfaceImageConfig pool_cfg = {
.format = surface->host_fmt.vk_format,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | surface->host_fmt.usage,
.samples = VK_SAMPLE_COUNT_1_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.width = width,
.height = height,
.usage = usage,
};
VmaAllocationCreateInfo alloc_create_info = {
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
};
if (surface_image_pool_acquire(r, &pool_cfg,
&surface->image, &surface->allocation,
&surface->image_scratch,
&surface->allocation_scratch)) {
surface->image_scratch_current_layout = VK_IMAGE_LAYOUT_UNDEFINED;
} else {
VkImageCreateInfo image_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.extent.width = width,
.extent.height = height,
.extent.depth = 1,
.mipLevels = 1,
.arrayLayers = 1,
.format = surface->host_fmt.vk_format,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.usage = usage,
.samples = VK_SAMPLE_COUNT_1_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
VK_CHECK(vmaCreateImage(r->allocator, &image_create_info,
&alloc_create_info, &surface->image,
&surface->allocation, NULL));
VmaAllocationCreateInfo alloc_create_info = {
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
};
VK_CHECK(vmaCreateImage(r->allocator, &image_create_info,
&alloc_create_info, &surface->image_scratch,
&surface->allocation_scratch, NULL));
surface->image_scratch_current_layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkResult res = vmaCreateImage(r->allocator, &image_create_info,
&alloc_create_info, &surface->image,
&surface->allocation, NULL);
if (res != VK_SUCCESS) {
pgraph_vk_surface_image_pool_drain(r);
VK_CHECK(vmaCreateImage(r->allocator, &image_create_info,
&alloc_create_info, &surface->image,
&surface->allocation, NULL));
}
res = vmaCreateImage(r->allocator, &image_create_info,
&alloc_create_info, &surface->image_scratch,
&surface->allocation_scratch, NULL);
if (res != VK_SUCCESS) {
pgraph_vk_surface_image_pool_drain(r);
VK_CHECK(vmaCreateImage(r->allocator, &image_create_info,
&alloc_create_info,
&surface->image_scratch,
&surface->allocation_scratch, NULL));
}
surface->image_scratch_current_layout = VK_IMAGE_LAYOUT_UNDEFINED;
}
VkImageViewCreateInfo image_view_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
@@ -871,12 +982,28 @@ static void destroy_surface_image(PGRAPHVkState *r, SurfaceBinding *surface)
vkDestroyImageView(r->device, surface->image_view, NULL);
surface->image_view = VK_NULL_HANDLE;
vmaDestroyImage(r->allocator, surface->image, surface->allocation);
unsigned int width = surface->width ? surface->width : 1;
unsigned int height = surface->height ? surface->height : 1;
unsigned int scale_factor = g_nv2a->pgraph.surface_scale_factor;
if (scale_factor > 1) {
width *= scale_factor;
height *= scale_factor;
}
SurfaceImageConfig pool_cfg = {
.format = surface->host_fmt.vk_format,
.width = width,
.height = height,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | surface->host_fmt.usage,
};
surface_image_pool_release(r, &pool_cfg,
surface->image, surface->allocation,
surface->image_scratch,
surface->allocation_scratch);
surface->image = VK_NULL_HANDLE;
surface->allocation = VK_NULL_HANDLE;
vmaDestroyImage(r->allocator, surface->image_scratch,
surface->allocation_scratch);
surface->image_scratch = VK_NULL_HANDLE;
surface->allocation_scratch = VK_NULL_HANDLE;
}
@@ -1748,6 +1875,7 @@ void pgraph_vk_init_surfaces(PGRAPHState *pg)
QTAILQ_INIT(&r->surfaces);
QTAILQ_INIT(&r->invalid_surfaces);
pgraph_vk_surface_image_pool_init(r);
r->downloads_pending = false;
qemu_event_init(&r->downloads_complete, false);
@@ -1785,6 +1913,7 @@ void pgraph_vk_surface_flush(NV2AState *d)
invalidate_surface(d, s);
}
prune_invalid_surfaces(r, 0);
pgraph_vk_surface_image_pool_drain(r);
pgraph_vk_reload_surface_scale_factor(pg);
}