nv2a: Handle renderer init errors more gracefully

This commit is contained in:
Matt Borgerson 2024-07-26 17:21:01 -07:00 committed by mborgerson
parent c1bbe39f22
commit 25afb8603d
7 changed files with 173 additions and 64 deletions

View File

@ -33,7 +33,7 @@ static void early_context_init(void)
g_nv2a_context_display = glo_context_create(); 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; PGRAPHState *pg = &d->pgraph;

View File

@ -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; PGRAPHState *pg = &d->pgraph;
pg->null_renderer_state = NULL; pg->null_renderer_state = NULL;

View File

@ -20,6 +20,7 @@
*/ */
#include "../nv2a_int.h" #include "../nv2a_int.h"
#include "ui/xemu-notifications.h"
#include "ui/xemu-settings.h" #include "ui/xemu-settings.h"
#include "util.h" #include "util.h"
#include "swizzle.h" #include "swizzle.h"
@ -238,8 +239,6 @@ void pgraph_init(NV2AState *d)
} }
pgraph_clear_dirty_reg_map(pg); pgraph_clear_dirty_reg_map(pg);
pg->renderer = renderers[g_config.display.renderer];
} }
void pgraph_clear_dirty_reg_map(PGRAPHState *pg) 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)); 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) static CONFIG_DISPLAY_RENDERER get_default_renderer(void)
{ {
#ifdef CONFIG_OPENGL #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) void pgraph_destroy(PGRAPHState *pg)
{ {
NV2AState *d = container_of(pg, NV2AState, pgraph); NV2AState *d = container_of(pg, NV2AState, pgraph);
@ -2919,12 +2964,7 @@ void pgraph_process_pending(NV2AState *d)
} }
} }
// FIXME: Handle missing renderer, init errors init_renderer(pg);
pg->renderer = renderers[g_config.display.renderer];
if (pg->renderer->ops.init) {
pg->renderer->ops.init(d);
}
qemu_mutex_unlock(&d->pgraph.renderer_lock); qemu_mutex_unlock(&d->pgraph.renderer_lock);
qemu_mutex_unlock(&d->pgraph.lock); qemu_mutex_unlock(&d->pgraph.lock);

View File

@ -100,7 +100,7 @@ typedef struct PGRAPHRenderer {
const char *name; const char *name;
struct { struct {
void (*early_context_init)(void); void (*early_context_init)(void);
void (*init)(NV2AState *d); void (*init)(NV2AState *d, Error **errp);
void (*finalize)(NV2AState *d); void (*finalize)(NV2AState *d);
void (*clear_report_value)(NV2AState *d); void (*clear_report_value)(NV2AState *d);
void (*clear_surface)(NV2AState *d, uint32_t parameter); void (*clear_surface)(NV2AState *d, uint32_t parameter);

View File

@ -98,19 +98,23 @@ static bool check_validation_layer_support(void)
return true; 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, "SDL Offscreen Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
640, 480, SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN); 640, 480, SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN);
if (window == NULL) { if (r->window == NULL) {
fprintf(stderr, "%s: Failed to create window\n", __func__); error_setg(errp, "SDL_CreateWindow failed: %s", SDL_GetError());
SDL_Quit();
exit(1);
} }
}
return window; static void destroy_window(PGRAPHVkState *r)
{
if (r->window) {
SDL_DestroyWindow(r->window);
r->window = NULL;
}
} }
static VkExtensionPropertiesArray * static VkExtensionPropertiesArray *
@ -199,13 +203,22 @@ add_optional_instance_extension_names(PGRAPHState *pg,
VK_EXT_DEBUG_UTILS_EXTENSION_NAME); 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; 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 = { VkApplicationInfo app_info = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
@ -234,14 +247,19 @@ static void create_instance(PGRAPHState *pg)
all_required_extensions_available = false; 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, add_optional_instance_extension_names(pg, available_extensions,
enabled_extension_names); enabled_extension_names);
fprintf(stderr, "Enabled instance extensions:\n"); fprintf(stderr, "Enabled instance extensions:\n");
for (int i = 0; i < enabled_extension_names->len; i++) { 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 = { VkInstanceCreateInfo create_info = {
@ -270,7 +288,8 @@ static void create_instance(PGRAPHState *pg)
if (enable_validation) { if (enable_validation) {
if (check_validation_layer_support()) { 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.enabledLayerCount = ARRAY_SIZE(validation_layers);
create_info.ppEnabledLayerNames = validation_layers; create_info.ppEnabledLayerNames = validation_layers;
if (r->debug_utils_extension_enabled) { 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); volkLoadInstance(r->instance);
return true;
error:
volkFinalize();
destroy_window(r);
return false;
} }
static bool is_queue_family_indicies_complete(QueueFamilyIndices indices) static bool is_queue_family_indicies_complete(QueueFamilyIndices indices)
@ -399,15 +428,18 @@ static bool is_device_compatible(VkPhysicalDevice device)
// FIXME: Check vram // 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; PGRAPHVkState *r = pg->vk_renderer_state;
VkResult result;
uint32_t num_physical_devices = 0; uint32_t num_physical_devices = 0;
vkEnumeratePhysicalDevices(r->instance, &num_physical_devices, NULL); result =
if (num_physical_devices == 0) { vkEnumeratePhysicalDevices(r->instance, &num_physical_devices, NULL);
assert(!"failed to find GPUs with Vulkan support"); if (result != VK_SUCCESS || num_physical_devices == 0) {
error_setg(errp, "Failed to find GPUs with Vulkan support");
return false;
} }
g_autofree VkPhysicalDevice *devices = g_autofree VkPhysicalDevice *devices =
@ -430,7 +462,8 @@ static void select_physical_device(PGRAPHState *pg)
} }
} }
if (r->physical_device == VK_NULL_HANDLE) { 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); vkGetPhysicalDeviceProperties(r->physical_device, &r->device_props);
@ -448,11 +481,13 @@ static void select_physical_device(PGRAPHState *pg)
size_t vsh_attr_values_size = size_t vsh_attr_values_size =
NV2A_VERTEXSHADER_ATTRIBUTES * 4 * sizeof(float); NV2A_VERTEXSHADER_ATTRIBUTES * 4 * sizeof(float);
assert(r->device_props.limits.maxPushConstantsSize >= vsh_attr_values_size); 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; PGRAPHVkState *r = pg->vk_renderer_state;
VkResult result;
QueueFamilyIndices indices = QueueFamilyIndices indices =
pgraph_vk_find_queue_families(r->physical_device); 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"); fprintf(stderr, "Enabled device extensions:\n");
for (int i = 0; i < enabled_extension_names->len; i++) { 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; float queuePriority = 1.0f;
@ -501,12 +537,18 @@ static void create_logical_device(PGRAPHState *pg)
bool all_features_available = true; bool all_features_available = true;
for (int i = 0; i < ARRAY_SIZE(required_features); i++) { for (int i = 0; i < ARRAY_SIZE(required_features); i++) {
if (required_features[i].available != VK_TRUE) { 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; all_features_available = false;
} }
*required_features[i].enabled = VK_TRUE; *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; void *next_struct = NULL;
@ -548,10 +590,15 @@ static void create_logical_device(PGRAPHState *pg)
device_create_info.ppEnabledLayerNames = validation_layers; device_create_info.ppEnabledLayerNames = validation_layers;
} }
VK_CHECK(vkCreateDevice(r->physical_device, &device_create_info, NULL, result = vkCreateDevice(r->physical_device, &device_create_info, NULL,
&r->device)); &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); vkGetDeviceQueue(r->device, indices.queue_family, 0, &r->queue);
return true;
} }
uint32_t pgraph_vk_get_memory_type(PGRAPHState *pg, uint32_t type_bits, 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 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; PGRAPHVkState *r = pg->vk_renderer_state;
VkResult result;
VmaVulkanFunctions vulkanFunctions = { VmaVulkanFunctions vulkanFunctions = {
/// Required when using VMA_DYNAMIC_VULKAN_FUNCTIONS. /// Required when using VMA_DYNAMIC_VULKAN_FUNCTIONS.
@ -631,32 +679,49 @@ static void init_allocator(PGRAPHState *pg)
.pVulkanFunctions = &vulkanFunctions, .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); if (*errp) {
} error_prepend(errp, "Failed to initialize Vulkan renderer: ");
}
void pgraph_vk_init_instance(PGRAPHState *pg) pgraph_vk_finalize_instance(pg);
{
create_instance(pg);
select_physical_device(pg);
create_logical_device(pg);
init_allocator(pg);
} }
void pgraph_vk_finalize_instance(PGRAPHState *pg) void pgraph_vk_finalize_instance(PGRAPHState *pg)
{ {
PGRAPHVkState *r = pg->vk_renderer_state; PGRAPHVkState *r = pg->vk_renderer_state;
finalize_allocator(pg); if (r->allocator != VK_NULL_HANDLE) {
vkDestroyDevice(r->device, NULL); vmaDestroyAllocator(r->allocator);
r->device = VK_NULL_HANDLE; r->allocator = VK_NULL_HANDLE;
}
vkDestroyInstance(r->instance, NULL); if (r->device != VK_NULL_HANDLE) {
r->instance = 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);
} }

View File

@ -33,7 +33,7 @@ static void early_context_init(void)
#endif #endif
} }
static void pgraph_vk_init(NV2AState *d) static void pgraph_vk_init(NV2AState *d, Error **errp)
{ {
PGRAPHState *pg = &d->pgraph; PGRAPHState *pg = &d->pgraph;
@ -45,7 +45,11 @@ static void pgraph_vk_init(NV2AState *d)
pgraph_vk_debug_init(); 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_command_buffers(pg);
pgraph_vk_init_buffers(d); pgraph_vk_init_buffers(d);
pgraph_vk_init_surfaces(pg); pgraph_vk_init_surfaces(pg);

View File

@ -380,7 +380,7 @@ void pgraph_vk_check_memory_budget(PGRAPHState *pg);
void pgraph_vk_debug_init(void); void pgraph_vk_debug_init(void);
// instance.c // 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); void pgraph_vk_finalize_instance(PGRAPHState *pg);
QueueFamilyIndices pgraph_vk_find_queue_families(VkPhysicalDevice device); QueueFamilyIndices pgraph_vk_find_queue_families(VkPhysicalDevice device);
uint32_t pgraph_vk_get_memory_type(PGRAPHState *pg, uint32_t type_bits, uint32_t pgraph_vk_get_memory_type(PGRAPHState *pg, uint32_t type_bits,