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();
}
static void pgraph_gl_init(NV2AState *d)
static void pgraph_gl_init(NV2AState *d, Error **errp)
{
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;
pg->null_renderer_state = NULL;

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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,