diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.c b/hw/xbox/nv2a/pgraph/gl/renderer.c index 02d28a2130..9e22a80f6d 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.c +++ b/hw/xbox/nv2a/pgraph/gl/renderer.c @@ -33,7 +33,7 @@ static void early_context_init(void) g_nv2a_context_display = glo_context_create(); } -static void pgraph_gl_init(NV2AState *d) +static void pgraph_gl_init(NV2AState *d, Error **errp) { PGRAPHState *pg = &d->pgraph; diff --git a/hw/xbox/nv2a/pgraph/null/renderer.c b/hw/xbox/nv2a/pgraph/null/renderer.c index 9a9c2512cc..8b34efc5d1 100644 --- a/hw/xbox/nv2a/pgraph/null/renderer.c +++ b/hw/xbox/nv2a/pgraph/null/renderer.c @@ -111,7 +111,7 @@ static void pgraph_null_surface_update(NV2AState *d, bool upload, { } -static void pgraph_null_init(NV2AState *d) +static void pgraph_null_init(NV2AState *d, Error **errp) { PGRAPHState *pg = &d->pgraph; pg->null_renderer_state = NULL; diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index e66dd86039..7ffc1ae5de 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -20,6 +20,7 @@ */ #include "../nv2a_int.h" +#include "ui/xemu-notifications.h" #include "ui/xemu-settings.h" #include "util.h" #include "swizzle.h" @@ -238,8 +239,6 @@ void pgraph_init(NV2AState *d) } pgraph_clear_dirty_reg_map(pg); - - pg->renderer = renderers[g_config.display.renderer]; } void pgraph_clear_dirty_reg_map(PGRAPHState *pg) @@ -247,13 +246,6 @@ void pgraph_clear_dirty_reg_map(PGRAPHState *pg) memset(pg->regs_dirty, 0, sizeof(pg->regs_dirty)); } -void pgraph_init_thread(NV2AState *d) -{ - if (d->pgraph.renderer->ops.init) { - d->pgraph.renderer->ops.init(d); - } -} - static CONFIG_DISPLAY_RENDERER get_default_renderer(void) { #ifdef CONFIG_OPENGL @@ -293,6 +285,59 @@ void nv2a_context_init(void) } } +static bool attempt_renderer_init(PGRAPHState *pg) +{ + NV2AState *d = container_of(pg, NV2AState, pgraph); + + pg->renderer = renderers[g_config.display.renderer]; + if (!pg->renderer) { + xemu_queue_error_message("Configured renderer not available"); + return false; + } + + Error *local_err = NULL; + if (pg->renderer->ops.init) { + pg->renderer->ops.init(d, &local_err); + } + if (local_err) { + const char *msg = error_get_pretty(local_err); + xemu_queue_error_message(msg); + error_free(local_err); + local_err = NULL; + return false; + } + + return true; +} + +static void init_renderer(PGRAPHState *pg) +{ + if (attempt_renderer_init(pg)) { + return; // Success + } + + CONFIG_DISPLAY_RENDERER default_renderer = get_default_renderer(); + if (default_renderer != g_config.display.renderer) { + g_config.display.renderer = default_renderer; + if (attempt_renderer_init(pg)) { + g_autofree gchar *msg = g_strdup_printf( + "Switched to default renderer: %s", pg->renderer->name); + xemu_queue_notification(msg); + return; + } + } + + // FIXME: Try others + + fprintf(stderr, "Fatal error: cannot initialize renderer\n"); + exit(1); +} + +void pgraph_init_thread(NV2AState *d) +{ + init_renderer(&d->pgraph); +} + void pgraph_destroy(PGRAPHState *pg) { NV2AState *d = container_of(pg, NV2AState, pgraph); @@ -2919,12 +2964,7 @@ void pgraph_process_pending(NV2AState *d) } } - // FIXME: Handle missing renderer, init errors - pg->renderer = renderers[g_config.display.renderer]; - - if (pg->renderer->ops.init) { - pg->renderer->ops.init(d); - } + init_renderer(pg); qemu_mutex_unlock(&d->pgraph.renderer_lock); qemu_mutex_unlock(&d->pgraph.lock); diff --git a/hw/xbox/nv2a/pgraph/pgraph.h b/hw/xbox/nv2a/pgraph/pgraph.h index 576cefcdb0..bbff5f2c58 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.h +++ b/hw/xbox/nv2a/pgraph/pgraph.h @@ -100,7 +100,7 @@ typedef struct PGRAPHRenderer { const char *name; struct { void (*early_context_init)(void); - void (*init)(NV2AState *d); + void (*init)(NV2AState *d, Error **errp); void (*finalize)(NV2AState *d); void (*clear_report_value)(NV2AState *d); void (*clear_surface)(NV2AState *d, uint32_t parameter); diff --git a/hw/xbox/nv2a/pgraph/vk/instance.c b/hw/xbox/nv2a/pgraph/vk/instance.c index 6520592146..d9504402db 100644 --- a/hw/xbox/nv2a/pgraph/vk/instance.c +++ b/hw/xbox/nv2a/pgraph/vk/instance.c @@ -98,19 +98,23 @@ static bool check_validation_layer_support(void) return true; } -static SDL_Window *create_window(void) +static void create_window(PGRAPHVkState *r, Error **errp) { - SDL_Window *window = SDL_CreateWindow( + r->window = SDL_CreateWindow( "SDL Offscreen Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN); - if (window == NULL) { - fprintf(stderr, "%s: Failed to create window\n", __func__); - SDL_Quit(); - exit(1); + if (r->window == NULL) { + error_setg(errp, "SDL_CreateWindow failed: %s", SDL_GetError()); } +} - return window; +static void destroy_window(PGRAPHVkState *r) +{ + if (r->window) { + SDL_DestroyWindow(r->window); + r->window = NULL; + } } static VkExtensionPropertiesArray * @@ -199,13 +203,22 @@ add_optional_instance_extension_names(PGRAPHState *pg, VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } -static void create_instance(PGRAPHState *pg) +static bool create_instance(PGRAPHState *pg, Error **errp) { PGRAPHVkState *r = pg->vk_renderer_state; + VkResult result; - r->window = create_window(); + create_window(r, errp); + if (*errp) { + return false; + } - VK_CHECK(volkInitialize()); + result = volkInitialize(); + if (result != VK_SUCCESS) { + error_setg(errp, "volkInitialize failed"); + destroy_window(r); + return false; + } VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, @@ -234,14 +247,19 @@ static void create_instance(PGRAPHState *pg) all_required_extensions_available = false; } } - assert(all_required_extensions_available); + + if (!all_required_extensions_available) { + error_setg(errp, "Required instance extensions not available"); + goto error; + } add_optional_instance_extension_names(pg, available_extensions, enabled_extension_names); fprintf(stderr, "Enabled instance extensions:\n"); for (int i = 0; i < enabled_extension_names->len; i++) { - fprintf(stderr, "- %s\n", g_array_index(enabled_extension_names, char *, i)); + fprintf(stderr, "- %s\n", + g_array_index(enabled_extension_names, char *, i)); } VkInstanceCreateInfo create_info = { @@ -270,7 +288,8 @@ static void create_instance(PGRAPHState *pg) if (enable_validation) { if (check_validation_layer_support()) { - fprintf(stderr, "Warning: Validation layers enabled. Expect performance impact.\n"); + fprintf(stderr, "Warning: Validation layers enabled. Expect " + "performance impact.\n"); create_info.enabledLayerCount = ARRAY_SIZE(validation_layers); create_info.ppEnabledLayerNames = validation_layers; if (r->debug_utils_extension_enabled) { @@ -283,9 +302,19 @@ static void create_instance(PGRAPHState *pg) } } - VK_CHECK(vkCreateInstance(&create_info, NULL, &r->instance)); + result = vkCreateInstance(&create_info, NULL, &r->instance); + if (result != VK_SUCCESS) { + error_setg(errp, "Failed to create instance"); + return false; + } volkLoadInstance(r->instance); + return true; + +error: + volkFinalize(); + destroy_window(r); + return false; } static bool is_queue_family_indicies_complete(QueueFamilyIndices indices) @@ -399,15 +428,18 @@ static bool is_device_compatible(VkPhysicalDevice device) // FIXME: Check vram } -static void select_physical_device(PGRAPHState *pg) +static bool select_physical_device(PGRAPHState *pg, Error **errp) { PGRAPHVkState *r = pg->vk_renderer_state; + VkResult result; uint32_t num_physical_devices = 0; - vkEnumeratePhysicalDevices(r->instance, &num_physical_devices, NULL); - if (num_physical_devices == 0) { - assert(!"failed to find GPUs with Vulkan support"); + result = + vkEnumeratePhysicalDevices(r->instance, &num_physical_devices, NULL); + if (result != VK_SUCCESS || num_physical_devices == 0) { + error_setg(errp, "Failed to find GPUs with Vulkan support"); + return false; } g_autofree VkPhysicalDevice *devices = @@ -430,7 +462,8 @@ static void select_physical_device(PGRAPHState *pg) } } if (r->physical_device == VK_NULL_HANDLE) { - assert(!"failed to find a suitable GPU"); + error_setg(errp, "Failed to find a suitable GPU"); + return false; } vkGetPhysicalDeviceProperties(r->physical_device, &r->device_props); @@ -448,11 +481,13 @@ static void select_physical_device(PGRAPHState *pg) size_t vsh_attr_values_size = NV2A_VERTEXSHADER_ATTRIBUTES * 4 * sizeof(float); assert(r->device_props.limits.maxPushConstantsSize >= vsh_attr_values_size); + return true; } -static void create_logical_device(PGRAPHState *pg) +static bool create_logical_device(PGRAPHState *pg, Error **errp) { PGRAPHVkState *r = pg->vk_renderer_state; + VkResult result; QueueFamilyIndices indices = pgraph_vk_find_queue_families(r->physical_device); @@ -468,7 +503,8 @@ static void create_logical_device(PGRAPHState *pg) fprintf(stderr, "Enabled device extensions:\n"); for (int i = 0; i < enabled_extension_names->len; i++) { - fprintf(stderr, "- %s\n", g_array_index(enabled_extension_names, char *, i)); + fprintf(stderr, "- %s\n", + g_array_index(enabled_extension_names, char *, i)); } float queuePriority = 1.0f; @@ -501,12 +537,18 @@ static void create_logical_device(PGRAPHState *pg) bool all_features_available = true; for (int i = 0; i < ARRAY_SIZE(required_features); i++) { if (required_features[i].available != VK_TRUE) { - fprintf(stderr, "Error: Device does not support required feature %s\n", required_features[i].name); + fprintf(stderr, + "Error: Device does not support required feature %s\n", + required_features[i].name); all_features_available = false; } *required_features[i].enabled = VK_TRUE; } - assert(all_features_available); + + if (!all_features_available) { + error_setg(errp, "Device does not support required features"); + return false; + } void *next_struct = NULL; @@ -548,10 +590,15 @@ static void create_logical_device(PGRAPHState *pg) device_create_info.ppEnabledLayerNames = validation_layers; } - VK_CHECK(vkCreateDevice(r->physical_device, &device_create_info, NULL, - &r->device)); + result = vkCreateDevice(r->physical_device, &device_create_info, NULL, + &r->device); + if (result != VK_SUCCESS) { + error_setg(errp, "Failed to create logical device"); + return false; + } vkGetDeviceQueue(r->device, indices.queue_family, 0, &r->queue); + return true; } uint32_t pgraph_vk_get_memory_type(PGRAPHState *pg, uint32_t type_bits, @@ -570,9 +617,10 @@ uint32_t pgraph_vk_get_memory_type(PGRAPHState *pg, uint32_t type_bits, return 0xFFFFFFFF; // Unable to find memoryType } -static void init_allocator(PGRAPHState *pg) +static bool init_allocator(PGRAPHState *pg, Error **errp) { PGRAPHVkState *r = pg->vk_renderer_state; + VkResult result; VmaVulkanFunctions vulkanFunctions = { /// Required when using VMA_DYNAMIC_VULKAN_FUNCTIONS. @@ -631,32 +679,49 @@ static void init_allocator(PGRAPHState *pg) .pVulkanFunctions = &vulkanFunctions, }; - VK_CHECK(vmaCreateAllocator(&create_info, &r->allocator)); + result = vmaCreateAllocator(&create_info, &r->allocator); + if (result != VK_SUCCESS) { + error_setg(errp, "vmaCreateAllocator failed"); + return false; + } + + return true; } -static void finalize_allocator(PGRAPHState *pg) +void pgraph_vk_init_instance(PGRAPHState *pg, Error **errp) { - PGRAPHVkState *r = pg->vk_renderer_state; + if (create_instance(pg, errp) && + select_physical_device(pg, errp) && + create_logical_device(pg, errp) && + init_allocator(pg, errp)) { + return; + } - vmaDestroyAllocator(r->allocator); -} - -void pgraph_vk_init_instance(PGRAPHState *pg) -{ - create_instance(pg); - select_physical_device(pg); - create_logical_device(pg); - init_allocator(pg); + if (*errp) { + error_prepend(errp, "Failed to initialize Vulkan renderer: "); + } + pgraph_vk_finalize_instance(pg); } void pgraph_vk_finalize_instance(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; - finalize_allocator(pg); - vkDestroyDevice(r->device, NULL); - r->device = VK_NULL_HANDLE; + if (r->allocator != VK_NULL_HANDLE) { + vmaDestroyAllocator(r->allocator); + r->allocator = VK_NULL_HANDLE; + } - vkDestroyInstance(r->instance, NULL); - r->instance = VK_NULL_HANDLE; + if (r->device != VK_NULL_HANDLE) { + vkDestroyDevice(r->device, NULL); + r->device = VK_NULL_HANDLE; + } + + if (r->instance != VK_NULL_HANDLE) { + vkDestroyInstance(r->instance, NULL); + r->instance = VK_NULL_HANDLE; + } + + volkFinalize(); + destroy_window(r); } diff --git a/hw/xbox/nv2a/pgraph/vk/renderer.c b/hw/xbox/nv2a/pgraph/vk/renderer.c index 0cf9ee41ea..bc8adb62a1 100644 --- a/hw/xbox/nv2a/pgraph/vk/renderer.c +++ b/hw/xbox/nv2a/pgraph/vk/renderer.c @@ -33,7 +33,7 @@ static void early_context_init(void) #endif } -static void pgraph_vk_init(NV2AState *d) +static void pgraph_vk_init(NV2AState *d, Error **errp) { PGRAPHState *pg = &d->pgraph; @@ -45,7 +45,11 @@ static void pgraph_vk_init(NV2AState *d) pgraph_vk_debug_init(); - pgraph_vk_init_instance(pg); + 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); diff --git a/hw/xbox/nv2a/pgraph/vk/renderer.h b/hw/xbox/nv2a/pgraph/vk/renderer.h index 1897948e11..787f3df8a5 100644 --- a/hw/xbox/nv2a/pgraph/vk/renderer.h +++ b/hw/xbox/nv2a/pgraph/vk/renderer.h @@ -380,7 +380,7 @@ void pgraph_vk_check_memory_budget(PGRAPHState *pg); void pgraph_vk_debug_init(void); // instance.c -void pgraph_vk_init_instance(PGRAPHState *pg); +void pgraph_vk_init_instance(PGRAPHState *pg, Error **errp); void pgraph_vk_finalize_instance(PGRAPHState *pg); QueueFamilyIndices pgraph_vk_find_queue_families(VkPhysicalDevice device); uint32_t pgraph_vk_get_memory_type(PGRAPHState *pg, uint32_t type_bits,