mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
Port hakuX Vulkan memory budget and surface pool
This commit is contained in:
@@ -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){
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user