2026-02-02 12:24:50 -08:00
|
|
|
#include "core/application.hpp"
|
|
|
|
|
#include "core/logger.hpp"
|
|
|
|
|
#include <exception>
|
2026-02-07 17:59:40 -08:00
|
|
|
#include <csignal>
|
2026-02-22 06:32:49 -08:00
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <cctype>
|
|
|
|
|
#include <string>
|
2026-02-07 17:59:40 -08:00
|
|
|
#include <SDL2/SDL.h>
|
2026-04-03 23:27:02 -07:00
|
|
|
#ifdef __APPLE__
|
|
|
|
|
#include <mach-o/dyld.h>
|
|
|
|
|
#include <libgen.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#endif
|
2026-02-18 17:38:08 -08:00
|
|
|
#ifdef __linux__
|
2026-02-07 17:59:40 -08:00
|
|
|
#include <X11/Xlib.h>
|
2026-03-22 21:40:16 +03:00
|
|
|
#include <execinfo.h>
|
|
|
|
|
#include <unistd.h>
|
2026-04-04 00:21:15 -07:00
|
|
|
#include <libgen.h>
|
2026-04-03 20:19:33 -07:00
|
|
|
#include <cstring>
|
2026-02-07 17:59:40 -08:00
|
|
|
|
2026-02-07 18:33:14 -08:00
|
|
|
// Keep a persistent X11 connection for emergency mouse release in signal handlers.
|
|
|
|
|
// XOpenDisplay inside a signal handler is unreliable, so we open it once at startup.
|
|
|
|
|
static Display* g_emergencyDisplay = nullptr;
|
|
|
|
|
|
2026-02-07 17:59:40 -08:00
|
|
|
static void releaseMouseGrab() {
|
2026-02-07 18:33:14 -08:00
|
|
|
if (g_emergencyDisplay) {
|
|
|
|
|
XUngrabPointer(g_emergencyDisplay, CurrentTime);
|
|
|
|
|
XUngrabKeyboard(g_emergencyDisplay, CurrentTime);
|
|
|
|
|
XFlush(g_emergencyDisplay);
|
2026-02-07 17:59:40 -08:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-18 17:38:08 -08:00
|
|
|
#else
|
|
|
|
|
static void releaseMouseGrab() {}
|
|
|
|
|
#endif
|
2026-02-07 17:59:40 -08:00
|
|
|
|
2026-03-22 21:40:16 +03:00
|
|
|
#ifdef __linux__
|
2026-04-03 20:19:33 -07:00
|
|
|
static void crashHandlerSigaction(int sig, siginfo_t* info, void* /*ucontext*/) {
|
|
|
|
|
releaseMouseGrab();
|
|
|
|
|
void* frames[64];
|
|
|
|
|
int n = backtrace(frames, 64);
|
|
|
|
|
const char* sigName = (sig == SIGSEGV) ? "SIGSEGV" :
|
|
|
|
|
(sig == SIGABRT) ? "SIGABRT" :
|
|
|
|
|
(sig == SIGFPE) ? "SIGFPE" : "UNKNOWN";
|
|
|
|
|
void* faultAddr = info ? info->si_addr : nullptr;
|
2026-04-03 20:58:32 -07:00
|
|
|
fprintf(stderr, "\n=== CRASH: signal %s (%d) faultAddr=%p ===\n",
|
|
|
|
|
sigName, sig, faultAddr);
|
2026-04-03 20:19:33 -07:00
|
|
|
backtrace_symbols_fd(frames, n, STDERR_FILENO);
|
|
|
|
|
FILE* f = fopen("/tmp/wowee_debug.log", "a");
|
|
|
|
|
if (f) {
|
2026-04-03 20:58:32 -07:00
|
|
|
fprintf(f, "\n=== CRASH: signal %s (%d) faultAddr=%p ===\n",
|
|
|
|
|
sigName, sig, faultAddr);
|
2026-04-03 20:19:33 -07:00
|
|
|
fflush(f);
|
|
|
|
|
backtrace_symbols_fd(frames, n, fileno(f));
|
|
|
|
|
fclose(f);
|
2026-03-22 21:40:16 +03:00
|
|
|
}
|
2026-04-03 20:19:33 -07:00
|
|
|
// Re-raise with default handler
|
|
|
|
|
struct sigaction sa;
|
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
|
|
|
sa.sa_handler = SIG_DFL;
|
|
|
|
|
sigaction(sig, &sa, nullptr);
|
|
|
|
|
raise(sig);
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
static void crashHandler(int sig) {
|
|
|
|
|
releaseMouseGrab();
|
2026-02-07 17:59:40 -08:00
|
|
|
std::signal(sig, SIG_DFL);
|
|
|
|
|
std::raise(sig);
|
|
|
|
|
}
|
2026-04-03 20:19:33 -07:00
|
|
|
#endif
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-02-22 06:32:49 -08:00
|
|
|
static wowee::core::LogLevel readLogLevelFromEnv() {
|
|
|
|
|
const char* raw = std::getenv("WOWEE_LOG_LEVEL");
|
|
|
|
|
if (!raw || !*raw) return wowee::core::LogLevel::WARNING;
|
|
|
|
|
std::string level(raw);
|
|
|
|
|
for (char& c : level) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
|
|
|
if (level == "debug") return wowee::core::LogLevel::DEBUG;
|
|
|
|
|
if (level == "info") return wowee::core::LogLevel::INFO;
|
|
|
|
|
if (level == "warn" || level == "warning") return wowee::core::LogLevel::WARNING;
|
2026-02-25 11:14:53 -08:00
|
|
|
if (level == "error") return wowee::core::kLogLevelError;
|
2026-02-22 06:32:49 -08:00
|
|
|
if (level == "fatal") return wowee::core::LogLevel::FATAL;
|
|
|
|
|
return wowee::core::LogLevel::WARNING;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 11:43:37 -08:00
|
|
|
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
|
2026-02-18 17:38:08 -08:00
|
|
|
#ifdef __linux__
|
2026-02-07 18:33:14 -08:00
|
|
|
g_emergencyDisplay = XOpenDisplay(nullptr);
|
2026-04-03 20:19:33 -07:00
|
|
|
// Use sigaction for SIGSEGV/SIGABRT/SIGFPE to get si_addr (faulting address)
|
|
|
|
|
{
|
|
|
|
|
struct sigaction sa;
|
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
|
|
|
sa.sa_sigaction = crashHandlerSigaction;
|
|
|
|
|
sa.sa_flags = SA_SIGINFO;
|
|
|
|
|
sigaction(SIGSEGV, &sa, nullptr);
|
|
|
|
|
sigaction(SIGABRT, &sa, nullptr);
|
|
|
|
|
sigaction(SIGFPE, &sa, nullptr);
|
|
|
|
|
}
|
|
|
|
|
std::signal(SIGTERM, [](int) { std::_Exit(1); });
|
|
|
|
|
std::signal(SIGINT, [](int) { std::_Exit(1); });
|
|
|
|
|
#else
|
2026-02-07 17:59:40 -08:00
|
|
|
std::signal(SIGSEGV, crashHandler);
|
|
|
|
|
std::signal(SIGABRT, crashHandler);
|
|
|
|
|
std::signal(SIGFPE, crashHandler);
|
|
|
|
|
std::signal(SIGTERM, crashHandler);
|
|
|
|
|
std::signal(SIGINT, crashHandler);
|
2026-04-03 20:19:33 -07:00
|
|
|
#endif
|
2026-04-03 23:27:02 -07:00
|
|
|
// Change working directory to the executable's directory so relative asset
|
|
|
|
|
// paths (assets/shaders/, Data/, etc.) resolve correctly from any launch location.
|
|
|
|
|
#ifdef __APPLE__
|
|
|
|
|
{
|
|
|
|
|
uint32_t bufSize = 0;
|
|
|
|
|
_NSGetExecutablePath(nullptr, &bufSize);
|
|
|
|
|
std::string exePath(bufSize, '\0');
|
|
|
|
|
_NSGetExecutablePath(exePath.data(), &bufSize);
|
feat(rendering): GPU architecture + visual quality fixes
M2 GPU instancing
- M2InstanceGPU SSBO (96 B/entry, double-buffered, 16384 max)
- Group opaque instances by (modelId, LOD); single vkCmdDrawIndexed per group
- boneBase field indexes into mega bone SSBO via gl_InstanceIndex
Indirect terrain drawing
- 24 MB mega index buffer (6M uint32) + 64 MB mega vertex buffer
- CPU builds VkDrawIndexedIndirectCommand per visible chunk
- Single VB/IB bind per frame; shadow pass reuses mega buffers
- Replaced vkCmdDrawIndexedIndirect with direct vkCmdDrawIndexed to fix
host-mapped buffer race condition that caused terrain flickering
GPU frustum culling (compute shader)
- m2_cull.comp.glsl: 64-thread workgroups, sphere-vs-6-planes + distance cull
- CullInstanceGPU SSBO input, uint visibility[] output, double-buffered
- dispatchCullCompute() runs before main pass via render graph node
Consolidated bone matrix SSBOs
- 16 MB double-buffered mega bone SSBO (2048 instances × 128 bones)
- Eliminated per-instance descriptor sets; one megaBoneSet_ per frame
- prepareRender() packs bone matrices consecutively into current frame slot
Render graph / frame graph
- RenderGraph: RGResource handles, RGPass nodes, Kahn topological sort
- Automatic VkImageMemoryBarrier/VkBufferMemoryBarrier between passes
- Passes: minimap_composite, worldmap_composite, preview_composite,
shadow_pass, reflection_pass, compute_cull
- beginFrame() uses buildFrameGraph() + renderGraph_->execute(cmd)
Pipeline derivatives
- PipelineBuilder::setFlags/setBasePipeline for VK_PIPELINE_CREATE_DERIVATIVE_BIT
- M2 opaque = base; alphaTest/alpha/additive are derivatives
- Applied to terrain (wireframe) and WMO (alpha-test) renderers
Rendering bug fixes:
- fix(shadow): compute lightSpaceMatrix before updatePerFrameUBO to eliminate
one-frame lag that caused shadow trails and flicker on moving objects
- fix(shadow): scale depth bias with shadowDistance_ instead of hardcoded 0.8f
to prevent acne at close range and gaps at far range
- fix(visibility): WMO group distance threshold 500u → 1200u to match terrain
view distance; buildings were disappearing on the horizon
- fix(precision): camera near plane 0.05 → 0.5 (ratio 600K:1 → 60K:1),
eliminating Z-fighting and improving frustum plane extraction stability
- fix(streaming): terrain load radius 4 → 6 tiles (~2133u → ~3200u) to exceed
M2 render distance (2800u) and eliminate pop-in when camera turns;
unload radius 7 → 9; spawn radius 3 → 4
- fix(visibility): ground-detail M2 distance multiplier 0.75 → 0.9 to reduce
early pop of grass and debris
2026-04-04 13:43:16 +03:00
|
|
|
if (chdir(dirname(exePath.data())) != 0) {}
|
2026-04-03 23:27:02 -07:00
|
|
|
}
|
|
|
|
|
#elif defined(__linux__)
|
|
|
|
|
{
|
|
|
|
|
char buf[4096];
|
|
|
|
|
ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
|
feat(rendering): GPU architecture + visual quality fixes
M2 GPU instancing
- M2InstanceGPU SSBO (96 B/entry, double-buffered, 16384 max)
- Group opaque instances by (modelId, LOD); single vkCmdDrawIndexed per group
- boneBase field indexes into mega bone SSBO via gl_InstanceIndex
Indirect terrain drawing
- 24 MB mega index buffer (6M uint32) + 64 MB mega vertex buffer
- CPU builds VkDrawIndexedIndirectCommand per visible chunk
- Single VB/IB bind per frame; shadow pass reuses mega buffers
- Replaced vkCmdDrawIndexedIndirect with direct vkCmdDrawIndexed to fix
host-mapped buffer race condition that caused terrain flickering
GPU frustum culling (compute shader)
- m2_cull.comp.glsl: 64-thread workgroups, sphere-vs-6-planes + distance cull
- CullInstanceGPU SSBO input, uint visibility[] output, double-buffered
- dispatchCullCompute() runs before main pass via render graph node
Consolidated bone matrix SSBOs
- 16 MB double-buffered mega bone SSBO (2048 instances × 128 bones)
- Eliminated per-instance descriptor sets; one megaBoneSet_ per frame
- prepareRender() packs bone matrices consecutively into current frame slot
Render graph / frame graph
- RenderGraph: RGResource handles, RGPass nodes, Kahn topological sort
- Automatic VkImageMemoryBarrier/VkBufferMemoryBarrier between passes
- Passes: minimap_composite, worldmap_composite, preview_composite,
shadow_pass, reflection_pass, compute_cull
- beginFrame() uses buildFrameGraph() + renderGraph_->execute(cmd)
Pipeline derivatives
- PipelineBuilder::setFlags/setBasePipeline for VK_PIPELINE_CREATE_DERIVATIVE_BIT
- M2 opaque = base; alphaTest/alpha/additive are derivatives
- Applied to terrain (wireframe) and WMO (alpha-test) renderers
Rendering bug fixes:
- fix(shadow): compute lightSpaceMatrix before updatePerFrameUBO to eliminate
one-frame lag that caused shadow trails and flicker on moving objects
- fix(shadow): scale depth bias with shadowDistance_ instead of hardcoded 0.8f
to prevent acne at close range and gaps at far range
- fix(visibility): WMO group distance threshold 500u → 1200u to match terrain
view distance; buildings were disappearing on the horizon
- fix(precision): camera near plane 0.05 → 0.5 (ratio 600K:1 → 60K:1),
eliminating Z-fighting and improving frustum plane extraction stability
- fix(streaming): terrain load radius 4 → 6 tiles (~2133u → ~3200u) to exceed
M2 render distance (2800u) and eliminate pop-in when camera turns;
unload radius 7 → 9; spawn radius 3 → 4
- fix(visibility): ground-detail M2 distance multiplier 0.75 → 0.9 to reduce
early pop of grass and debris
2026-04-04 13:43:16 +03:00
|
|
|
if (len > 0) { buf[len] = '\0'; if (chdir(dirname(buf)) != 0) {} }
|
2026-04-03 23:27:02 -07:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
try {
|
2026-02-22 06:32:49 -08:00
|
|
|
wowee::core::Logger::getInstance().setLogLevel(readLogLevelFromEnv());
|
2026-02-02 23:22:58 -08:00
|
|
|
LOG_INFO("=== Wowee Native Client ===");
|
2026-02-02 12:24:50 -08:00
|
|
|
LOG_INFO("Starting application...");
|
|
|
|
|
|
|
|
|
|
wowee::core::Application app;
|
|
|
|
|
|
|
|
|
|
if (!app.initialize()) {
|
|
|
|
|
LOG_FATAL("Failed to initialize application");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
app.run();
|
|
|
|
|
app.shutdown();
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Application exited successfully");
|
2026-02-18 17:38:08 -08:00
|
|
|
#ifdef __linux__
|
2026-02-07 18:33:14 -08:00
|
|
|
if (g_emergencyDisplay) { XCloseDisplay(g_emergencyDisplay); g_emergencyDisplay = nullptr; }
|
2026-02-18 17:38:08 -08:00
|
|
|
#endif
|
2026-02-02 12:24:50 -08:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& e) {
|
2026-02-07 17:59:40 -08:00
|
|
|
releaseMouseGrab();
|
2026-02-02 12:24:50 -08:00
|
|
|
LOG_FATAL("Unhandled exception: ", e.what());
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
2026-02-07 17:59:40 -08:00
|
|
|
releaseMouseGrab();
|
2026-02-02 12:24:50 -08:00
|
|
|
LOG_FATAL("Unknown exception occurred");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|