[fix] prevent Windows startup crashes from OpenGL failures, fixes #97

This commit is contained in:
dijunkun
2026-09-07 04:20:54 +08:00
parent 19800ef350
commit 2000654792
6 changed files with 218 additions and 0 deletions
+5
View File
@@ -15,6 +15,7 @@
#ifdef _WIN32
#include <cstdio>
#include "platform/windows/gui/slint_backend.h"
#include "service_host.h"
#endif
@@ -175,6 +176,10 @@ int HandleServiceCliCommand(const std::string& command) {
int main(int argc, char* argv[]) {
#ifdef _WIN32
if (argc == 2 &&
std::strcmp(argv[1], crossdesk::kSlintRendererProbeArgument) == 0) {
return crossdesk::RunSlintRendererProbe();
}
if (argc > 1 && IsServiceCliCommand(argv[1])) {
return HandleServiceCliCommand(argv[1]);
}
@@ -37,6 +37,7 @@
#if _WIN32
#include <windows.h>
#include "platform/windows/gui/slint_backend.h"
#include "platform/windows/gui/tray/win_tray.h"
#elif defined(__APPLE__)
#include "platform/macos/gui/tray/mac_tray.h"
@@ -992,6 +993,15 @@ int GuiApplication::Run() {
if (!InitializeSDL()) {
return -1;
}
#if _WIN32
const auto backend = ConfigureWindowsSlintBackend();
if (!backend.success) {
LOG_ERROR("{}", backend.diagnostic);
SDL_Quit();
return -1;
}
LOG_INFO("Slint backend: {}; {}", backend.backend, backend.diagnostic);
#endif
InitializeModules();
InitializeUi();
@@ -0,0 +1,109 @@
#include "platform/windows/gui/slint_backend.h"
#include <windows.h>
#include <cstdlib>
#include <iomanip>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
namespace crossdesk {
namespace {
struct HandleCloser {
void operator()(HANDLE handle) const noexcept {
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
}
}
};
using ScopedHandle = std::unique_ptr<void, HandleCloser>;
std::string ErrorCode(DWORD code) {
std::ostringstream text;
text << "0x" << std::hex << std::uppercase << code;
return text.str();
}
std::string ProbeSlintRenderer(DWORD timeout_ms) {
std::wstring executable(32768, L'\0');
const DWORD length = GetModuleFileNameW(
nullptr, executable.data(), static_cast<DWORD>(executable.size()));
if (length == 0 || length >= executable.size()) {
return "Unable to locate renderer probe executable";
}
executable.resize(length);
// A job also cleans up the probe if the application is closed while the
// graphics driver is stuck. Create it suspended so assignment cannot race.
ScopedHandle job(CreateJobObjectW(nullptr, nullptr));
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = {};
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!job ||
!SetInformationJobObject(job.get(), JobObjectExtendedLimitInformation,
&limits, sizeof(limits))) {
return "Unable to create renderer probe job: " + ErrorCode(GetLastError());
}
constexpr std::string_view argument = kSlintRendererProbeArgument;
std::wstring command = L"\"" + executable + L"\" ";
command.append(argument.begin(), argument.end());
STARTUPINFOW startup = {sizeof(startup)};
PROCESS_INFORMATION process = {};
if (!CreateProcessW(executable.c_str(), command.data(), nullptr, nullptr,
FALSE, CREATE_NO_WINDOW | CREATE_SUSPENDED, nullptr,
nullptr, &startup, &process)) {
return "Unable to start renderer probe: " + ErrorCode(GetLastError());
}
ScopedHandle process_handle(process.hProcess);
ScopedHandle thread_handle(process.hThread);
if (!AssignProcessToJobObject(job.get(), process.hProcess) ||
ResumeThread(process.hThread) == static_cast<DWORD>(-1)) {
const DWORD error = GetLastError();
TerminateProcess(process.hProcess, 1);
return "Unable to run renderer probe: " + ErrorCode(error);
}
const DWORD wait = WaitForSingleObject(process.hProcess, timeout_ms);
if (wait != WAIT_OBJECT_0) {
// Closing the job kills only our probe, including any stuck driver thread.
return wait == WAIT_TIMEOUT ? "Slint renderer probe timed out"
: "Waiting for renderer probe failed: " +
ErrorCode(GetLastError());
}
DWORD exit_code = 0;
if (!GetExitCodeProcess(process.hProcess, &exit_code)) {
return "Unable to read renderer probe result: " + ErrorCode(GetLastError());
}
return exit_code == 0
? std::string{}
: "Slint renderer probe exited with " + ErrorCode(exit_code);
}
} // namespace
SlintBackendSelection ConfigureWindowsSlintBackend(uint32_t probe_timeout_ms) {
if (const char* requested = std::getenv("SLINT_BACKEND");
requested && requested[0] != '\0') {
return {true, requested, "Using explicitly requested Slint backend"};
}
// Slint creates its own GL context. A successful SDL/WGL capability check
// cannot validate it, and a Rust panic cannot be caught by C++ exceptions.
// Exercise Slint in a separate process without touching GL in this process.
const std::string error = ProbeSlintRenderer(probe_timeout_ms);
if (error.empty()) {
return {true, "default", "Slint renderer probe rendered successfully"};
}
// _putenv_s updates the CRT and the OS environment read by slint_cpp.dll.
// This is process-local so a later launch can retry hardware rendering.
if (_putenv_s("SLINT_BACKEND", "winit-software") != 0) {
return {false, {}, "Unable to select Slint software rendering: " + error};
}
return {true, "winit-software", error};
}
} // namespace crossdesk
@@ -0,0 +1,33 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-09-07
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SLINT_BACKEND_H_
#define _SLINT_BACKEND_H_
#include <cstdint>
#include <string>
namespace crossdesk {
struct SlintBackendSelection {
bool success = true;
std::string backend;
std::string diagnostic;
};
inline constexpr char kSlintRendererProbeArgument[] = "--slint-renderer-probe";
// Call before creating any Slint components. The executable's main() must
// dispatch kSlintRendererProbeArgument before services, settings or the daemon.
SlintBackendSelection ConfigureWindowsSlintBackend(
uint32_t probe_timeout_ms = 10000);
// Internal child-process entry point; never call in the main application.
int RunSlintRendererProbe();
} // namespace crossdesk
#endif
@@ -0,0 +1,59 @@
#include <SDL3/SDL.h>
#include <windows.h>
#include <chrono>
#include <iostream>
#include "crossdesk_ui.h"
#include "platform/windows/gui/slint_backend.h"
namespace crossdesk {
int RunSlintRendererProbe() {
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
if (!SDL_Init(SDL_INIT_VIDEO)) {
return 1;
}
int result = 2;
{
// Reuse the application's single AOT entry point. Independently generated
// headers can contain colliding internal component names across TUs.
auto probe = ui::MainWindow::create();
probe->set_custom_titlebar(true);
// AfterRendering confirms that the real GL context, shader initialization
// and first draw all succeeded. GPU readback from an off-screen window can
// be black even when rendering works, so do not use its pixels as a test.
const auto notifier_error = probe->window().set_rendering_notifier(
[&](slint::RenderingState state, slint::GraphicsAPI) {
if (state == slint::RenderingState::AfterRendering) {
result = 0;
}
});
probe->window().set_position(
slint::PhysicalPosition(slint::Point<int32_t>{-32000, -32000}));
probe->show();
slint::Timer timer;
timer.start(slint::TimerMode::SingleShot, std::chrono::milliseconds(100),
[&] {
// Renderers without a rendering notifier (software) support
// snapshots.
if (notifier_error) {
const auto pixels = probe->window().take_snapshot();
if (pixels && pixels->width() > 0 && pixels->height() > 0) {
result = 0;
} else {
std::cerr << "Slint probe could not read rendered pixels"
<< std::endl;
}
}
slint::quit_event_loop();
});
slint::run_event_loop();
probe->hide();
}
SDL_Quit();
return result;
}
} // namespace crossdesk
+2
View File
@@ -329,6 +329,8 @@ function setup_targets()
add_cxxflags("/bigobj")
add_links("opengl32")
add_files("apps/desktop/src/platform/windows/gui/tray/win_tray.cpp",
"apps/desktop/src/platform/windows/gui/slint_backend.cpp",
"apps/desktop/src/platform/windows/gui/slint_renderer_probe.cpp",
"apps/desktop/src/platform/common/gui/opengl_video_renderer.cpp",
"apps/desktop/src/platform/common/gui/video_renderer_factory_opengl.cpp",
"apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp",