Skip to content
13 changes: 10 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,13 @@ if(PLATFORM STREQUAL "desktop")
if(ENABLE_MODERN_GL)
file(GLOB GL_SOURCES ${GL_SOURCES} src/gl/*.c)
endif()
target_sources(butterscotch PRIVATE ${GL_SOURCES})
file(GLOB DEBUG_FONT_SOURCES src/debug_font/*.c)
target_sources(butterscotch PRIVATE ${GL_SOURCES} ${DEBUG_FONT_SOURCES})
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/debug_font)
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl)
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_common)
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/image)

if (MSVC)
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/compat/getopt)
add_compile_definitions(NO_STRTOK_R)
Expand Down Expand Up @@ -326,7 +329,9 @@ elseif(PLATFORM STREQUAL "web")
message(FATAL_ERROR "Web requires modern gl!")
endif()
file(GLOB GL_SOURCES src/gl/*.c src/gl_common/*.c src/image/*.c)
target_sources(butterscotch PRIVATE ${GL_SOURCES})
file(GLOB DEBUG_FONT_SOURCES src/debug_font/*.c)
target_sources(butterscotch PRIVATE ${GL_SOURCES} ${DEBUG_FONT_SOURCES})
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/debug_font)
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl)
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_common)
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/image)
Expand Down Expand Up @@ -384,7 +389,9 @@ elseif(PLATFORM STREQUAL "android")
message(FATAL_ERROR "Android requires modern gl!")
endif()
file(GLOB GL_SOURCES src/gl/*.c src/gl_common/*.c src/image/*.c)
target_sources(butterscotch PRIVATE ${GL_SOURCES})
file(GLOB DEBUG_FONT_SOURCES src/debug_font/*.c)
target_sources(butterscotch PRIVATE ${GL_SOURCES} ${DEBUG_FONT_SOURCES})
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/debug_font)
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl)
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_common)
target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/image)
Expand Down
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ INCLUDES += $(INCLUDE). \
$(INCLUDE)vendor/md5 \
$(INCLUDE)vendor/sha1 \
$(INCLUDE)vendor/base64 \
$(INCLUDE)vendor/bzip2
$(INCLUDE)vendor/bzip2 \
$(INCLUDE)src/debug_font

HEADERS := $(wildcard src/*.h) $(shell find vendor -name '*.h')
SRCS := $(wildcard src/*.c) $(wildcard src/image/*.c) $(wildcard vendor/bzip2/*.c) vendor/md5/md5.c vendor/sha1/sha1.c vendor/base64/base64.c
SRCS := $(wildcard src/*.c) $(wildcard src/debug_font/*.c) $(wildcard src/image/*.c) $(wildcard vendor/bzip2/*.c) vendor/md5/md5.c vendor/sha1/sha1.c vendor/base64/base64.c

DESKTOP_BACKEND := glfw3
AUDIO_BACKEND := miniaudio
Expand Down
66 changes: 61 additions & 5 deletions src/desktop/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#ifdef _WIN32
#include <windows.h>
#include <mmsystem.h>
#include <psapi.h>
#endif
#ifdef __APPLE__
#include <mach/mach.h>
#endif
#ifdef __GLIBC__
#include <malloc.h>
Expand All @@ -27,6 +31,7 @@
#include "runner.h"
#include "input_recording.h"
#include "debug_overlay.h"
#include "overlay.h"
#if defined(ENABLE_LEGACY_GL) || defined(ENABLE_MODERN_GL) || ((defined(USE_GLFW3) || defined(USE_GLFW2)) && defined(ENABLE_SW_RENDERER))
#include <glad/glad.h>
#endif
Expand Down Expand Up @@ -69,13 +74,15 @@ enum GraphicsAPI gfx;
const GLuint *hostFramebuffer;
#endif

#ifdef __linux__
#if defined(__linux__)
#include <fcntl.h>
#include <unistd.h>
#elif defined(__APPLE__)
#include <malloc/malloc.h>
#endif

static size_t get_used_memory(void) {
#ifdef __linux__
#if defined(__linux__)
int fd = open("/proc/self/smaps_rollup", O_RDONLY);
if (fd < 0)
return 0;
Expand Down Expand Up @@ -103,6 +110,30 @@ static size_t get_used_memory(void) {
if (*p)
p++;
}
#elif defined(__APPLE__)
malloc_statistics_t stats;
malloc_zone_statistics(malloc_default_zone(), &stats);
return stats.size_allocated;
#elif defined(_WIN32)
typedef BOOL (WINAPI *GetProcessMemoryInfo_t)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD);
static GetProcessMemoryInfo_t func = NULL;
static bool initialized = false;

if (!initialized) {
initialized = true;
HMODULE dll = LoadLibrary("psapi.dll");
if (dll) {
FARPROC p = GetProcAddress(dll, "GetProcessMemoryInfo");
memcpy(&func, &p, sizeof(func));
}
}

if (func) {
PROCESS_MEMORY_COUNTERS pmc;
pmc.cb = sizeof(pmc);
if (func(GetCurrentProcess(), &pmc, sizeof(pmc)))
return pmc.WorkingSetSize;
}
#endif
return 0;
}
Expand Down Expand Up @@ -1416,6 +1447,7 @@ int main(int argc, char* argv[]) {
#endif
}
}
Overlay_init(renderer, args.debug ? OVERLAY_STATS_ENABLED : OVERLAY_STATS_DISABLED);
runner->debugMode = args.debug;
runner->osType = args.osType;
runner->setWindowSize = platformSetWindowSize;
Expand Down Expand Up @@ -1472,6 +1504,7 @@ int main(int argc, char* argv[]) {
// Main loop
bool debugPaused = false;
bool debugShowCollisionMasks = false;
OverlayState overlayState = OVERLAY_STATS_ENABLED;
bool freeCamActive = false;
bool actuallyShuttingDown = false;
uint64_t lastFrameTime = nowNanos();
Expand Down Expand Up @@ -1517,11 +1550,10 @@ int main(int argc, char* argv[]) {
if (shouldStep) fprintf(stderr, "Debug: Frame advance (frame %d)\n", runner->frameCount);
}

uint64_t frameStartTime = 0;

if (shouldStep) {
uint64_t frameStartTime = nowNanos();
if (args.traceFrames) {
frameStartTime = nowNanos();
(void)frameStartTime;
fprintf(stderr, "Frame %d (Start)\n", runner->frameCount);
}

Expand Down Expand Up @@ -1579,6 +1611,13 @@ int main(int argc, char* argv[]) {
free(json);
}

// Toggle the debug info overlay
if (RunnerKeyboard_checkPressed(runner->keyboard, VK_F1)) {
Overlay_toggle(runner);
overlayState = Overlay_getState();
fprintf(stderr, "Debug: Stats overlay %s!\n", overlayState == OVERLAY_STATS_DISABLED ? "disabled" : overlayState == OVERLAY_STATS_ENABLED ? "enabled" : "enabled with profiler");
}

// Toggle the collision mask debug overlay
if (RunnerKeyboard_checkPressed(runner->keyboard, VK_F2)) {
debugShowCollisionMasks = !debugShowCollisionMasks;
Expand Down Expand Up @@ -1759,6 +1798,22 @@ int main(int argc, char* argv[]) {
renderer->vtable->endFrameEnd(renderer);
Runner_drawGUI(runner, fbWidth, fbHeight, gameW, gameH);

// Draw the debug overlay if enabled
if (overlayState != OVERLAY_STATS_DISABLED) {
size_t memBytes = get_used_memory();
static uint32_t frameCount = 0;
static uint32_t fps = 0;
static uint64_t then = 0;
++frameCount;

if (lastFrameStartTime - then > 1000000000) {
then = lastFrameStartTime;
fps = frameCount;
frameCount = 0;
}
Overlay_draw(runner, fps, fbWidth, fbHeight, memBytes);
}

#if defined(ENABLE_LEGACY_GL) || defined(ENABLE_MODERN_GL)
// Capture screenshot if this frame matches a requested frame
bool shouldScreenshot = hmget(args.screenshotFrames, runner->frameCount);
Expand Down Expand Up @@ -1839,6 +1894,7 @@ int main(int argc, char* argv[]) {
platformInitialized = false;
}

Overlay_deinit();
Runner_free(runner);
OverlayFileSystem_destroy(overlayFs);
#ifdef ENABLE_VM_OPCODE_PROFILER
Expand Down
127 changes: 127 additions & 0 deletions src/desktop/overlay.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include "overlay.h"

#include "profiler.h"
#include "utils.h"

#include "stb_ds.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define DEBUGFONT_LINE_HEIGHT 43
#define OVERLAY_FONT_SCALE 0.5f
#define PROFILER_SCALE 0.4f
#define PROFILER_WINDOW_FRAMES 60

typedef struct {
bool initialized;
Renderer* renderer;
OverlayState state;
int profilerFramesInWindow;
#ifdef ENABLE_VM_GML_PROFILER
char profilerOverlayText[4096];
#endif
} Overlay;

static Overlay gOverlay = { 0 };

void Overlay_init(Renderer* renderer, OverlayState initialState) {
if (gOverlay.initialized) return;
gOverlay.renderer = renderer;
gOverlay.state = initialState;
gOverlay.profilerFramesInWindow = 0;
gOverlay.initialized = true;
}

void Overlay_deinit(void) {
memset(&gOverlay, 0, sizeof(gOverlay));
}

OverlayState Overlay_getState(void) {
if (!gOverlay.initialized) return OVERLAY_STATS_DISABLED;
return gOverlay.state;
}

void Overlay_toggle(Runner* runner) {
if (!gOverlay.initialized) return;
gOverlay.state = (OverlayState)((gOverlay.state + 1) % OVERLAY_STATS_MAX);

#ifdef ENABLE_VM_GML_PROFILER
Profiler_setEnabled(&runner->vmContext->profiler, gOverlay.state == OVERLAY_STATS_ENABLED_WITH_PROFILER);
gOverlay.profilerFramesInWindow = 0;
gOverlay.profilerOverlayText[0] = '\0';
#endif
}

static void drawOverlayText(Renderer* renderer, float x, float y, float scale, uint32_t color, float alpha, const char* text) {
if (text == NULL) return;
uint32_t prevColor = renderer->drawColor;
float prevAlpha = renderer->drawAlpha;
int32_t prevFont = renderer->drawFont;
renderer->drawColor = color;
renderer->drawAlpha = alpha;
renderer->drawFont = -1;
renderer->vtable->drawText(renderer, text, x, y, scale, scale, 0.0f, -1.0f, true);
renderer->drawColor = prevColor;
renderer->drawAlpha = prevAlpha;
renderer->drawFont = prevFont;
}

void Overlay_draw(Runner* runner, uint32_t fps, int32_t fbWidth, int32_t fbHeight, size_t memBytes) {
if (!gOverlay.initialized) return;
if (gOverlay.state == OVERLAY_STATS_DISABLED) return;

Renderer* renderer = gOverlay.renderer;

const char* roomName = runner->currentRoom != NULL && runner->currentRoom->name != NULL ? runner->currentRoom->name : "?";

char debugText[512];
char memLine[64];
if (memBytes > 0) {
snprintf(memLine, sizeof(memLine), "\nMemory: %.2f MB", (double) memBytes / (1024.0 * 1024.0));
} else {
memLine[0] = '\0';
}
snprintf(debugText, sizeof(debugText),
"Room: %s\nFPS: %u\nInstances: %d\nStructs: %d%s",
roomName, fps,
(int) arrlen(runner->instances), (int) arrlen(runner->structInstances),
memLine
);

bool blendWas = renderer->vtable->gpuGetBlendEnable(renderer);
renderer->vtable->gpuSetBlendEnable(renderer, true);
renderer->vtable->gpuSetBlendMode(renderer, bm_normal);

renderer->vtable->beginGUI(renderer, fbWidth, fbHeight, 0, 0, fbWidth, fbHeight, RENDER_TARGET_HOST_FRAMEBUFFER);

drawOverlayText(renderer, 10.0f, 10.0f, OVERLAY_FONT_SCALE, 0xFFFFFF, 1.0f, debugText);

if (gOverlay.state == OVERLAY_STATS_ENABLED_WITH_PROFILER) {
float profilerY = 10.0f + ((float) DEBUGFONT_LINE_HEIGHT * OVERLAY_FONT_SCALE * 5.0f);

#ifdef ENABLE_VM_GML_PROFILER
gOverlay.profilerFramesInWindow++;
if (gOverlay.profilerFramesInWindow >= PROFILER_WINDOW_FRAMES) {
char* profilerReport = Profiler_createReport(runner->vmContext->profiler, 25, gOverlay.profilerFramesInWindow);
if (profilerReport != NULL) {
snprintf(gOverlay.profilerOverlayText, sizeof(gOverlay.profilerOverlayText), "%s", profilerReport);
free(profilerReport);
}
Profiler_reset(runner->vmContext->profiler);
gOverlay.profilerFramesInWindow = 0;
}
const char* profilerDisplay = gOverlay.profilerOverlayText[0] != '\0' ? gOverlay.profilerOverlayText : "GML Profiler (collecting...)";
drawOverlayText(renderer, 10.0f, profilerY, PROFILER_SCALE, 0xFFFFFF, 1.0f, profilerDisplay);
#else
drawOverlayText(renderer, 10.0f, profilerY, PROFILER_SCALE, 0xFFFFFF, 1.0f, "Butterscotch GML Profiler is disabled on this build :(");
#endif
}

renderer->vtable->flush(renderer);
renderer->vtable->endGUI(renderer);

renderer->vtable->gpuSetBlendMode(renderer, bm_normal);
renderer->vtable->gpuSetBlendEnable(renderer, blendWas);
}
19 changes: 19 additions & 0 deletions src/desktop/overlay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef _BS_OVERLAY_H_
#define _BS_OVERLAY_H_

#include "runner.h"

typedef enum {
OVERLAY_STATS_ENABLED = 0,
OVERLAY_STATS_ENABLED_WITH_PROFILER = 1,
OVERLAY_STATS_DISABLED = 2,
OVERLAY_STATS_MAX
} OverlayState;

void Overlay_init(Renderer* renderer, OverlayState initialState);
void Overlay_deinit(void);
OverlayState Overlay_getState(void);
void Overlay_toggle(Runner* runner);
void Overlay_draw(Runner* runner, uint32_t fps, int32_t fbWidth, int32_t fbHeight, size_t memBytes);

#endif
Loading
Loading