diff --git a/core/os/os.h b/core/os/os.h index 0fe20c1d39c..e0ec320e32b 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -109,6 +109,7 @@ protected: HasServerFeatureCallback has_server_feature_callback = nullptr; bool _separate_thread_render = false; + bool _silent_crash_handler = false; // Functions used by Main to initialize/deinitialize the OS. void add_logger(Logger *p_logger); @@ -262,6 +263,9 @@ public: void set_stdout_enabled(bool p_enabled); void set_stderr_enabled(bool p_enabled); + virtual void set_crash_handler_silent() { _silent_crash_handler = true; } + virtual bool is_crash_handler_silent() { return _silent_crash_handler; } + virtual void disable_crash_handler() {} virtual bool is_disable_crash_handler() const { return false; } virtual void initialize_debugging() {} @@ -358,6 +362,10 @@ public: // This is invoked by the GDExtensionManager after loading GDExtensions specified by the project. virtual void load_platform_gdextensions() const {} + // Windows only. Tests OpenGL context and Rendering Device simultaneous creation. This function is expected to crash on some NVIDIA drivers. + virtual bool _test_create_rendering_device_and_gl() const { return true; } + virtual bool _test_create_rendering_device() const { return true; } + OS(); virtual ~OS(); }; diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp index 6f2e9da3e0c..09819215c8f 100644 --- a/editor/project_manager/project_dialog.cpp +++ b/editor/project_manager/project_dialog.cpp @@ -973,7 +973,7 @@ ProjectDialog::ProjectDialog() { default_renderer_type = EditorSettings::get_singleton()->get_setting("project_manager/default_renderer"); } - rendering_device_supported = DisplayServer::can_create_rendering_device(); + rendering_device_supported = DisplayServer::is_rendering_device_supported(); if (!rendering_device_supported) { default_renderer_type = "gl_compatibility"; diff --git a/main/main.cpp b/main/main.cpp index 6fbe3867622..10064b35037 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -979,6 +979,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph String project_path = "."; bool upwards = false; String debug_uri = ""; +#if defined(TOOLS_ENABLED) && defined(WINDOWS_ENABLED) + bool test_rd_creation = false; + bool test_rd_support = false; +#endif bool skip_breakpoints = false; String main_pack; bool quiet_stdout = false; @@ -1666,6 +1670,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph debug_canvas_item_redraw = true; } else if (arg == "--debug-stringnames") { StringName::set_debug_stringnames(true); +#endif +#if defined(TOOLS_ENABLED) && defined(WINDOWS_ENABLED) + } else if (arg == "--test-rd-support") { + test_rd_support = true; + } else if (arg == "--test-rd-creation") { + test_rd_creation = true; #endif } else if (arg == "--remote-debug") { if (N) { @@ -1870,6 +1880,30 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph #endif } +#if defined(TOOLS_ENABLED) && defined(WINDOWS_ENABLED) + if (test_rd_support) { + // Test Rendering Device creation and exit. + + OS::get_singleton()->set_crash_handler_silent(); + if (OS::get_singleton()->_test_create_rendering_device()) { + exit_err = ERR_HELP; + } else { + exit_err = ERR_UNAVAILABLE; + } + goto error; + } else if (test_rd_creation) { + // Test OpenGL context and Rendering Device simultaneous creation and exit. + + OS::get_singleton()->set_crash_handler_silent(); + if (OS::get_singleton()->_test_create_rendering_device_and_gl()) { + exit_err = ERR_HELP; + } else { + exit_err = ERR_UNAVAILABLE; + } + goto error; + } +#endif + #ifdef TOOLS_ENABLED if (editor) { Engine::get_singleton()->set_editor_hint(true); diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index 446fe5c7a1e..a56a68768ba 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -57,6 +57,10 @@ static void handle_crash(int sig) { abort(); } + if (OS::get_singleton()->is_crash_handler_silent()) { + std::_Exit(0); + } + void *bt_buffer[256]; size_t size = backtrace(bt_buffer, 256); String _execpath = OS::get_singleton()->get_executable_path(); diff --git a/platform/macos/crash_handler_macos.mm b/platform/macos/crash_handler_macos.mm index bc83c077b11..49dc863a1a2 100644 --- a/platform/macos/crash_handler_macos.mm +++ b/platform/macos/crash_handler_macos.mm @@ -81,6 +81,10 @@ static void handle_crash(int sig) { abort(); } + if (OS::get_singleton()->is_crash_handler_silent()) { + std::_Exit(0); + } + void *bt_buffer[256]; size_t size = backtrace(bt_buffer, 256); String _execpath = OS::get_singleton()->get_executable_path(); diff --git a/platform/windows/crash_handler_windows_seh.cpp b/platform/windows/crash_handler_windows_seh.cpp index a6015092e84..b760440a389 100644 --- a/platform/windows/crash_handler_windows_seh.cpp +++ b/platform/windows/crash_handler_windows_seh.cpp @@ -41,6 +41,7 @@ // Backtrace code based on: https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app #include +#include #include #include #include @@ -127,6 +128,10 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { return EXCEPTION_CONTINUE_SEARCH; } + if (OS::get_singleton()->is_crash_handler_silent()) { + std::_Exit(0); + } + String msg; const ProjectSettings *proj_settings = ProjectSettings::get_singleton(); if (proj_settings) { diff --git a/platform/windows/crash_handler_windows_signal.cpp b/platform/windows/crash_handler_windows_signal.cpp index ebc89a8181f..e26cd7809be 100644 --- a/platform/windows/crash_handler_windows_signal.cpp +++ b/platform/windows/crash_handler_windows_signal.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -133,6 +134,10 @@ extern void CrashHandlerException(int signal) { return; } + if (OS::get_singleton()->is_crash_handler_silent()) { + std::_Exit(0); + } + String msg; const ProjectSettings *proj_settings = ProjectSettings::get_singleton(); if (proj_settings) { diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 7846cc404a3..073080ac8fb 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -62,6 +62,24 @@ #include #include +#if defined(RD_ENABLED) +#include "servers/rendering/rendering_device.h" +#endif + +#if defined(GLES3_ENABLED) +#include "gl_manager_windows_native.h" +#endif + +#if defined(VULKAN_ENABLED) +#include "rendering_context_driver_vulkan_windows.h" +#endif +#if defined(D3D12_ENABLED) +#include "drivers/d3d12/rendering_context_driver_d3d12.h" +#endif +#if defined(GLES3_ENABLED) +#include "drivers/gles3/rasterizer_gles3.h" +#endif + #ifdef DEBUG_ENABLED #pragma pack(push, before_imagehlp, 8) #include @@ -2346,6 +2364,99 @@ void OS_Windows::add_frame_delay(bool p_can_draw) { } } +bool OS_Windows::_test_create_rendering_device() const { + // Tests Rendering Device creation. + + bool ok = false; +#if defined(RD_ENABLED) + Error err; + RenderingContextDriver *rcd = nullptr; + +#if defined(VULKAN_ENABLED) + rcd = memnew(RenderingContextDriverVulkan); +#endif +#ifdef D3D12_ENABLED + if (rcd == nullptr) { + rcd = memnew(RenderingContextDriverD3D12); + } +#endif + if (rcd != nullptr) { + err = rcd->initialize(); + if (err == OK) { + RenderingDevice *rd = memnew(RenderingDevice); + err = rd->initialize(rcd); + memdelete(rd); + rd = nullptr; + if (err == OK) { + ok = true; + } + } + memdelete(rcd); + rcd = nullptr; + } +#endif + + return ok; +} + +bool OS_Windows::_test_create_rendering_device_and_gl() const { + // Tests OpenGL context and Rendering Device simultaneous creation. This function is expected to crash on some NVIDIA drivers. + + WNDCLASSEXW wc_probe; + memset(&wc_probe, 0, sizeof(WNDCLASSEXW)); + wc_probe.cbSize = sizeof(WNDCLASSEXW); + wc_probe.style = CS_OWNDC | CS_DBLCLKS; + wc_probe.lpfnWndProc = (WNDPROC)::DefWindowProcW; + wc_probe.cbClsExtra = 0; + wc_probe.cbWndExtra = 0; + wc_probe.hInstance = GetModuleHandle(nullptr); + wc_probe.hIcon = LoadIcon(nullptr, IDI_WINLOGO); + wc_probe.hCursor = nullptr; + wc_probe.hbrBackground = nullptr; + wc_probe.lpszMenuName = nullptr; + wc_probe.lpszClassName = L"Engine probe window"; + + if (!RegisterClassExW(&wc_probe)) { + return false; + } + + HWND hWnd = CreateWindowExW(WS_EX_WINDOWEDGE, L"Engine probe window", L"", WS_OVERLAPPEDWINDOW, 0, 0, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + if (!hWnd) { + UnregisterClassW(L"Engine probe window", GetModuleHandle(nullptr)); + return false; + } + + bool ok = true; +#ifdef GLES3_ENABLED + GLManagerNative_Windows *test_gl_manager_native = memnew(GLManagerNative_Windows); + if (test_gl_manager_native->window_create(DisplayServer::MAIN_WINDOW_ID, hWnd, GetModuleHandle(nullptr), 800, 600) == OK) { + RasterizerGLES3::make_current(true); + } else { + ok = false; + } +#endif + + MSG msg = {}; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + if (ok) { + ok = _test_create_rendering_device(); + } + +#ifdef GLES3_ENABLED + if (test_gl_manager_native) { + memdelete(test_gl_manager_native); + } +#endif + + DestroyWindow(hWnd); + UnregisterClassW(L"Engine probe window", GetModuleHandle(nullptr)); + return ok; +} + OS_Windows::OS_Windows(HINSTANCE _hInstance) { hInstance = _hInstance; diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 9f5eef49624..c46d86a650e 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -252,6 +252,9 @@ public: void set_main_window(HWND p_main_window) { main_window = p_main_window; } + virtual bool _test_create_rendering_device_and_gl() const override; + virtual bool _test_create_rendering_device() const override; + HINSTANCE get_hinstance() { return hInstance; } OS_Windows(HINSTANCE _hInstance); ~OS_Windows(); diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 6dd9044617e..59c99d71049 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -1316,8 +1316,87 @@ void DisplayServer::_input_set_custom_mouse_cursor_func(const Ref &p_i singleton->cursor_set_custom_image(p_image, (CursorShape)p_shape, p_hotspot); } +bool DisplayServer::is_rendering_device_supported() { +#if defined(RD_ENABLED) + RenderingDevice *device = RenderingDevice::get_singleton(); + if (device) { + return true; + } + + if (supported_rendering_device == RenderingDeviceCreationStatus::SUCCESS) { + return true; + } else if (supported_rendering_device == RenderingDeviceCreationStatus::FAILURE) { + return false; + } + + Error err; + +#ifdef WINDOWS_ENABLED + // On some NVIDIA drivers combining OpenGL and RenderingDevice can result in crash, offload the check to the subprocess. + List arguments; + arguments.push_back("--test-rd-support"); + + String pipe; + int exitcode = 0; + err = OS::get_singleton()->execute(OS::get_singleton()->get_executable_path(), arguments, &pipe, &exitcode); + if (err == OK && exitcode == 0) { + supported_rendering_device = RenderingDeviceCreationStatus::SUCCESS; + return true; + } else { + supported_rendering_device = RenderingDeviceCreationStatus::FAILURE; + return false; + } +#endif + + RenderingContextDriver *rcd = nullptr; + +#if defined(VULKAN_ENABLED) + rcd = memnew(RenderingContextDriverVulkan); +#endif +#ifdef D3D12_ENABLED + if (rcd == nullptr) { + rcd = memnew(RenderingContextDriverD3D12); + } +#endif +#ifdef METAL_ENABLED + if (rcd == nullptr) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + // Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer". + rcd = memnew(RenderingContextDriverMetal); +#pragma clang diagnostic pop + } +#endif + + if (rcd != nullptr) { + err = rcd->initialize(); + if (err == OK) { + RenderingDevice *rd = memnew(RenderingDevice); + err = rd->initialize(rcd); + memdelete(rd); + rd = nullptr; + if (err == OK) { + // Creating a RenderingDevice is quite slow. + // Cache the result for future usage, so that it's much faster on subsequent calls. + supported_rendering_device = RenderingDeviceCreationStatus::SUCCESS; + memdelete(rcd); + rcd = nullptr; + return true; + } else { + supported_rendering_device = RenderingDeviceCreationStatus::FAILURE; + } + } + + memdelete(rcd); + rcd = nullptr; + } + +#endif // RD_ENABLED + return false; +} + bool DisplayServer::can_create_rendering_device() { - if (get_singleton()->get_name() == "headless") { + if (get_singleton() && get_singleton()->get_name() == "headless") { return false; } @@ -1334,6 +1413,24 @@ bool DisplayServer::can_create_rendering_device() { } Error err; + +#ifdef WINDOWS_ENABLED + // On some NVIDIA drivers combining OpenGL and RenderingDevice can result in crash, offload the check to the subprocess. + List arguments; + arguments.push_back("--test-rd-creation"); + + String pipe; + int exitcode = 0; + err = OS::get_singleton()->execute(OS::get_singleton()->get_executable_path(), arguments, &pipe, &exitcode); + if (err == OK && exitcode == 0) { + created_rendering_device = RenderingDeviceCreationStatus::SUCCESS; + return true; + } else { + created_rendering_device = RenderingDeviceCreationStatus::FAILURE; + return false; + } +#endif + RenderingContextDriver *rcd = nullptr; #if defined(VULKAN_ENABLED) diff --git a/servers/display_server.h b/servers/display_server.h index 79cf9ffc44b..80bba6897ff 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -652,9 +652,11 @@ public: // Used to cache the result of `can_create_rendering_device()` when RenderingDevice isn't currently being used. // This is done as creating a RenderingDevice is quite slow. static inline RenderingDeviceCreationStatus created_rendering_device = RenderingDeviceCreationStatus::UNKNOWN; - static bool can_create_rendering_device(); + static inline RenderingDeviceCreationStatus supported_rendering_device = RenderingDeviceCreationStatus::UNKNOWN; + static bool is_rendering_device_supported(); + DisplayServer(); ~DisplayServer(); };