main.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. #include <QCoreApplication>
  2. #include <QDateTime>
  3. #include <QDebug>
  4. #include <QDir>
  5. #include <QFile>
  6. #include <QGuiApplication>
  7. #include <QOffscreenSurface>
  8. #include <QOpenGLContext>
  9. #include <QOpenGLFunctions>
  10. #include <QQmlApplicationEngine>
  11. #include <QQmlContext>
  12. #include <QQuickWindow>
  13. #include <QSGRendererInterface>
  14. #include <QSurfaceFormat>
  15. #include <QTextStream>
  16. #include <QUrl>
  17. #include <cstdio>
  18. #include <memory>
  19. #include <qglobal.h>
  20. #include <qguiapplication.h>
  21. #include <qnamespace.h>
  22. #include <qobject.h>
  23. #include <qqml.h>
  24. #include <qqmlapplicationengine.h>
  25. #include <qsgrendererinterface.h>
  26. #include <qstringliteral.h>
  27. #include <qstringview.h>
  28. #include <qsurfaceformat.h>
  29. #include <qurl.h>
  30. #ifdef Q_OS_WIN
  31. #include <QProcess>
  32. #include <gl/gl.h>
  33. #include <windows.h>
  34. #pragma comment(lib, "opengl32.lib")
  35. #endif
  36. #include "app/core/game_engine.h"
  37. #include "app/core/language_manager.h"
  38. #include "app/models/graphics_settings_proxy.h"
  39. #include "app/models/map_preview_image_provider.h"
  40. #include "app/models/minimap_image_provider.h"
  41. #include "ui/campaign_map_view.h"
  42. #include "ui/gl_view.h"
  43. #include "ui/theme.h"
  44. // Constants replacing magic numbers
  45. constexpr int k_depth_buffer_bits = 24;
  46. constexpr int k_stencil_buffer_bits = 8;
  47. #ifdef Q_OS_WIN
  48. // Test OpenGL using native Win32 API (before any Qt initialization)
  49. // Returns true if OpenGL is available, false otherwise
  50. static bool testNativeOpenGL() {
  51. WNDCLASSA wc = {};
  52. wc.lpfnWndProc = DefWindowProcA;
  53. wc.hInstance = GetModuleHandle(nullptr);
  54. wc.lpszClassName = "OpenGLTest";
  55. if (!RegisterClassA(&wc)) {
  56. return false;
  57. }
  58. HWND hwnd = CreateWindowExA(0, "OpenGLTest", "", WS_OVERLAPPEDWINDOW, 0, 0, 1,
  59. 1, nullptr, nullptr, wc.hInstance, nullptr);
  60. if (!hwnd) {
  61. UnregisterClassA("OpenGLTest", wc.hInstance);
  62. return false;
  63. }
  64. HDC hdc = GetDC(hwnd);
  65. if (!hdc) {
  66. DestroyWindow(hwnd);
  67. UnregisterClassA("OpenGLTest", wc.hInstance);
  68. return false;
  69. }
  70. PIXELFORMATDESCRIPTOR pfd = {};
  71. pfd.nSize = sizeof(pfd);
  72. pfd.nVersion = 1;
  73. pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
  74. pfd.iPixelType = PFD_TYPE_RGBA;
  75. pfd.cColorBits = 24;
  76. pfd.cDepthBits = 24;
  77. pfd.cStencilBits = 8;
  78. pfd.iLayerType = PFD_MAIN_PLANE;
  79. int pixelFormat = ChoosePixelFormat(hdc, &pfd);
  80. bool success = false;
  81. if (pixelFormat != 0 && SetPixelFormat(hdc, pixelFormat, &pfd)) {
  82. HGLRC hglrc = wglCreateContext(hdc);
  83. if (hglrc) {
  84. if (wglMakeCurrent(hdc, hglrc)) {
  85. // Successfully created OpenGL context
  86. const char *vendor = (const char *)glGetString(GL_VENDOR);
  87. const char *renderer = (const char *)glGetString(GL_RENDERER);
  88. const char *version = (const char *)glGetString(GL_VERSION);
  89. if (vendor && renderer && version) {
  90. fprintf(stderr,
  91. "[OpenGL Test] Native context created successfully\n");
  92. fprintf(stderr, "[OpenGL Test] Vendor: %s\n", vendor);
  93. fprintf(stderr, "[OpenGL Test] Renderer: %s\n", renderer);
  94. fprintf(stderr, "[OpenGL Test] Version: %s\n", version);
  95. success = true;
  96. }
  97. wglMakeCurrent(nullptr, nullptr);
  98. }
  99. wglDeleteContext(hglrc);
  100. }
  101. }
  102. ReleaseDC(hwnd, hdc);
  103. DestroyWindow(hwnd);
  104. UnregisterClassA("OpenGLTest", wc.hInstance);
  105. return success;
  106. }
  107. // Windows crash handler to detect OpenGL failures and suggest fallback
  108. static bool g_opengl_crashed = false;
  109. static LONG WINAPI crashHandler(EXCEPTION_POINTERS *exceptionInfo) {
  110. if (exceptionInfo->ExceptionRecord->ExceptionCode ==
  111. EXCEPTION_ACCESS_VIOLATION) {
  112. // Log crash
  113. FILE *crash_log = fopen("opengl_crash.txt", "w");
  114. if (crash_log) {
  115. fprintf(crash_log,
  116. "OpenGL/Qt rendering crash detected (Access Violation)\n");
  117. fprintf(crash_log, "Try running with: run_debug_softwaregl.cmd\n");
  118. fprintf(crash_log,
  119. "Or set environment variable: QT_QUICK_BACKEND=software\n");
  120. fclose(crash_log);
  121. }
  122. qCritical() << "=== CRASH DETECTED ===";
  123. qCritical() << "OpenGL rendering failed. This usually means:";
  124. qCritical() << "1. Graphics drivers are outdated";
  125. qCritical() << "2. Running in a VM with incomplete OpenGL support";
  126. qCritical() << "3. GPU doesn't support required OpenGL version";
  127. qCritical() << "";
  128. qCritical() << "To fix: Run run_debug_softwaregl.cmd instead";
  129. qCritical() << "Or set: set QT_QUICK_BACKEND=software";
  130. g_opengl_crashed = true;
  131. }
  132. return EXCEPTION_CONTINUE_SEARCH;
  133. }
  134. #endif
  135. auto main(int argc, char *argv[]) -> int {
  136. #ifdef Q_OS_WIN
  137. // Install crash handler to detect OpenGL failures
  138. SetUnhandledExceptionFilter(crashHandler);
  139. // Test OpenGL BEFORE any Qt initialization (using native Win32 API)
  140. fprintf(stderr, "[Pre-Init] Testing native OpenGL availability...\n");
  141. bool opengl_available = testNativeOpenGL();
  142. if (!opengl_available) {
  143. fprintf(stderr, "[Pre-Init] WARNING: OpenGL test failed!\n");
  144. fprintf(stderr, "[Pre-Init] Forcing software rendering mode\n");
  145. _putenv("QT_QUICK_BACKEND=software");
  146. _putenv("QT_OPENGL=software");
  147. } else {
  148. fprintf(stderr, "[Pre-Init] OpenGL test passed\n");
  149. }
  150. // Check if we should use software rendering
  151. bool use_software = qEnvironmentVariableIsSet("QT_QUICK_BACKEND") &&
  152. qEnvironmentVariable("QT_QUICK_BACKEND") == "software";
  153. if (use_software) {
  154. qInfo() << "=== SOFTWARE RENDERING MODE ===";
  155. qInfo() << "Using Qt Quick Software renderer (CPU-based)";
  156. qInfo() << "Performance will be limited but should work on all systems";
  157. }
  158. #endif
  159. // Setup message handler for debugging
  160. qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &context,
  161. const QString &msg) {
  162. QByteArray const local_msg = msg.toLocal8Bit();
  163. const char *file = (context.file != nullptr) ? context.file : "";
  164. const char *function =
  165. (context.function != nullptr) ? context.function : "";
  166. FILE *out = stderr;
  167. switch (type) {
  168. case QtDebugMsg:
  169. fprintf(out, "[DEBUG] %s (%s:%u, %s)\n", local_msg.constData(), file,
  170. context.line, function);
  171. break;
  172. case QtInfoMsg:
  173. fprintf(out, "[INFO] %s\n", local_msg.constData());
  174. break;
  175. case QtWarningMsg:
  176. fprintf(out, "[WARNING] %s (%s:%u, %s)\n", local_msg.constData(), file,
  177. context.line, function);
  178. // Check for critical OpenGL warnings
  179. if (msg.contains("OpenGL", Qt::CaseInsensitive) ||
  180. msg.contains("scene graph", Qt::CaseInsensitive) ||
  181. msg.contains("RHI", Qt::CaseInsensitive)) {
  182. fprintf(out, "[HINT] If you see crashes, try software rendering: set "
  183. "QT_QUICK_BACKEND=software\n");
  184. }
  185. break;
  186. case QtCriticalMsg:
  187. fprintf(out, "[CRITICAL] %s (%s:%u, %s)\n", local_msg.constData(), file,
  188. context.line, function);
  189. fprintf(
  190. out,
  191. "[CRITICAL] Try running with software rendering if this persists\n");
  192. break;
  193. case QtFatalMsg:
  194. fprintf(out, "[FATAL] %s (%s:%u, %s)\n", local_msg.constData(), file,
  195. context.line, function);
  196. fprintf(out, "[FATAL] === RECOVERY SUGGESTION ===\n");
  197. fprintf(out, "[FATAL] Run: run_debug_softwaregl.cmd\n");
  198. fprintf(out, "[FATAL] Or set: QT_QUICK_BACKEND=software\n");
  199. abort();
  200. }
  201. fflush(out);
  202. });
  203. qInfo() << "=== Standard of Iron - Starting ===";
  204. qInfo() << "Qt version:" << QT_VERSION_STR;
  205. // Linux-specific: prefer X11 over Wayland for better OpenGL compatibility
  206. #ifndef Q_OS_WIN
  207. if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY") &&
  208. qEnvironmentVariableIsSet("DISPLAY")) {
  209. qputenv("QT_QPA_PLATFORM", "xcb");
  210. qInfo() << "Linux: Using X11 (xcb) platform";
  211. }
  212. #endif
  213. qInfo() << "Setting OpenGL environment...";
  214. qputenv("QT_OPENGL", "desktop");
  215. qputenv("QSG_RHI_BACKEND", "opengl");
  216. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  217. qInfo() << "Setting graphics API to OpenGLRhi...";
  218. QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);
  219. #endif
  220. qInfo() << "Configuring OpenGL surface format...";
  221. QSurfaceFormat fmt;
  222. fmt.setVersion(3, 3);
  223. fmt.setProfile(QSurfaceFormat::CoreProfile);
  224. fmt.setDepthBufferSize(k_depth_buffer_bits);
  225. fmt.setStencilBufferSize(k_stencil_buffer_bits);
  226. fmt.setSamples(0);
  227. #ifdef Q_OS_WIN
  228. // Windows: Request compatibility profile for better driver support
  229. // Some Windows drivers have issues with Core profile on older hardware
  230. fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
  231. qInfo() << "Windows detected: Using OpenGL Compatibility Profile";
  232. #endif
  233. QSurfaceFormat::setDefaultFormat(fmt);
  234. qInfo() << "Surface format configured: OpenGL" << fmt.majorVersion() << "."
  235. << fmt.minorVersion();
  236. qInfo() << "Creating QGuiApplication...";
  237. QGuiApplication app(argc, argv);
  238. qInfo() << "QGuiApplication created successfully";
  239. // Use unique_ptr with custom deleter for Qt objects
  240. // This ensures proper cleanup order and prevents segfaults
  241. std::unique_ptr<LanguageManager> language_manager;
  242. std::unique_ptr<GameEngine> game_engine;
  243. std::unique_ptr<App::Models::GraphicsSettingsProxy> graphics_settings;
  244. std::unique_ptr<QQmlApplicationEngine> engine;
  245. qInfo() << "Creating LanguageManager...";
  246. language_manager = std::make_unique<LanguageManager>(&app);
  247. qInfo() << "LanguageManager created";
  248. qInfo() << "Creating GameEngine...";
  249. game_engine = std::make_unique<GameEngine>(&app);
  250. qInfo() << "GameEngine created";
  251. qInfo() << "Creating GraphicsSettingsProxy...";
  252. graphics_settings =
  253. std::make_unique<App::Models::GraphicsSettingsProxy>(&app);
  254. qInfo() << "GraphicsSettingsProxy created";
  255. qInfo() << "Setting up QML engine...";
  256. engine = std::make_unique<QQmlApplicationEngine>();
  257. // Register minimap image provider
  258. qInfo() << "Registering minimap image provider...";
  259. auto *minimap_provider = new MinimapImageProvider();
  260. engine->addImageProvider("minimap", minimap_provider);
  261. // Register map preview image provider
  262. qInfo() << "Registering map preview image provider...";
  263. auto *map_preview_provider = new MapPreviewImageProvider();
  264. engine->addImageProvider("mappreview", map_preview_provider);
  265. qInfo() << "Adding context properties...";
  266. engine->rootContext()->setContextProperty("languageManager",
  267. language_manager.get());
  268. engine->rootContext()->setContextProperty("game", game_engine.get());
  269. engine->rootContext()->setContextProperty("mapPreviewProvider",
  270. map_preview_provider);
  271. engine->rootContext()->setContextProperty("graphicsSettings",
  272. graphics_settings.get());
  273. // Connect minimap image updates to the provider with DirectConnection
  274. // This ensures the image is set in the provider BEFORE QML reacts to the
  275. // signal
  276. QObject::connect(
  277. game_engine.get(), &GameEngine::minimap_image_changed, &app,
  278. [minimap_provider, game_engine_ptr = game_engine.get()]() {
  279. minimap_provider->set_minimap_image(game_engine_ptr->minimap_image());
  280. },
  281. Qt::DirectConnection);
  282. // Set initial minimap image if available
  283. if (!game_engine->minimap_image().isNull()) {
  284. qInfo() << "Setting initial minimap image";
  285. minimap_provider->set_minimap_image(game_engine->minimap_image());
  286. }
  287. qInfo() << "Adding import path...";
  288. engine->addImportPath("qrc:/StandardOfIron/ui/qml");
  289. engine->addImportPath("qrc:/");
  290. qInfo() << "Registering QML types...";
  291. qmlRegisterType<GLView>("StandardOfIron", 1, 0, "GLView");
  292. qmlRegisterType<CampaignMapView>("StandardOfIron", 1, 0, "CampaignMapView");
  293. // Register Theme singleton
  294. qmlRegisterSingletonType<Theme>("StandardOfIron", 1, 0, "Theme",
  295. &Theme::create);
  296. // Register StyleGuide singleton from QML file
  297. qmlRegisterSingletonType(QUrl("qrc:/StandardOfIron/ui/qml/StyleGuide.qml"),
  298. "StandardOfIron", 1, 0, "StyleGuide");
  299. qInfo() << "Loading Main.qml...";
  300. qInfo() << "Loading Main.qml...";
  301. engine->load(QUrl(QStringLiteral("qrc:/StandardOfIron/ui/qml/Main.qml")));
  302. qInfo() << "Checking if QML loaded...";
  303. if (engine->rootObjects().isEmpty()) {
  304. qWarning() << "Failed to load QML file";
  305. return -1;
  306. }
  307. qInfo() << "QML loaded successfully, root objects count:"
  308. << engine->rootObjects().size();
  309. // Connect language changed signal to retranslate QML
  310. qInfo() << "Connecting language change handler...";
  311. QObject::connect(language_manager.get(), &LanguageManager::languageChanged,
  312. engine.get(), &QQmlApplicationEngine::retranslate);
  313. qInfo() << "Language change handler connected";
  314. qInfo() << "Finding QQuickWindow...";
  315. auto *root_obj = engine->rootObjects().first();
  316. auto *window = qobject_cast<QQuickWindow *>(root_obj);
  317. if (window == nullptr) {
  318. qInfo() << "Root object is not a window, searching children...";
  319. window = root_obj->findChild<QQuickWindow *>();
  320. }
  321. if (window == nullptr) {
  322. qWarning() << "No QQuickWindow found for OpenGL initialization.";
  323. return -2;
  324. }
  325. qInfo() << "QQuickWindow found";
  326. qInfo() << "Setting window in GameEngine...";
  327. game_engine->setWindow(window);
  328. qInfo() << "Window set successfully";
  329. qInfo() << "Connecting scene graph signals...";
  330. qInfo() << "Connecting scene graph signals...";
  331. QObject::connect(
  332. window, &QQuickWindow::sceneGraphInitialized, window, [window]() {
  333. qInfo() << "Scene graph initialized!";
  334. if (auto *renderer_interface = window->rendererInterface()) {
  335. const auto api = renderer_interface->graphicsApi();
  336. QString name;
  337. switch (api) {
  338. case QSGRendererInterface::OpenGLRhi:
  339. name = "OpenGLRhi";
  340. break;
  341. case QSGRendererInterface::VulkanRhi:
  342. name = "VulkanRhi";
  343. break;
  344. case QSGRendererInterface::Direct3D11Rhi:
  345. name = "D3D11Rhi";
  346. break;
  347. case QSGRendererInterface::MetalRhi:
  348. name = "MetalRhi";
  349. break;
  350. case QSGRendererInterface::Software:
  351. name = "Software";
  352. break;
  353. default:
  354. name = "Unknown";
  355. break;
  356. }
  357. qInfo() << "QSG graphicsApi:" << name;
  358. }
  359. });
  360. QObject::connect(window, &QQuickWindow::sceneGraphError, &app,
  361. [&](QQuickWindow::SceneGraphError, const QString &msg) {
  362. qCritical()
  363. << "Failed to initialize OpenGL scene graph:" << msg;
  364. QGuiApplication::exit(3);
  365. });
  366. qInfo() << "Starting event loop...";
  367. int const result = QGuiApplication::exec();
  368. // Explicitly destroy in correct order to prevent segfault
  369. qInfo() << "Shutting down...";
  370. // Destroy QML engine first (destroys OpenGL context)
  371. engine.reset();
  372. qInfo() << "QML engine destroyed";
  373. // Then destroy game engine
  374. // OpenGL cleanup in destructors will be skipped if no valid context
  375. game_engine.reset();
  376. qInfo() << "GameEngine destroyed";
  377. // Finally destroy language manager
  378. language_manager.reset();
  379. qInfo() << "LanguageManager destroyed";
  380. #ifdef Q_OS_WIN
  381. // Check if we crashed during OpenGL initialization
  382. if (g_opengl_crashed) {
  383. qCritical() << "";
  384. qCritical() << "========================================";
  385. qCritical() << "OPENGL CRASH RECOVERY";
  386. qCritical() << "========================================";
  387. qCritical() << "";
  388. qCritical() << "The application crashed during OpenGL initialization.";
  389. qCritical()
  390. << "This is a known issue with Qt + some Windows graphics drivers.";
  391. qCritical() << "";
  392. qCritical() << "SOLUTION: Set environment variable before running:";
  393. qCritical() << " set QT_QUICK_BACKEND=software";
  394. qCritical() << "";
  395. qCritical() << "Or use the provided launcher:";
  396. qCritical() << " run_debug_softwaregl.cmd";
  397. qCritical() << "";
  398. return -1;
  399. }
  400. #endif
  401. return result;
  402. }