Merge pull request #22171 from hrydgard/vulkan-surface-init-errors

Vulkan: Report surface init failures instead of asserting later
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-08-30 10:25:29 +02:00
commit 480f42c2b8
6 files changed
+39 -11

No files matched your search

+11
View File
@@ -1316,10 +1316,14 @@ VkResult VulkanContext::ReinitSurface() {
}
if (retval != VK_SUCCESS) {
init_error_ = StringFromFormat("Failed to create a Vulkan surface for window system %s: %s", WindowSystemToString(winsys_), VulkanResultToString(retval));
ERROR_LOG(Log::G3D, "%s", init_error_.c_str());
return retval;
}
if (!ChooseQueue()) {
init_error_ = "Failed to find a Vulkan queue and surface format that can present to the window";
ERROR_LOG(Log::G3D, "%s", init_error_.c_str());
return VK_ERROR_INITIALIZATION_FAILED;
}
@@ -1337,6 +1341,13 @@ VkResult VulkanContext::ReinitSurface() {
res = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_devices_[physical_device_], surface_, &presentModeCount, availablePresentModes_.data());
_dbg_assert_(res == VK_SUCCESS);
}
if (res != VK_SUCCESS || availablePresentModes_.empty()) {
// Should be impossible - the spec guarantees at least FIFO for any surface we could create.
availablePresentModes_.clear();
init_error_ = StringFromFormat("Failed to enumerate Vulkan present modes: %s (count %d)", VulkanResultToString(res), (int)presentModeCount);
ERROR_LOG(Log::G3D, "%s", init_error_.c_str());
return res != VK_SUCCESS ? res : VK_ERROR_INITIALIZATION_FAILED;
}
return VK_SUCCESS;
}
+12 -1
View File
@@ -61,6 +61,7 @@
#include "Common/GPU/Vulkan/VulkanRenderManager.h"
#include "Common/GPU/Vulkan/VulkanGraphicsContext.h"
#include "Common/Data/Text/Parsers.h"
#include "Common/StringUtils.h"
#include "GPU/Vulkan/VulkanUtil.h"
#ifdef _DEBUG
@@ -119,7 +120,17 @@ bool VulkanGraphicsContext::InitAPI(void *wnd, std::string *deviceName, std::str
}
bool VulkanGraphicsContext::InitSurface(WindowSystem winsys, void *data1, void *data2, std::string *errorMessage) {
vulkan_->InitSurface(winsys, data1, data2);
// Don't proceed on failure - without a surface there's no present mode, no swapchain and no queue,
// so everything below would just fail in more confusing ways further down (it used to assert deep
// inside the thin3d context constructor). Let the caller fall back to another backend instead.
VkResult res = vulkan_->InitSurface(winsys, data1, data2);
if (res != VK_SUCCESS) {
*errorMessage = vulkan_->InitError();
if (errorMessage->empty()) {
*errorMessage = StringFromFormat("Failed to initialize Vulkan surface: %s", VulkanResultToString(res));
}
return false;
}
bool useMultiThreading = g_Config.bRenderMultiThreading;
if (g_Config.iInflightFrames == 1) {
+4 -5
View File
@@ -126,14 +126,13 @@ bool RunMainLoop(GraphicsContext *graphicsContext, Application *application, std
}
// Call InitAPI and ShutdownAPI outside this!
bool MainThreadFunc(GraphicsContext *graphicsContext, Application *application, const WindowDesc &windowDesc, std::function<bool(GraphicsContext *)> frame) {
bool MainThreadFunc(GraphicsContext *graphicsContext, Application *application, const WindowDesc &windowDesc, std::function<bool(GraphicsContext *)> frame, std::string *errorMessage) {
// This is now the render thread, and will spawn the emu thread below.
std::string error_string;
bool success = graphicsContext->InitSurface(windowDesc.winsys, windowDesc.data1, windowDesc.data2, &error_string);
if (!success) {
if (!graphicsContext->InitSurface(windowDesc.winsys, windowDesc.data1, windowDesc.data2, errorMessage)) {
ERROR_LOG(Log::G3D, "MainThreadFunc: InitSurface failed: %s", errorMessage->c_str());
delete application;
return false;
}
std::string errorMessage;
if (graphicsContext->NeedsSeparateEmuThread()) {
SetCurrentThreadName("RenderThread");
+4 -1
View File
@@ -18,6 +18,7 @@
#pragma once
#include <functional>
#include <string>
#include <thread>
#include "Common/System/Application.h"
@@ -34,7 +35,9 @@ struct WindowDesc;
// This should be used by platforms that launch a separate thread and doesn't
// need to run a polling loop in it.
// NOTE: Does take ownership over Application (which is just a wrapper for NativeInitGraphics/NativeShutdownGraphics/NativeFrame).
bool MainThreadFunc(GraphicsContext * graphicsContext, Application *application, const WindowDesc &windowDesc, std::function<bool(GraphicsContext *)> frame);
// On failure (which currently only means the graphics surface couldn't be initialized), errorMessage
// gets the reason - pass it on to the user, it's the only place it's available.
bool MainThreadFunc(GraphicsContext * graphicsContext, Application *application, const WindowDesc &windowDesc, std::function<bool(GraphicsContext *)> frame, std::string *errorMessage);
// If you're not using MainThreadFunc, you can at least use these to manage a spinning EmuThread (that calls NativeFrame),
// whether your graphics context requires multithreading or not. Then use RunMainLoop to implement your main loop for
+2 -2
View File
@@ -1251,8 +1251,8 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
[](GraphicsContext *graphicsContext) {
NativeFrame(graphicsContext);
return GetUIState() != UISTATE_EXIT;
})) {
HandleGraphicsFailure("Failed to initialize main thread function.");
}, &errorMessage)) {
HandleGraphicsFailure(errorMessage);
return;
}
graphicsContext->ShutdownAPI();
+6 -2
View File
@@ -884,10 +884,14 @@ int main(int argc, const char* argv[]) {
}
int retval = 0;
MainThreadFunc(graphicsContext, new HeadlessApplication(), windowDesc, [&retval, &coreParameter, &testOptions, &testFilenames](GraphicsContext *graphicsContext) {
if (!MainThreadFunc(graphicsContext, new HeadlessApplication(), windowDesc, [&retval, &coreParameter, &testOptions, &testFilenames](GraphicsContext *graphicsContext) {
retval = RunTests(graphicsContext, coreParameter, testOptions, testFilenames);
return false;
});
}, &errorMessage)) {
// No fallbacks in headless - if we can't run it, we can't. Let's not get confusing.
fprintf(stderr, "Failed to initialize graphics surface: %s\n", errorMessage.c_str());
retval = 1;
}
graphicsContext->ShutdownAPI();