From c51347c913c87ffd457e84f3d1f6198a34a2db65 Mon Sep 17 00:00:00 2001 From: SergioFLS Date: Sat, 18 Apr 2026 14:40:27 -0400 Subject: [PATCH 1/4] Initial mouse implementation for windowframe --- src/glfw/main.c | 35 ++++++++++++++++++++++ src/runner.c | 3 ++ src/runner.h | 2 ++ src/runner_mouse.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++ src/runner_mouse.h | 31 ++++++++++++++++++++ src/vm_builtins.c | 27 +++++++++++++++++ src/vm_builtins.h | 4 +++ 7 files changed, 175 insertions(+) create mode 100644 src/runner_mouse.c create mode 100644 src/runner_mouse.h diff --git a/src/glfw/main.c b/src/glfw/main.c index 4fae0b6d..1e2c7be0 100644 --- a/src/glfw/main.c +++ b/src/glfw/main.c @@ -1,5 +1,6 @@ #include "data_win.h" #include "glfw/gl_legacy_renderer.h" +#include "runner_mouse.h" #include "vm.h" #include @@ -468,6 +469,35 @@ void saveInputRecording() { } } +// ===[ MOUSE INPUT ]=== + +static int32_t glfwMouseButtonToGml(int glfwButton) { + switch (glfwButton) { + case GLFW_MOUSE_BUTTON_LEFT: return MB_LEFT; + case GLFW_MOUSE_BUTTON_RIGHT: return MB_RIGHT; + case GLFW_MOUSE_BUTTON_MIDDLE: return MB_MIDDLE; + default: return INT32_MIN; // Unknown + } +} + +static void cursorPositionCallback(GLFWwindow* window, double xpos, double ypos) { + Runner* runner = (Runner*) glfwGetWindowUserPointer(window); + int fbWidth, fbHeight; + glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + runner->mouse->mouseX = (xpos/fbWidth) * (runner->dataWin->gen8.defaultWindowWidth / 2.0); + runner->mouse->mouseY = (ypos/fbHeight) * (runner->dataWin->gen8.defaultWindowHeight / 2.0); +} + +static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { + (void)mods; + Runner* runner = (Runner*) glfwGetWindowUserPointer(window); + int32_t gmlButton = glfwMouseButtonToGml(button); + if (0 > gmlButton) return; + if (action == GLFW_PRESS) RunnerMouse_onButtonDown(runner->mouse, gmlButton); + else if (action == GLFW_RELEASE) RunnerMouse_onButtonUp(runner->mouse, gmlButton); +} + + #ifndef _WIN32 typedef struct { int key; struct sigaction value; } PreviousSignalActionEntry; static PreviousSignalActionEntry* previousSignalActions = nullptr; @@ -702,6 +732,10 @@ int main(int argc, char* argv[]) { glfwSetKeyCallback(window, keyCallback); glfwSetCharCallback(window, characterCallback); + // Set up mouse input + glfwSetCursorPosCallback(window, cursorPositionCallback); + glfwSetMouseButtonCallback(window, mouseButtonCallback); + #ifndef _WIN32 struct sigaction sa = { .sa_handler = onCrashSignal }; sigemptyset(&sa.sa_mask); @@ -722,6 +756,7 @@ int main(int argc, char* argv[]) { while (!glfwWindowShouldClose(window) && !runner->shouldExit) { // Clear last frame's pressed/released state, then poll new input events RunnerKeyboard_beginFrame(runner->keyboard); + RunnerMouse_beginFrame(runner->mouse); glfwPollEvents(); // Process input recording/playback (must happen after glfwPollEvents, before Runner_step) diff --git a/src/runner.c b/src/runner.c index 0e08a9cb..c4326cb1 100644 --- a/src/runner.c +++ b/src/runner.c @@ -2,6 +2,7 @@ #include "data_win.h" #include "instance.h" #include "renderer.h" +#include "runner_mouse.h" #include "vm.h" #include "utils.h" #include "json_writer.h" @@ -1118,6 +1119,7 @@ Runner* Runner_create(DataWin* dataWin, VMContext* vm, Renderer* renderer, FileS runner->frameCount = 0; runner->osType = OS_WINDOWS; runner->keyboard = RunnerKeyboard_create(); + runner->mouse = RunnerMouse_create(); Runner_reset(runner); @@ -2156,6 +2158,7 @@ void Runner_free(Runner* runner) { cleanupState(runner); RunnerKeyboard_free(runner->keyboard); + RunnerMouse_free(runner->mouse); Instance_free(runner->globalScopeInstance); free(runner); } diff --git a/src/runner.h b/src/runner.h index 2c5eb09a..464f3e4d 100644 --- a/src/runner.h +++ b/src/runner.h @@ -8,6 +8,7 @@ #include "instance.h" #include "renderer.h" #include "runner_keyboard.h" +#include "runner_mouse.h" #include "vm.h" // ===[ Event Type Constants ]=== @@ -253,6 +254,7 @@ typedef struct Runner { int frameCount; uint32_t nextInstanceId; RunnerKeyboardState* keyboard; + RunnerMouseState* mouse; RuntimeBackground backgrounds[8]; uint32_t backgroundColor; // runtime-mutable (BGR format) bool drawBackgroundColor; diff --git a/src/runner_mouse.c b/src/runner_mouse.c new file mode 100644 index 00000000..d6d5c004 --- /dev/null +++ b/src/runner_mouse.c @@ -0,0 +1,73 @@ +#include "runner_mouse.h" +#include "utils.h" +#include +#include +#include +#include +#include + +static bool isValidButtonVirtual(int_fast8_t button) { + return button >= -1 && GML_MOUSE_BUTTONS > button; +} + +static bool isValidButton(int_fast8_t button) { + return button >= 1 && GML_MOUSE_BUTTONS > button; +} + +RunnerMouseState* RunnerMouse_create(void) { + RunnerMouseState* m = safeCalloc(1, sizeof(RunnerMouseState)); + return m; +} + +void RunnerMouse_free(RunnerMouseState* m) { + free(m); +} + +void RunnerMouse_beginFrame(RunnerMouseState* m) { + memset(m->buttonPressed, 0, sizeof(m->buttonPressed)); +} + +void RunnerMouse_onButtonDown(RunnerMouseState* m, int32_t button) { + if (!isValidButton(button)) return; + m->buttonDown[button-1] = true; + m->buttonPressed[button-1] = true; +} + +void RunnerMouse_onButtonUp(RunnerMouseState* m, int32_t button) { + if (!isValidButton(button)) return; + m->buttonDown[button-1] = false; +} + +bool RunnerMouse_checkButton(RunnerMouseState* m, int32_t button) { + if (!isValidButtonVirtual(button)) return false; + + if (isValidButton(button)) { + return m->buttonDown[button-1]; + } + + bool any = false; + repeat(GML_MOUSE_BUTTON_COUNT, i) { + any &= m->buttonDown[i]; + } + + if (button == MB_ANY) return any; + else if (button == MB_NONE) return !any; + return false; +} + +bool RunnerMouse_checkButtonPressed(RunnerMouseState* m, int32_t button) { + if (!isValidButtonVirtual(button)) return false; + + if (isValidButton(button)) { + return m->buttonPressed[button-1]; + } + + bool any = false; + repeat(GML_MOUSE_BUTTON_COUNT, i) { + any &= m->buttonPressed[i]; + } + + if (button == MB_ANY) return any; + else if (button == MB_NONE) return !any; + return false; +} \ No newline at end of file diff --git a/src/runner_mouse.h b/src/runner_mouse.h new file mode 100644 index 00000000..047edd69 --- /dev/null +++ b/src/runner_mouse.h @@ -0,0 +1,31 @@ +#pragma once + +#include "common.h" +#include + +// Mouse buttons +// TODO verify side buttons are correct +#define MB_ANY -1 +#define MB_NONE 0 +#define MB_LEFT 1 +#define MB_RIGHT 2 +#define MB_MIDDLE 3 +#define MB_SIDE1 4 +#define MB_SIDE2 5 +#define GML_MOUSE_BUTTONS 6 + +#define GML_MOUSE_BUTTON_COUNT 5 + +typedef struct RunnerMouseState { + double mouseX, mouseY; + bool buttonDown[GML_MOUSE_BUTTON_COUNT]; + bool buttonPressed[GML_MOUSE_BUTTON_COUNT]; +} RunnerMouseState; + +RunnerMouseState* RunnerMouse_create(void); +void RunnerMouse_free(RunnerMouseState* m); +void RunnerMouse_beginFrame(RunnerMouseState* m); +void RunnerMouse_onButtonDown(RunnerMouseState* m, int32_t button); +void RunnerMouse_onButtonUp(RunnerMouseState* m, int32_t button); +bool RunnerMouse_checkButton(RunnerMouseState* m, int32_t button); +bool RunnerMouse_checkButtonPressed(RunnerMouseState* m, int32_t button); \ No newline at end of file diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 62a6b0a2..fc2c788d 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -247,6 +247,10 @@ int16_t VMBuiltins_resolveBuiltinVarId(const char* name) { if (strcmp(name, "keyboard_lastchar") == 0) return BUILTIN_VAR_KEYBOARD_LASTCHAR; if (strcmp(name, "keyboard_lastkey") == 0) return BUILTIN_VAR_KEYBOARD_LASTKEY; + // Mouse + if (strcmp(name, "mouse_x") == 0) return BUILTIN_VAR_MOUSE_X; + if (strcmp(name, "mouse_y") == 0) return BUILTIN_VAR_MOUSE_Y; + // Surfaces if (strcmp(name, "application_surface") == 0) return BUILTIN_VAR_APPLICATION_SURFACE; @@ -590,6 +594,10 @@ RValue VMBuiltins_getVariable(VMContext* ctx, int16_t builtinVarId, const char* if (builtinVarId == BUILTIN_VAR_KEYBOARD_LASTCHAR) return RValue_makeString(runner->keyboard->lastChar); if (builtinVarId == BUILTIN_VAR_KEYBOARD_LASTKEY) return RValue_makeReal((GMLReal) runner->keyboard->lastKey); + // Mouse variables + if (builtinVarId == BUILTIN_VAR_MOUSE_X) return RValue_makeReal((GMLReal) runner->mouse->mouseX); + if (builtinVarId == BUILTIN_VAR_MOUSE_Y) return RValue_makeReal((GMLReal) runner->mouse->mouseY); + // Surfaces if (builtinVarId == BUILTIN_VAR_APPLICATION_SURFACE) return RValue_makeReal(-1.0); // sentinel ID for the application surface @@ -3112,6 +3120,21 @@ static RValue builtinKeyboardClear(VMContext* ctx, RValue* args, int32_t argCoun return RValue_makeUndefined(); } +// Mouse functions +static RValue builtinMouseCheckButton(VMContext* ctx, RValue* args, int32_t argCount) { + if (1 > argCount) return RValue_makeBool(false); + Runner* runner = (Runner*) ctx->runner; + int32_t button = RValue_toInt32(args[0]); + return RValue_makeBool(RunnerMouse_checkButton(runner->mouse, button)); +} + +static RValue builtinMouseCheckButtonPressed(VMContext* ctx, RValue* args, int32_t argCount) { + if (1 > argCount) return RValue_makeBool(false); + Runner* runner = (Runner*) ctx->runner; + int32_t button = RValue_toInt32(args[0]); + return RValue_makeBool(RunnerMouse_checkButtonPressed(runner->mouse, button)); +} + // Joystick stubs STUB_RETURN_ZERO(joystick_exists) STUB_RETURN_ZERO(joystick_xpos) @@ -6234,6 +6257,10 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "keyboard_key_release", builtinKeyboardKeyRelease); VM_registerBuiltin(ctx, "keyboard_clear", builtinKeyboardClear); + // Mouse + VM_registerBuiltin(ctx, "mouse_check_button", builtinMouseCheckButton); + VM_registerBuiltin(ctx, "mouse_check_button_pressed", builtinMouseCheckButtonPressed); + // Joystick VM_registerBuiltin(ctx, "joystick_exists", builtin_joystick_exists); VM_registerBuiltin(ctx, "joystick_xpos", builtin_joystick_xpos); diff --git a/src/vm_builtins.h b/src/vm_builtins.h index 417e9105..185be566 100644 --- a/src/vm_builtins.h +++ b/src/vm_builtins.h @@ -160,6 +160,10 @@ typedef enum { BUILTIN_VAR_KEYBOARD_LASTCHAR, BUILTIN_VAR_KEYBOARD_LASTKEY, + // Mouse + BUILTIN_VAR_MOUSE_X, + BUILTIN_VAR_MOUSE_Y, + // Surfaces BUILTIN_VAR_APPLICATION_SURFACE, From 01652d03d7d450000e92e1c42ef409f6174eb393 Mon Sep 17 00:00:00 2001 From: SergioFLS Date: Sun, 19 Apr 2026 17:40:58 -0400 Subject: [PATCH 2/4] Add view awareness to cursor position, breaks windowframe though --- src/glfw/main.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/glfw/main.c b/src/glfw/main.c index cb5822db..0001f8b2 100644 --- a/src/glfw/main.c +++ b/src/glfw/main.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -482,10 +483,26 @@ static int32_t glfwMouseButtonToGml(int glfwButton) { static void cursorPositionCallback(GLFWwindow* window, double xpos, double ypos) { Runner* runner = (Runner*) glfwGetWindowUserPointer(window); - int fbWidth, fbHeight; - glfwGetFramebufferSize(window, &fbWidth, &fbHeight); - runner->mouse->mouseX = (xpos/fbWidth) * (runner->dataWin->gen8.defaultWindowWidth / 2.0); - runner->mouse->mouseY = (ypos/fbHeight) * (runner->dataWin->gen8.defaultWindowHeight / 2.0); + int winWidth, winHeight; + glfwGetWindowSize(window, &winWidth, &winHeight); + + int32_t currentView = runner->viewCurrent; + RoomView* view = &runner->currentRoom->views[currentView]; + + int32_t width = runner->currentRoom->width; + int32_t height = runner->currentRoom->height; + int32_t offsetX = 0; + int32_t offsetY = 0; + if (view->enabled) { + // FIXME this statement breaks windowframe for some reason? + width = view->viewWidth; + height = view->viewHeight; + offsetX = view->viewX; + offsetY = view->viewY; + } + + runner->mouse->mouseX = offsetX + (xpos/winWidth) * width; + runner->mouse->mouseY = offsetY + (ypos/winHeight) * height; } static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { From d2eaaee023e7957d457a936c5709aabd85903e13 Mon Sep 17 00:00:00 2001 From: Serg One Zero Date: Mon, 20 Apr 2026 19:23:20 -0400 Subject: [PATCH 3/4] Implement view_surface_id variable for windowframe Co-authored-by: MrPowerGamerBR --- src/runner.c | 3 +++ src/runner.h | 1 + src/vm_builtins.c | 6 ++++++ src/vm_builtins.h | 1 + 4 files changed, 11 insertions(+) diff --git a/src/runner.c b/src/runner.c index 77ed2e63..34993ed4 100644 --- a/src/runner.c +++ b/src/runner.c @@ -1108,6 +1108,9 @@ Runner* Runner_create(DataWin* dataWin, VMContext* vm, Renderer* renderer, FileS runner->osType = OS_WINDOWS; runner->keyboard = RunnerKeyboard_create(); runner->mouse = RunnerMouse_create(); + repeat(8, i) { + runner->viewSurfaceIds[i] = -1; + } Runner_reset(runner); diff --git a/src/runner.h b/src/runner.h index 3ecb2c0f..3c1a97d2 100644 --- a/src/runner.h +++ b/src/runner.h @@ -268,6 +268,7 @@ typedef struct Runner { SavedRoomState* savedRoomStates; // array of size dataWin->room.count, for persistent room support float viewAngles[8]; // runtime-only view_angle per view (not stored in data.win) int32_t viewCurrent; // index of the view currently being drawn (for view_current) + int32_t viewSurfaceIds[8]; // view_surface_id per view, -1 = default (render to screen), else surface index struct { char* key; int value; }* disabledObjects; // stb_ds string hashmap, nullptr = no filtering struct { int key; Instance* value; }* instancesToId; bool forceDrawDepth; diff --git a/src/vm_builtins.c b/src/vm_builtins.c index e35187dc..d1b4ce78 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -168,6 +168,7 @@ int16_t VMBuiltins_resolveBuiltinVarId(const char* name) { if (strcmp(name, "view_object") == 0) return BUILTIN_VAR_VIEW_OBJECT; if (strcmp(name, "view_hspeed") == 0) return BUILTIN_VAR_VIEW_HSPEED; if (strcmp(name, "view_vspeed") == 0) return BUILTIN_VAR_VIEW_VSPEED; + if (strcmp(name, "view_surface_id") == 0) return BUILTIN_VAR_VIEW_SURFACE_ID; // Background properties if (strcmp(name, "background_visible") == 0) return BUILTIN_VAR_BACKGROUND_VISIBLE; @@ -502,6 +503,10 @@ RValue VMBuiltins_getVariable(VMContext* ctx, int16_t builtinVarId, const char* if (arrayIndex >= 0 && MAX_VIEWS > arrayIndex) return RValue_makeReal((GMLReal) runner->currentRoom->views[arrayIndex].speedY); return RValue_makeReal(0.0); } + if (builtinVarId == BUILTIN_VAR_VIEW_SURFACE_ID) { + if (arrayIndex >= 0 && MAX_VIEWS > arrayIndex) return RValue_makeReal((GMLReal) runner->viewSurfaceIds[arrayIndex]); + return RValue_makeReal(-1.0); + } // Background properties if (builtinVarId == BUILTIN_VAR_BACKGROUND_VISIBLE) { @@ -745,6 +750,7 @@ void VMBuiltins_setVariable(VMContext* ctx, int16_t builtinVarId, const char* na if (builtinVarId == BUILTIN_VAR_VIEW_OBJECT) { if (arrayIndex >= 0 && MAX_VIEWS > arrayIndex) { runner->currentRoom->views[arrayIndex].objectId = RValue_toInt32(val); } return; } if (builtinVarId == BUILTIN_VAR_VIEW_HSPEED) { if (arrayIndex >= 0 && MAX_VIEWS > arrayIndex) { runner->currentRoom->views[arrayIndex].speedX = RValue_toInt32(val); } return; } if (builtinVarId == BUILTIN_VAR_VIEW_VSPEED) { if (arrayIndex >= 0 && MAX_VIEWS > arrayIndex) { runner->currentRoom->views[arrayIndex].speedY = RValue_toInt32(val); } return; } + if (builtinVarId == BUILTIN_VAR_VIEW_SURFACE_ID) { if (arrayIndex >= 0 && MAX_VIEWS > arrayIndex) { runner->viewSurfaceIds[arrayIndex] = RValue_toInt32(val); } return; } // Background properties if (builtinVarId == BUILTIN_VAR_BACKGROUND_VISIBLE) { if (arrayIndex >= 0 && MAX_BACKGROUNDS > arrayIndex) runner->backgrounds[arrayIndex].visible = RValue_toBool(val); return; } diff --git a/src/vm_builtins.h b/src/vm_builtins.h index 185be566..e287af2f 100644 --- a/src/vm_builtins.h +++ b/src/vm_builtins.h @@ -81,6 +81,7 @@ typedef enum { BUILTIN_VAR_VIEW_OBJECT, BUILTIN_VAR_VIEW_HSPEED, BUILTIN_VAR_VIEW_VSPEED, + BUILTIN_VAR_VIEW_SURFACE_ID, // Background properties BUILTIN_VAR_BACKGROUND_VISIBLE, From 142fdf0be142295d585313ac7bb90328d88d355b Mon Sep 17 00:00:00 2001 From: Serg One Zero Date: Mon, 20 Apr 2026 20:06:50 -0400 Subject: [PATCH 4/4] Make cursorPositionCallback view and surface aware Co-authored-by: MrPowerGamerBR --- src/glfw/main.c | 59 ++++++++++++++++++++++++++++++++++++------------- src/runner.h | 2 ++ 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/glfw/main.c b/src/glfw/main.c index b6c8c2a5..3d1810a7 100644 --- a/src/glfw/main.c +++ b/src/glfw/main.c @@ -504,23 +504,49 @@ static void cursorPositionCallback(GLFWwindow* window, double xpos, double ypos) int winWidth, winHeight; glfwGetWindowSize(window, &winWidth, &winHeight); - int32_t currentView = runner->viewCurrent; - RoomView* view = &runner->currentRoom->views[currentView]; - - int32_t width = runner->currentRoom->width; - int32_t height = runner->currentRoom->height; - int32_t offsetX = 0; - int32_t offsetY = 0; - if (view->enabled) { - // FIXME this statement breaks windowframe for some reason? - width = view->viewWidth; - height = view->viewHeight; - offsetX = view->viewX; - offsetY = view->viewY; + if (winWidth <= 0 || winHeight <= 0 || runner->currentRoom == nullptr) return; + + // Map window pixel -> FBO pixel. The FBO is blit-stretched to fill the window. + int32_t gameW = runner->renderGameW > 0 ? runner->renderGameW : runner->currentRoom->width; + int32_t gameH = runner->renderGameH > 0 ? runner->renderGameH : runner->currentRoom->height; + double fboX = (xpos / winWidth) * gameW; + double fboY = (ypos / winHeight) * gameH; + + // Find the view whose port rect contains the cursor; fall back to the first enabled view, then to a default (0,0,roomW,roomH) mapping when no views are enabled. + // Native runner rule (GR_Window_Views_Convert): count enabled views that render directly to screen (view_surface_id == -1). + // If any exist, map via the one whose port contains the cursor (or fall through to the last one tried). + // If ALL enabled views have a surface bound, use room-space mapping scaled by the window, since the game is manually compositing those surfaces onto the window. + bool viewsEnabled = (runner->currentRoom->flags & 1) != 0; + int32_t screenViewCount = 0; + RoomView* pickedView = nullptr; + RoomView* lastScreenView = nullptr; + if (viewsEnabled) { + repeat(8, vi) { + RoomView* v = &runner->currentRoom->views[vi]; + if (!v->enabled || runner->viewSurfaceIds[vi] != -1) continue; + screenViewCount++; + lastScreenView = v; + if (fboX >= v->portX && fboX < v->portX + v->portWidth && fboY >= v->portY && fboY < v->portY + v->portHeight) { + pickedView = v; + break; + } + } + if (pickedView == nullptr) pickedView = lastScreenView; } - runner->mouse->mouseX = offsetX + (xpos/winWidth) * width; - runner->mouse->mouseY = offsetY + (ypos/winHeight) * height; + if (pickedView != nullptr && pickedView->portWidth > 0 && pickedView->portHeight > 0) { + runner->mouse->mouseX = pickedView->viewX + (fboX - pickedView->portX) * ((double) pickedView->viewWidth / pickedView->portWidth); + runner->mouse->mouseY = pickedView->viewY + (fboY - pickedView->portY) * ((double) pickedView->viewHeight / pickedView->portHeight); + } else if (viewsEnabled && screenViewCount == 0) { + // No enabled view renders to screen (all redirect to surfaces). Mouse is in room space. + int32_t roomW = runner->currentRoom->width; + int32_t roomH = runner->currentRoom->height; + runner->mouse->mouseX = (xpos / winWidth) * roomW; + runner->mouse->mouseY = (ypos / winHeight) * roomH; + } else { + runner->mouse->mouseX = fboX; + runner->mouse->mouseY = fboY; + } } static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { @@ -968,6 +994,9 @@ int main(int argc, char* argv[]) { } } + runner->renderGameW = gameW; + runner->renderGameH = gameH; + renderer->vtable->beginFrame(renderer, gameW, gameH, fbWidth, fbHeight); // Clear FBO with room background color diff --git a/src/runner.h b/src/runner.h index 3c1a97d2..6b70edee 100644 --- a/src/runner.h +++ b/src/runner.h @@ -268,6 +268,8 @@ typedef struct Runner { SavedRoomState* savedRoomStates; // array of size dataWin->room.count, for persistent room support float viewAngles[8]; // runtime-only view_angle per view (not stored in data.win) int32_t viewCurrent; // index of the view currently being drawn (for view_current) + int32_t renderGameW; // FBO width used by the last frame (= max port bound), 0 if not yet rendered + int32_t renderGameH; // FBO height used by the last frame (= max port bound), 0 if not yet rendered int32_t viewSurfaceIds[8]; // view_surface_id per view, -1 = default (render to screen), else surface index struct { char* key; int value; }* disabledObjects; // stb_ds string hashmap, nullptr = no filtering struct { int key; Instance* value; }* instancesToId;