diff --git a/CMakeLists.txt b/CMakeLists.txt index 924d470fa..5231403c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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) @@ -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) diff --git a/Makefile b/Makefile index ae98734b1..633f05625 100644 --- a/Makefile +++ b/Makefile @@ -38,10 +38,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 diff --git a/src/desktop/main.c b/src/desktop/main.c index 085830abc..4667013b3 100644 --- a/src/desktop/main.c +++ b/src/desktop/main.c @@ -13,6 +13,10 @@ #ifdef _WIN32 #include #include +#include +#endif +#ifdef __APPLE__ +#include #endif #ifdef __GLIBC__ #include @@ -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 #endif @@ -75,7 +80,7 @@ const GLuint *hostFramebuffer; #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; @@ -103,6 +108,32 @@ static size_t get_used_memory(void) { if (*p) p++; } +#elif defined(__APPLE__) + task_basic_info_data_t info; + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &count) == KERN_SUCCESS) { + return info.resident_size; + } +#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; } @@ -1413,6 +1444,7 @@ int main(int argc, char* argv[]) { #endif } } + Overlay_init(args.debug ? OVERLAY_STATS_ENABLED : OVERLAY_STATS_DISABLED); runner->debugMode = args.debug; runner->osType = args.osType; runner->setWindowSize = platformSetWindowSize; @@ -1469,6 +1501,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(); @@ -1576,6 +1609,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; @@ -1756,6 +1796,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); @@ -1836,6 +1892,7 @@ int main(int argc, char* argv[]) { platformInitialized = false; } + Overlay_deinit(); Runner_free(runner); OverlayFileSystem_destroy(overlayFs); #ifdef ENABLE_VM_OPCODE_PROFILER diff --git a/src/desktop/overlay.c b/src/desktop/overlay.c new file mode 100644 index 000000000..2cbaca4c7 --- /dev/null +++ b/src/desktop/overlay.c @@ -0,0 +1,125 @@ +#include "overlay.h" + +#include "profiler.h" +#include "utils.h" + +#include "stb_ds.h" +#include +#include +#include +#include + +#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; + OverlayState state; + int profilerFramesInWindow; +#ifdef ENABLE_VM_GML_PROFILER + char profilerOverlayText[4096]; +#endif +} Overlay; + +static Overlay gOverlay = { 0 }; + +void Overlay_init(OverlayState initialState) { + if (gOverlay.initialized) return; + 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 = runner->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); +} diff --git a/src/desktop/overlay.h b/src/desktop/overlay.h new file mode 100644 index 000000000..99be06a00 --- /dev/null +++ b/src/desktop/overlay.h @@ -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(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 diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 170aba63c..69dfc9c22 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -1,6 +1,7 @@ #include "gl_renderer.h" #include "matrix_math.h" #include "text_utils.h" +#include "debug_font/debug_font.h" #if defined(__EMSCRIPTEN__) || defined(__ANDROID__) #include @@ -602,6 +603,7 @@ static void glDestroy(Renderer* renderer) { GLRenderer* gl = (GLRenderer*) renderer; glDeleteTextures(1, &gl->whiteTexture); + if (gl->debugFontTexture != 0) glDeleteTextures(1, &gl->debugFontTexture); repeat(gl->gmlShaderCount, i) { freeShader(&gl->gmlShaders[i]); @@ -1671,6 +1673,15 @@ static bool glResolveGlyph(GLRenderer* gl, DataWin* dw, GlFontState* state, Font return true; } +static void debugFontDrawModern(void* user, GLuint texture, + float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, + float u0, float v0, float u1, float v1, + uint8_t r, uint8_t g, uint8_t b, float alpha) +{ + GLRenderer* gl = (GLRenderer*) user; + emitTexturedQuad(gl, texture, x0, y0, x1, y1, x2, y2, x3, y3, u0, v0, u1, v1, r, g, b, r, g, b, r, g, b, r, g, b, alpha); +} + static void drawText( Renderer* renderer, const char* text, @@ -1684,9 +1695,20 @@ static void drawText( uint32_t _c2, uint32_t _c3, uint32_t _c4, - float alpha + float alpha, + bool debugFont ) { GLRenderer* gl = (GLRenderer*) renderer; + + if (debugFont) { + GLCommon_ensureDebugFontTexture(&gl->debugFontTexture); + uint8_t cr = (uint8_t) BGR_R(_c1); + uint8_t cg = (uint8_t) BGR_G(_c1); + uint8_t cb = (uint8_t) BGR_B(_c1); + GLCommon_drawDebugFontText(gl->debugFontTexture, text, x, y, xscale, yscale, angleDeg, cr, cg, cb, alpha, gl, debugFontDrawModern); + return; + } + DataWin* dw = renderer->dataWin; int32_t fontIndex = renderer->drawFont; @@ -1842,7 +1864,7 @@ static void drawText( } } -static void glDrawText(Renderer* renderer, const char* text, float x, float y, float xscale, float yscale, float angleDeg, float lineSeparation) { +static void glDrawText(Renderer* renderer, const char* text, float x, float y, float xscale, float yscale, float angleDeg, float lineSeparation, bool debugFont) { drawText( renderer, text, @@ -1856,7 +1878,8 @@ static void glDrawText(Renderer* renderer, const char* text, float x, float y, f renderer->drawColor, renderer->drawColor, renderer->drawColor, - renderer->drawAlpha + renderer->drawAlpha, + debugFont ); } @@ -1874,7 +1897,8 @@ static void glDrawTextColor(Renderer* renderer, const char* text, float x, float _c2, _c3, _c4, - alpha + alpha, + false ); } diff --git a/src/gl/gl_renderer.h b/src/gl/gl_renderer.h index 6ca2e97b7..9df6da59f 100644 --- a/src/gl/gl_renderer.h +++ b/src/gl/gl_renderer.h @@ -64,6 +64,7 @@ typedef struct { uint32_t textureCount; GLuint whiteTexture; // 1x1 white pixel for drawing primitives (rectangles, lines, etc.) + GLuint debugFontTexture; // texture for debug font atlas int32_t windowW; // stored from beginFrame for endFrame blit int32_t windowH; diff --git a/src/gl_common/gl_common.c b/src/gl_common/gl_common.c index 4819b35bd..b1e48cc04 100644 --- a/src/gl_common/gl_common.c +++ b/src/gl_common/gl_common.c @@ -3,10 +3,13 @@ #include #include #include +#include #include "runner.h" #include "utils.h" #include "renderer.h" // for bm_* constants +#include "matrix_math.h" +#include "debug_font.h" // ===[ Letterbox blit ]=== @@ -216,3 +219,70 @@ GLenum GLCommon_blendModeToDFactor(int mode) { case bm_max: return GL_ONE_MINUS_SRC_COLOR; } } + +// ===[ Debug font helpers ]=== + +const DebugFontGlyphEntry* GLCommon_lookupDebugFontGlyph(uint8_t c) { + if (DEBUGFONT_FIRST_CP > c || c > DEBUGFONT_LAST_CP) return NULL; + return &debugFontGlyphs[c - DEBUGFONT_FIRST_CP]; +} + +void GLCommon_ensureDebugFontTexture(GLuint* outTexture) { + if (*outTexture != 0) return; + glGenTextures(1, outTexture); + glBindTexture(GL_TEXTURE_2D, *outTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + uint8_t* rgba = (uint8_t*) safeMalloc((size_t) DEBUGFONT_ATLAS_W * DEBUGFONT_ATLAS_H * 4); + for (int i = 0; i < DEBUGFONT_ATLAS_W * DEBUGFONT_ATLAS_H; i++) { + uint8_t v = debugFontPixels[i]; + rgba[i * 4 + 0] = 0xFF; + rgba[i * 4 + 1] = 0xFF; + rgba[i * 4 + 2] = 0xFF; + rgba[i * 4 + 3] = v; + } + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, DEBUGFONT_ATLAS_W, DEBUGFONT_ATLAS_H, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); + free(rgba); +} + +void GLCommon_drawDebugFontText(GLuint texture, const char* text, float x, float y, float xscale, float yscale, float angleDeg, uint8_t cr, uint8_t cg, uint8_t cb, float alpha, void* user, DebugFontDrawFn drawFn) { + if (text == NULL) return; + int32_t len = (int32_t) strlen(text); + float angleRad = -angleDeg * ((float) M_PI / 180.0f); + Matrix4f transform; + Matrix4f_setTransform2D(&transform, x, y, xscale, yscale, angleRad); + float cursorY = 0; + int32_t lineStart = 0; + + for (int32_t i = 0; len >= i; i++) { + if (i == len || text[i] == '\n') { + int32_t lineLen = i - lineStart; + float pen = 0; + for (int32_t j = 0; j < lineLen; j++) { + const DebugFontGlyphEntry* glyph = GLCommon_lookupDebugFontGlyph((uint8_t) text[lineStart + j]); + if (glyph == NULL) continue; + if (glyph->w > 0 && glyph->h > 0) { + float localX0 = pen + (float) glyph->xoffset; + float localY0 = cursorY + (float) glyph->yoffset; + float localX1 = localX0 + (float) glyph->w; + float localY1 = localY0 + (float) glyph->h; + float u0 = (float) glyph->x / (float) DEBUGFONT_ATLAS_W; + float v0 = (float) glyph->y / (float) DEBUGFONT_ATLAS_H; + float u1 = (float) (glyph->x + glyph->w) / (float) DEBUGFONT_ATLAS_W; + float v1 = (float) (glyph->y + glyph->h) / (float) DEBUGFONT_ATLAS_H; + float sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3; + Matrix4f_transformPoint(&transform, localX0, localY0, &sx0, &sy0); + Matrix4f_transformPoint(&transform, localX1, localY0, &sx1, &sy1); + Matrix4f_transformPoint(&transform, localX1, localY1, &sx2, &sy2); + Matrix4f_transformPoint(&transform, localX0, localY1, &sx3, &sy3); + drawFn(user, texture, sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3, u0, v0, u1, v1, cr, cg, cb, alpha); + } + pen += (float) glyph->xadvance; + } + cursorY += (float) DEBUGFONT_LINE_HEIGHT; + lineStart = i + 1; + } + } +} diff --git a/src/gl_common/gl_common.h b/src/gl_common/gl_common.h index 5a66b86f9..de3be6da8 100644 --- a/src/gl_common/gl_common.h +++ b/src/gl_common/gl_common.h @@ -58,6 +58,8 @@ GLenum GLCommon_blendModeToSFactor(int mode); // Maps a bm_* mode constant to its conventional destination blend factor. GLenum GLCommon_blendModeToDFactor(int mode); +#include "debug_font.h" + #ifndef PLATFORM_PS3 // ===[ GL version queries ]=== @@ -73,4 +75,19 @@ GLVer GLCommon_getGLVersion(void); #endif +// ===[ Debug font helpers ]=== + +const DebugFontGlyphEntry* GLCommon_lookupDebugFontGlyph(uint8_t c); + +// Creates a GL texture from the debug font atlas if *outTexture is 0. Idempotent. +void GLCommon_ensureDebugFontTexture(GLuint* outTexture); + +// Callback for GLCommon_drawDebugFontText: draws a single quad. +// The four (x,y) pairs are screen-space corners (TL, TR, BR, BL) after transform. +typedef void (*DebugFontDrawFn)(void* user, GLuint texture, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float u0, float v0, float u1, float v1, uint8_t r, uint8_t g, uint8_t b, float alpha); + +// Iterates the debug-font text, computes glyph positions, transforms them, +// and calls drawFn for each visible glyph. +void GLCommon_drawDebugFontText(GLuint texture, const char* text, float x, float y, float xscale, float yscale, float angleDeg, uint8_t cr, uint8_t cg, uint8_t cb, float alpha, void* user, DebugFontDrawFn drawFn); + #endif /* _BS_GL_COMMON_H_ */ diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index baf6f9211..2345370e4 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -2,6 +2,7 @@ #include "matrix_math.h" #include "text_utils.h" #include "gl_wrappers.h" +#include "debug_font.h" #ifdef PLATFORM_PS3 @@ -217,6 +218,7 @@ static void glDestroy(Renderer* renderer) { GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; glDeleteTextures(1, &gl->whiteTexture); + if (gl->debugFontTexture != 0) glDeleteTextures(1, &gl->debugFontTexture); glDeleteTextures((GLsizei) gl->textureCount, gl->glTextures); @@ -1037,8 +1039,43 @@ static bool glResolveGlyph(GLLegacyRenderer* gl, DataWin* dw, GlFontState* state return true; } -static void glDrawText(Renderer* renderer, const char* text, float x, float y, float xscale, float yscale, float angleDeg, float lineSeparation) { +static void debugFontDrawLegacy(void* user, GLuint texture, + float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, + float u0, float v0, float u1, float v1, + uint8_t r, uint8_t g, uint8_t b, float alpha) +{ + (void)user; + glBindTexture(GL_TEXTURE_2D, texture); + glBegin(GL_QUADS); + glColor4ub(r, g, b, (uint8_t)(alpha * 255.0f)); + glTexCoord2f(u0, v0); + glVertex2f(x0, y0); + glColor4ub(r, g, b, (uint8_t)(alpha * 255.0f)); + glTexCoord2f(u1, v0); + glVertex2f(x1, y1); + glColor4ub(r, g, b, (uint8_t)(alpha * 255.0f)); + glTexCoord2f(u1, v1); + glVertex2f(x2, y2); + glColor4ub(r, g, b, (uint8_t)(alpha * 255.0f)); + glTexCoord2f(u0, v1); + glVertex2f(x3, y3); + glEnd(); +} + +static void glDrawText(Renderer* renderer, const char* text, float x, float y, float xscale, float yscale, float angleDeg, float lineSeparation, bool debugFont) { GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; + + if (debugFont) { + GLCommon_ensureDebugFontTexture(&gl->debugFontTexture); + uint32_t color = renderer->drawColor; + float dfAlpha = renderer->drawAlpha; + uint8_t cr = (uint8_t) BGR_R(color); + uint8_t cg = (uint8_t) BGR_G(color); + uint8_t cb = (uint8_t) BGR_B(color); + GLCommon_drawDebugFontText(gl->debugFontTexture, text, x, y, xscale, yscale, angleDeg, cr, cg, cb, dfAlpha, NULL, debugFontDrawLegacy); + return; + } + DataWin* dw = renderer->dataWin; int32_t fontIndex = renderer->drawFont; @@ -1049,11 +1086,11 @@ static void glDrawText(Renderer* renderer, const char* text, float x, float y, f GlFontState fontState; if (!glResolveFontState(gl, dw, font, &fontState)) return; - uint32_t color = renderer->drawColor; - float alpha = renderer->drawAlpha; - float r = (float) BGR_R(color) / 255.0f; - float g = (float) BGR_G(color) / 255.0f; - float b = (float) BGR_B(color) / 255.0f; + uint32_t legacyColor = renderer->drawColor; + float legacyAlpha = renderer->drawAlpha; + float lr = (float) BGR_R(legacyColor) / 255.0f; + float lg = (float) BGR_G(legacyColor) / 255.0f; + float lb = (float) BGR_B(legacyColor) / 255.0f; int32_t textLen = (int32_t) strlen(text); @@ -1136,19 +1173,19 @@ static void glDrawText(Renderer* renderer, const char* text, float x, float y, f Matrix4f_transformPoint(&transform, localX0, localY1, &px3, &py3); glBegin(GL_QUADS); - glColor4f(r, g, b, alpha); + glColor4f(lr, lg, lb, legacyAlpha); glTexCoord2f(u0, v0); glVertex2f(px0, py0); - glColor4f(r, g, b, alpha); + glColor4f(lr, lg, lb, legacyAlpha); glTexCoord2f(u1, v0); glVertex2f(px1, py1); - glColor4f(r, g, b, alpha); + glColor4f(lr, lg, lb, legacyAlpha); glTexCoord2f(u1, v1); glVertex2f(px2, py2); - glColor4f(r, g, b, alpha); + glColor4f(lr, lg, lb, legacyAlpha); glTexCoord2f(u0, v1); glVertex2f(px3, py3); glEnd(); diff --git a/src/gl_legacy/gl_legacy_renderer.h b/src/gl_legacy/gl_legacy_renderer.h index a423bf4bf..76cff65d5 100644 --- a/src/gl_legacy/gl_legacy_renderer.h +++ b/src/gl_legacy/gl_legacy_renderer.h @@ -23,6 +23,7 @@ typedef struct { uint32_t textureCount; GLuint whiteTexture; // 1x1 white pixel for drawing primitives (rectangles, lines, etc.) + GLuint debugFontTexture; // texture for debug font atlas int32_t windowW; // stored from beginFrame for endFrame blit int32_t windowH; diff --git a/src/ps2/gs_renderer.c b/src/ps2/gs_renderer.c index 75cbbea33..758e58f3b 100644 --- a/src/ps2/gs_renderer.c +++ b/src/ps2/gs_renderer.c @@ -1885,7 +1885,8 @@ static bool gsResolveGlyph(GsRenderer* gs, DataWin* dw, GsFontState* state, Font return true; } -static void gsDrawText(Renderer* renderer, const char* text, float x, float y, float xscale, float yscale, MAYBE_UNUSED float angleDeg, float lineSeparation) { +static void gsDrawText(Renderer* renderer, const char* text, float x, float y, float xscale, float yscale, MAYBE_UNUSED float angleDeg, float lineSeparation, bool debugFont) { + (void) debugFont; GsRenderer* gs = (GsRenderer*) renderer; DataWin* dw = renderer->dataWin; diff --git a/src/renderer.h b/src/renderer.h index 77dac7828..8c7336f26 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -98,7 +98,7 @@ typedef struct { void (*drawLine)(Renderer* renderer, float x1, float y1, float x2, float y2, float width, uint32_t color, float alpha); void (*drawTriangle)(Renderer *renderer, float x1, float y1, float x2, float y2, float x3, float y3, uint32_t color1, uint32_t color2, uint32_t color3, float alpha, bool outline); void (*drawLineColor)(Renderer* renderer, float x1, float y1, float x2, float y2, float width, uint32_t color1, uint32_t color2, float alpha); - void (*drawText)(Renderer* renderer, const char* text, float x, float y, float xscale, float yscale, float angleDeg, float lineSeparation); + void (*drawText)(Renderer* renderer, const char* text, float x, float y, float xscale, float yscale, float angleDeg, float lineSeparation, bool debugFont); void (*drawTextColor)(Renderer* renderer, const char* text, float x, float y, float xscale, float yscale, float angleDeg, int32_t c1, int32_t c2, int32_t c3, int32_t c4, float alpha, float lineSeparation); void (*flush)(Renderer* renderer); void (*clearScreen)(Renderer* renderer, uint32_t color, float alpha); diff --git a/src/vm_builtins.c b/src/vm_builtins.c index ad61091e4..5632a4500 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -9843,7 +9843,7 @@ static RValue builtin_draw_text(VMContext* ctx, RValue* args, MAYBE_UNUSED int32 char* str = RValue_toString(args[2]); PreprocessedText processedText = TextUtils_preprocessGmlTextIfNeeded(runner, str); - runner->renderer->vtable->drawText(runner->renderer, processedText.text, x, y, 1.0f, 1.0f, 0.0f, -1.0f); + runner->renderer->vtable->drawText(runner->renderer, processedText.text, x, y, 1.0f, 1.0f, 0.0f, -1.0f, false); PreprocessedText_free(processedText); free(str); return RValue_makeUndefined(); @@ -9861,7 +9861,7 @@ static RValue builtin_draw_text_transformed(VMContext* ctx, RValue* args, MAYBE_ float angle = (float) RValue_toReal(args[5]); PreprocessedText processedText = TextUtils_preprocessGmlTextIfNeeded(runner, str); - runner->renderer->vtable->drawText(runner->renderer, processedText.text, x, y, xscale, yscale, angle, -1.0f); + runner->renderer->vtable->drawText(runner->renderer, processedText.text, x, y, xscale, yscale, angle, -1.0f, false); PreprocessedText_free(processedText); free(str); return RValue_makeUndefined(); @@ -9878,7 +9878,7 @@ static void drawTextExtCommonColor(Runner* runner, const char* str, float x, flo PreprocessedText wrappedText = TextUtils_wrapText(font, processedText.text, width); if (c1 == c2 && c2 == c3 && c3 == c4 && c4 == runner->renderer->drawColor) { // using the ordinary drawText is safe - runner->renderer->vtable->drawText(runner->renderer, wrappedText.text, x, y, xscale, yscale, angle, (float) separation); + runner->renderer->vtable->drawText(runner->renderer, wrappedText.text, x, y, xscale, yscale, angle, (float) separation, false); } else { runner->renderer->vtable->drawTextColor(runner->renderer, wrappedText.text, x, y, xscale, yscale, angle, c1, c2, c3, c4, 1.0f, (float) separation); } @@ -12277,7 +12277,7 @@ static void drawLegacyDndCaptionedCounter(VMContext* ctx, RValue* args, int32_t memcpy(combined + captionLen, numBuf, numLen + 1); PreprocessedText processedText = TextUtils_preprocessGmlTextIfNeeded(runner, combined); - runner->renderer->vtable->drawText(runner->renderer, processedText.text, x, y, 1.0f, 1.0f, 0.0f, -1.0f); + runner->renderer->vtable->drawText(runner->renderer, processedText.text, x, y, 1.0f, 1.0f, 0.0f, -1.0f, false); PreprocessedText_free(processedText); free(combined); free(caption); @@ -12625,7 +12625,7 @@ static RValue builtin_action_draw_text(VMContext* ctx, RValue* args, MAYBE_UNUSE applyActionRelativeOffset(ctx, &x, &y); PreprocessedText processedText = TextUtils_preprocessGmlTextIfNeeded(runner, str); - runner->renderer->vtable->drawText(runner->renderer, processedText.text, x, y, 1.0f, 1.0f, 0.0f, -1.0f); + runner->renderer->vtable->drawText(runner->renderer, processedText.text, x, y, 1.0f, 1.0f, 0.0f, -1.0f, false); PreprocessedText_free(processedText); free(str); return RValue_makeUndefined(); @@ -12662,7 +12662,7 @@ static RValue builtin_action_draw_variable(VMContext* ctx, RValue* args, MAYBE_U applyActionRelativeOffset(ctx, &x, &y); PreprocessedText processedText = TextUtils_preprocessGmlTextIfNeeded(runner, str); - runner->renderer->vtable->drawText(runner->renderer, processedText.text, x, y, 1.0f, 1.0f, 0.0f, -1.0f); + runner->renderer->vtable->drawText(runner->renderer, processedText.text, x, y, 1.0f, 1.0f, 0.0f, -1.0f, false); PreprocessedText_free(processedText); free(str); return RValue_makeUndefined();