/* * Geforce NV2A PGRAPH Vulkan Renderer * * Copyright (c) 2024-2025 Matt Borgerson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . */ #include "hw/xbox/nv2a/nv2a_int.h" #include "renderer.h" #include "gloffscreen.h" #if HAVE_EXTERNAL_MEMORY static GloContext *g_gl_context; #endif static void early_context_init(void) { #if HAVE_EXTERNAL_MEMORY g_gl_context = glo_context_create(); #endif } static void pgraph_vk_init(NV2AState *d, Error **errp) { PGRAPHState *pg = &d->pgraph; pg->vk_renderer_state = (PGRAPHVkState *)g_malloc0(sizeof(PGRAPHVkState)); #if HAVE_EXTERNAL_MEMORY glo_set_current(g_gl_context); #endif pgraph_vk_debug_init(); pgraph_vk_init_instance(pg, errp); if (*errp) { return; } pgraph_vk_init_command_buffers(pg); pgraph_vk_init_buffers(d); pgraph_vk_init_surfaces(pg); pgraph_vk_init_shaders(pg); pgraph_vk_init_pipelines(pg); pgraph_vk_init_textures(pg); pgraph_vk_init_reports(pg); pgraph_vk_init_compute(pg); pgraph_vk_init_display(pg); pgraph_vk_update_vertex_ram_buffer(&d->pgraph, 0, d->vram_ptr, memory_region_size(d->vram)); } static void pgraph_vk_finalize(NV2AState *d) { PGRAPHState *pg = &d->pgraph; pgraph_vk_finalize_display(pg); pgraph_vk_finalize_compute(pg); pgraph_vk_finalize_reports(pg); pgraph_vk_finalize_textures(pg); pgraph_vk_finalize_pipelines(pg); pgraph_vk_finalize_shaders(pg); pgraph_vk_finalize_surfaces(pg); pgraph_vk_finalize_buffers(d); pgraph_vk_finalize_command_buffers(pg); pgraph_vk_finalize_instance(pg); g_free(pg->vk_renderer_state); pg->vk_renderer_state = NULL; } static void pgraph_vk_flush(NV2AState *d) { PGRAPHState *pg = &d->pgraph; pgraph_vk_finish(pg, VK_FINISH_REASON_FLUSH); pgraph_vk_surface_flush(d); pgraph_vk_mark_textures_possibly_dirty(d, 0, memory_region_size(d->vram)); pgraph_vk_update_vertex_ram_buffer(&d->pgraph, 0, d->vram_ptr, memory_region_size(d->vram)); for (int i = 0; i < 4; i++) { pg->texture_dirty[i] = true; } /* FIXME: Flush more? */ qatomic_set(&d->pgraph.flush_pending, false); qemu_event_set(&d->pgraph.flush_complete); } static void pgraph_vk_sync(NV2AState *d) { PGRAPHState *pg = &d->pgraph; pgraph_vk_render_display(pg); qatomic_set(&d->pgraph.sync_pending, false); qemu_event_set(&d->pgraph.sync_complete); } static void pgraph_vk_process_pending(NV2AState *d) { PGRAPHVkState *r = d->pgraph.vk_renderer_state; if (qatomic_read(&r->downloads_pending) || qatomic_read(&r->download_dirty_surfaces_pending) || qatomic_read(&d->pgraph.sync_pending) || qatomic_read(&d->pgraph.flush_pending) ) { qemu_mutex_unlock(&d->pfifo.lock); qemu_mutex_lock(&d->pgraph.lock); if (qatomic_read(&r->downloads_pending)) { pgraph_vk_process_pending_downloads(d); } if (qatomic_read(&r->download_dirty_surfaces_pending)) { pgraph_vk_download_dirty_surfaces(d); } if (qatomic_read(&d->pgraph.sync_pending)) { pgraph_vk_sync(d); } if (qatomic_read(&d->pgraph.flush_pending)) { pgraph_vk_flush(d); } qemu_mutex_unlock(&d->pgraph.lock); qemu_mutex_lock(&d->pfifo.lock); } } static void pgraph_vk_flip_stall(NV2AState *d) { pgraph_vk_finish(&d->pgraph, VK_FINISH_REASON_FLIP_STALL); pgraph_vk_debug_frame_terminator(); } static void pgraph_vk_pre_savevm_trigger(NV2AState *d) { qatomic_set(&d->pgraph.vk_renderer_state->download_dirty_surfaces_pending, true); qemu_event_reset(&d->pgraph.vk_renderer_state->dirty_surfaces_download_complete); } static void pgraph_vk_pre_savevm_wait(NV2AState *d) { qemu_event_wait(&d->pgraph.vk_renderer_state->dirty_surfaces_download_complete); } static void pgraph_vk_pre_shutdown_trigger(NV2AState *d) { // qatomic_set(&d->pgraph.vk_renderer_state->shader_cache_writeback_pending, true); // qemu_event_reset(&d->pgraph.vk_renderer_state->shader_cache_writeback_complete); } static void pgraph_vk_pre_shutdown_wait(NV2AState *d) { // qemu_event_wait(&d->pgraph.vk_renderer_state->shader_cache_writeback_complete); } static int pgraph_vk_get_framebuffer_surface(NV2AState *d) { PGRAPHState *pg = &d->pgraph; PGRAPHVkState *r = pg->vk_renderer_state; qemu_mutex_lock(&d->pfifo.lock); VGADisplayParams vga_display_params; d->vga.get_params(&d->vga, &vga_display_params); SurfaceBinding *surface = pgraph_vk_surface_get_within( d, d->pcrtc.start + vga_display_params.line_offset); if (surface == NULL || !surface->color) { qemu_mutex_unlock(&d->pfifo.lock); return 0; } assert(surface->color); surface->frame_time = pg->frame_time; #if HAVE_EXTERNAL_MEMORY qemu_event_reset(&d->pgraph.sync_complete); qatomic_set(&pg->sync_pending, true); pfifo_kick(d); qemu_mutex_unlock(&d->pfifo.lock); qemu_event_wait(&d->pgraph.sync_complete); return r->display.gl_texture_id; #else qemu_mutex_unlock(&d->pfifo.lock); pgraph_vk_wait_for_surface_download(surface); return 0; #endif } static PGRAPHRenderer pgraph_vk_renderer = { .type = CONFIG_DISPLAY_RENDERER_VULKAN, .name = "Vulkan", .ops = { .init = pgraph_vk_init, .early_context_init = early_context_init, .finalize = pgraph_vk_finalize, .clear_report_value = pgraph_vk_clear_report_value, .clear_surface = pgraph_vk_clear_surface, .draw_begin = pgraph_vk_draw_begin, .draw_end = pgraph_vk_draw_end, .flip_stall = pgraph_vk_flip_stall, .flush_draw = pgraph_vk_flush_draw, .get_report = pgraph_vk_get_report, .image_blit = pgraph_vk_image_blit, .pre_savevm_trigger = pgraph_vk_pre_savevm_trigger, .pre_savevm_wait = pgraph_vk_pre_savevm_wait, .pre_shutdown_trigger = pgraph_vk_pre_shutdown_trigger, .pre_shutdown_wait = pgraph_vk_pre_shutdown_wait, .process_pending = pgraph_vk_process_pending, .process_pending_reports = pgraph_vk_process_pending_reports, .surface_update = pgraph_vk_surface_update, .set_surface_scale_factor = pgraph_vk_set_surface_scale_factor, .get_surface_scale_factor = pgraph_vk_get_surface_scale_factor, .get_framebuffer_surface = pgraph_vk_get_framebuffer_surface, } }; static void __attribute__((constructor)) register_renderer(void) { pgraph_renderer_register(&pgraph_vk_renderer); } void pgraph_vk_check_memory_budget(PGRAPHState *pg) { #if 0 // FIXME PGRAPHVkState *r = pg->vk_renderer_state; VkPhysicalDeviceMemoryProperties const *props; vmaGetMemoryProperties(r->allocator, &props); g_autofree VmaBudget *budgets = g_malloc_n(props->memoryHeapCount, sizeof(VmaBudget)); vmaGetHeapBudgets(r->allocator, budgets); const float budget_threshold = 0.8; bool near_budget = false; for (int i = 0; i < props->memoryHeapCount; i++) { VmaBudget *b = &budgets[i]; float use_to_budget_ratio = (double)b->statistics.allocationBytes / (double)b->budget; NV2A_VK_DPRINTF("Heap %d: used %lu/%lu MiB (%.2f%%)", i, b->statistics.allocationBytes / (1024 * 1024), b->budget / (1024 * 1024), use_to_budget_ratio * 100); near_budget |= use_to_budget_ratio > budget_threshold; } // If any heaps are near budget, free up some resources if (near_budget) { pgraph_vk_trim_texture_cache(pg); } #endif #if 0 char *s; vmaBuildStatsString(r->allocator, &s, VK_TRUE); puts(s); vmaFreeStatsString(r->allocator, s); #endif }