Эх сурвалжийг харах

Add new demo: sdl3_renderer

This is a highly updated demo for libSDL3. It fixes a lot of long
occuring issues and brings some improvements specific to SDL3 API.
The origin and development of this demo is quite long.
The exact track of development is something like this:
`Nuklear/demo/sdl_renderer` -> https://github.com/Immediate-Mode-UI/Nuklear/pull/772 -> https://github.com/Immediate-Mode-UI/Nuklear/pull/779 + https://github.com/Immediate-Mode-UI/Nuklear/pull/825 -> _this_ commit...
You may wish to read the linked PRs in order to get the full context.
Commit message is too small to describe everything. I'm sorry...
sleeptightAnsiC 1 сар өмнө
parent
commit
ec5e8d711e

+ 41 - 0
demo/sdl3_renderer/Makefile

@@ -0,0 +1,41 @@
+# this Makefile is specific to GNU Make and GCC'compatible compilers
+
+PKG_CONFIG ?= $(shell command -v pkg-config)
+ifeq (,$(PKG_CONFIG))
+    $(error missing pkg-config utility!)
+endif
+
+PKG_SDL3 ?= sdl3
+ifeq (,$(shell $(PKG_CONFIG) --path $(PKG_SDL3)))
+    $(error $(PKG_CONFIG) could not find: $(PKG_SDL3))
+endif
+
+OS ?= $(shell uname -s)
+BINEXT-Windows_NT = .exe
+BINEXT ?= $(BINEXIT-$(OS))
+
+TEMPDIR ?= ./bin
+BIN ?= $(TEMPDIR)/demo$(BINEXT)
+
+CFLAGS += -std=c89 -Wall -Wextra -Wpedantic
+CFLAGS += -O2
+#CFLAGS += -O0 -g
+#CFLAGS += -fsanitize=address
+#CFLAGS += -fsanitize=undefined
+CFLAGS += $(shell $(PKG_CONFIG) $(PKG_SDL3) --cflags)
+
+LIBS += -lm
+LIBS += $(shell $(PKG_CONFIG) $(PKG_SDL3) --libs)
+
+DEP ?= $(BIN).d
+
+SRC = main.c
+
+$(BIN):
+	mkdir -p $(dir $@)
+	$(CC) $(SRC) -o $@ -MD -MF $(DEP) $(CFLAGS) $(LIBS)
+
+$(BIN): $(SRC) ./Makefile ./nuklear_sdl3_renderer.h ./../../nuklear.h
+
+-include $(DEP)
+

+ 416 - 0
demo/sdl3_renderer/main.c

@@ -0,0 +1,416 @@
+/* nuklear - public domain */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <limits.h>
+#include <time.h>
+
+#include <SDL3/SDL.h>
+
+/* This demo uses "main callbacks" which are new in SDL3
+ * Those provide highly portable entry point and event loop for the app
+ * see: https://wiki.libsdl.org/SDL3/README-main-functions
+ * */
+#define SDL_MAIN_USE_CALLBACKS
+#include <SDL3/SDL_main.h>
+
+/* ===============================================================
+ *
+ *                          CONFIG
+ *
+ * ===============================================================*/
+
+/* optional: sdl3_renderer does not need any of these defines
+ * (but some examples might need them, so be careful) */
+#define NK_INCLUDE_STANDARD_VARARGS
+#define NK_INCLUDE_STANDARD_IO
+
+/* note that sdl3_renderer comes with nk_sdl_style_set_debug_font()
+ * so you may wish to use that instead of font baking */
+#define NK_INCLUDE_FONT_BAKING
+#define NK_INCLUDE_DEFAULT_FONT
+
+/* note that sdl3_renderer comes with nk_sdl_allocator()
+ * and you probably want to use that allocator instead of the default ones */
+/*#define NK_INCLUDE_DEFAULT_ALLOCATOR*/
+
+/* mandatory: sdl3_renderer depends on those defines */
+#define NK_INCLUDE_COMMAND_USERDATA
+#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+
+
+/* We can re-use the types provided by SDL which are extremely portable,
+ * so there is no need for Nuklear to detect those on its own */
+/*#define NK_INCLUDE_FIXED_TYPES*/
+#ifndef NK_INCLUDE_FIXED_TYPES
+    #define NK_INT8              Sint8
+    #define NK_UINT8             Uint8
+    #define NK_INT16             Sint16
+    #define NK_UINT16            Uint16
+    #define NK_INT32             Sint32
+    #define NK_UINT32            Uint32
+    /* SDL guarantees 'uintptr_t' typedef */
+    #define NK_SIZE_TYPE         uintptr_t
+    #define NK_POINTER_TYPE      uintptr_t
+#endif
+
+/* FIXME: We could also use the `bool` symbol provided by SDL,
+ * but this is currently broken due to internal Nuklear issue, see:
+ * https://github.com/Immediate-Mode-UI/Nuklear/issues/849
+ * */
+#define NK_INCLUDE_STANDARD_BOOL
+#ifndef NK_INCLUDE_STANDARD_BOOL
+    #define NK_BOOL               bool
+#endif
+
+/* We can re-use various portable libc functions provided by SDL */
+#define NK_ASSERT(condition)      SDL_assert(condition)
+#define NK_STATIC_ASSERT(exp)     SDL_COMPILE_TIME_ASSERT(, exp)
+#define NK_MEMSET(dst, c, len)    SDL_memset(dst, c, len)
+#define NK_MEMCPY(dst, src, len)  SDL_memcpy(dst, src, len)
+#define NK_VSNPRINTF(s, n, f, a)  SDL_vsnprintf(s, n, f, a)
+#define NK_STRTOD(str, endptr)    SDL_strtod(str, endptr)
+
+/* sadly, SDL3 does not provide "dtoa" (only integer version) */
+/*#define NK_DTOA (str, d)*/
+
+/* SDL can also provide us with math functions, but beware that Nuklear's own
+ * implementation can be slightly faster at the cost of some precision */
+#define NK_INV_SQRT(f)            (1.0f / SDL_sqrtf(f))
+#define NK_SIN(f)                 SDL_sinf(f)
+#define NK_COS(f)                 SDL_cosf(f)
+
+/* HACK: Nuklear pulls two stb libraries in order to use font baking
+ * those libraries pull in some libc headers internally, creating a linkage dependency,
+ * so you’ll most likely want to use SDL symbols instead */
+#define STBTT_ifloor(x)       ((int)SDL_floor(x))
+#define STBTT_iceil(x)        ((int)SDL_ceil(x))
+#define STBTT_sqrt(x)         SDL_sqrt(x)
+#define STBTT_pow(x,y)        SDL_pow(x,y)
+#define STBTT_fmod(x,y)       SDL_fmod(x,y)
+#define STBTT_cos(x)          SDL_cosf(x)
+#define STBTT_acos(x)         SDL_acos(x)
+#define STBTT_fabs(x)         SDL_fabs(x)
+#define STBTT_assert(x)       SDL_assert(x)
+#define STBTT_strlen(x)       SDL_strlen(x)
+#define STBTT_memcpy          SDL_memcpy
+#define STBTT_memset          SDL_memset
+#define stbtt_uint8           Uint8
+#define stbtt_int8            Sint8
+#define stbtt_uint16          Uint16
+#define stbtt_int16           Sint16
+#define stbtt_uint32          Uint32
+#define stbtt_int32           Sint32
+#define STBRP_SORT            SDL_qsort
+#define STBRP_ASSERT          SDL_assert
+/* There is no need to define STBTT_malloc/STBTT_free macros
+ * Nuklear will define those to user-provided nk_allocator */
+
+
+#define NK_IMPLEMENTATION
+#include "../../nuklear.h"
+#define NK_SDL3_RENDERER_IMPLEMENTATION
+#include "nuklear_sdl3_renderer.h"
+
+#define WINDOW_WIDTH 1200
+#define WINDOW_HEIGHT 800
+
+/* ===============================================================
+ *
+ *                          EXAMPLE
+ *
+ * ===============================================================*/
+/* These are some code examples to provide a small overview of what can be
+ * done with this library. To try out an example uncomment the defines */
+/*#define INCLUDE_ALL */
+/*#define INCLUDE_STYLE */
+/*#define INCLUDE_CALCULATOR */
+/*#define INCLUDE_CANVAS */
+#define INCLUDE_OVERVIEW
+/*#define INCLUDE_CONFIGURATOR */
+/*#define INCLUDE_NODE_EDITOR */
+
+#ifdef INCLUDE_ALL
+    #define INCLUDE_STYLE
+    #define INCLUDE_CALCULATOR
+    #define INCLUDE_CANVAS
+    #define INCLUDE_OVERVIEW
+    #define INCLUDE_CONFIGURATOR
+    #define INCLUDE_NODE_EDITOR
+#endif
+
+#ifdef INCLUDE_STYLE
+    #include "../../demo/common/style.c"
+#endif
+#ifdef INCLUDE_CALCULATOR
+    #include "../../demo/common/calculator.c"
+#endif
+#ifdef INCLUDE_CANVAS
+    #include "../../demo/common/canvas.c"
+#endif
+#ifdef INCLUDE_OVERVIEW
+    #include "../../demo/common/overview.c"
+#endif
+#ifdef INCLUDE_CONFIGURATOR
+    #include "../../demo/common/style_configurator.c"
+#endif
+#ifdef INCLUDE_NODE_EDITOR
+    #include "../../demo/common/node_editor.c"
+#endif
+
+/* ===============================================================
+ *
+ *                          DEMO
+ *
+ * ===============================================================*/
+
+struct nk_sdl_app {
+    SDL_Window* window;
+    SDL_Renderer* renderer;
+    struct nk_context * ctx;
+    struct nk_colorf bg;
+    enum nk_anti_aliasing AA;
+};
+
+static SDL_AppResult
+nk_sdl_fail()
+{
+    SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Error: %s", SDL_GetError());
+    return SDL_APP_FAILURE;
+}
+
+SDL_AppResult
+SDL_AppInit(void** appstate, int argc, char* argv[])
+{
+    struct nk_sdl_app* app;
+    struct nk_context* ctx;
+    float font_scale;
+    NK_UNUSED(argc);
+    NK_UNUSED(argv);
+
+    if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
+        return nk_sdl_fail();
+    }
+
+    app = SDL_malloc(sizeof(*app));
+    if (app == NULL) {
+        return nk_sdl_fail();
+    }
+
+    if (!SDL_CreateWindowAndRenderer("Nuklear: SDL3 Renderer", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &app->window, &app->renderer)) {
+        SDL_free(app);
+        return nk_sdl_fail();
+    }
+    *appstate = app;
+
+    if (!SDL_SetRenderVSync(app->renderer, 1)) {
+        SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "SDL_SetRenderVSync failed: %s", SDL_GetError());
+    }
+
+    app->bg.r = 0.10f;
+    app->bg.g = 0.18f;
+    app->bg.b = 0.24f;
+    app->bg.a = 1.0f;
+
+
+    font_scale = 1;
+    {
+        /* This scaling logic was kept simple for the demo purpose.
+         * On some platforms, this might not be the exact scale
+         * that you want to use. For more information, see:
+         * https://wiki.libsdl.org/SDL3/README-highdpi */
+        const float scale = SDL_GetWindowDisplayScale(app->window);
+        SDL_SetRenderScale(app->renderer, scale, scale);
+        font_scale = scale;
+    }
+
+    ctx = nk_sdl_init(app->window, app->renderer, nk_sdl_allocator());
+    app->ctx = ctx;
+
+#if 0
+    {
+        /* If you don't want to use advanced Nuklear font baking API
+         * you can use simple ASCII debug font provided by SDL
+         * just change the `#if 0` above to `#if 1` */
+        nk_sdl_style_set_debug_font(ctx);
+
+        /* Note that since debug font is extremely small (only 8x8 pixels),
+         * scaling it does not make much sense. The font would appear blurry. */
+        NK_UNUSED(font_scale);
+
+        /* You may wish to change a few style options, here are few recommendations: */
+        ctx->style.button.rounding = 0.0f;
+        ctx->style.menu_button.rounding = 0.0f;
+        ctx->style.property.rounding = 0.0f;
+        ctx->style.property.border = 0.0f;
+        ctx->style.option.border = -1.0f;
+        ctx->style.checkbox.border = -1.0f;
+        ctx->style.property.dec_button.border = -2.0f;
+        ctx->style.property.inc_button.border = -2.0f;
+        ctx->style.tab.tab_minimize_button.border = -2.0f;
+        ctx->style.tab.tab_maximize_button.border = -2.0f;
+        ctx->style.tab.node_minimize_button.border = -2.0f;
+        ctx->style.tab.node_maximize_button.border = -2.0f;
+        ctx->style.checkbox.spacing = 5.0f;
+
+        /* It's better to disable anti-aliasing when using small fonts */
+        app->AA = NK_ANTI_ALIASING_OFF;
+    }
+#else
+    {
+        struct nk_font_atlas *atlas;
+        struct nk_font_config config = nk_font_config(0);
+        struct nk_font *font;
+
+        /* set up the font atlas and add desired font; note that font sizes are
+         * multiplied by font_scale to produce better results at higher DPIs */
+        atlas = nk_sdl_font_stash_begin(ctx);
+        font = nk_font_atlas_add_default(atlas, 13 * font_scale, &config);
+        /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/DroidSans.ttf", 14 * font_scale, &config);*/
+        /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/Roboto-Regular.ttf", 16 * font_scale, &config);*/
+        /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/kenvector_future_thin.ttf", 13 * font_scale, &config);*/
+        /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/ProggyClean.ttf", 12 * font_scale, &config);*/
+        /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/ProggyTiny.ttf", 10 * font_scale, &config);*/
+        /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/Cousine-Regular.ttf", 13 * font_scale, &config);*/
+        nk_sdl_font_stash_end(ctx);
+
+        /* this hack makes the font appear to be scaled down to the desired
+         * size and is only necessary when font_scale > 1 */
+        font->handle.height /= font_scale;
+        /*nk_style_load_all_cursors(ctx, atlas->cursors);*/
+        nk_style_set_font(ctx, &font->handle);
+
+        app->AA = NK_ANTI_ALIASING_ON;
+    }
+#endif
+
+    return SDL_APP_CONTINUE;
+}
+
+SDL_AppResult
+SDL_AppEvent(void *appstate, SDL_Event* event)
+{
+    struct nk_sdl_app* app = (struct nk_sdl_app*)appstate;
+
+    switch (event->type) {
+        case SDL_EVENT_QUIT:
+            return SDL_APP_SUCCESS;
+        case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
+            /* You may wish to rescale the renderer and Nuklear during this event.
+             * Without this the UI and Font could appear too small or too big.
+             * This is not handled by the demo in order to keep it simple,
+             * but you may wish to re-bake the Font whenever this happens. */
+            SDL_Log("Unhandled scale event! Nuklear may appear blurry");
+            return SDL_APP_CONTINUE;
+    }
+
+    /* Remember to always rescale the event coordinates,
+     * if your renderer uses custom scale. */
+    SDL_ConvertEventToRenderCoordinates(app->renderer, event);
+
+    nk_sdl_handle_event(app->ctx, event);
+
+    return SDL_APP_CONTINUE;
+}
+
+SDL_AppResult
+SDL_AppIterate(void *appstate)
+{
+    struct nk_sdl_app* app = (struct nk_sdl_app*)appstate;
+    struct nk_context* ctx = app->ctx;
+
+#ifdef INCLUDE_CONFIGURATOR
+    static struct nk_color color_table[NK_COLOR_COUNT];
+    NK_MEMCPY(color_table, nk_default_color_style, sizeof(color_table));
+#endif
+
+    nk_input_end(ctx);
+
+    /* GUI */
+    if (nk_begin(ctx, "Demo", nk_rect(50, 50, 230, 250),
+        NK_WINDOW_BORDER|NK_WINDOW_MOVABLE|NK_WINDOW_SCALABLE|
+        NK_WINDOW_MINIMIZABLE|NK_WINDOW_TITLE))
+    {
+        enum {EASY, HARD};
+        static int op = EASY;
+        static int property = 20;
+
+        nk_layout_row_static(ctx, 30, 80, 1);
+        if (nk_button_label(ctx, "button")) {
+            SDL_Log("button pressed");
+        }
+        nk_layout_row_dynamic(ctx, 30, 2);
+        if (nk_option_label(ctx, "easy", op == EASY)) op = EASY;
+        if (nk_option_label(ctx, "hard", op == HARD)) op = HARD;
+        nk_layout_row_dynamic(ctx, 25, 1);
+        nk_property_int(ctx, "Compression:", 0, &property, 1000, 1, 1);
+
+        nk_layout_row_dynamic(ctx, 20, 1);
+        nk_label(ctx, "background:", NK_TEXT_LEFT);
+        nk_layout_row_dynamic(ctx, 25, 1);
+        if (nk_combo_begin_color(ctx, nk_rgb_cf(app->bg), nk_vec2(nk_widget_width(ctx),400))) {
+            nk_layout_row_dynamic(ctx, 120, 1);
+            app->bg = nk_color_picker(ctx, app->bg, NK_RGBA);
+            nk_layout_row_dynamic(ctx, 25, 1);
+            app->bg.r = nk_propertyf(ctx, "#R:", 0, app->bg.r, 1.0f, 0.01f,0.005f);
+            app->bg.g = nk_propertyf(ctx, "#G:", 0, app->bg.g, 1.0f, 0.01f,0.005f);
+            app->bg.b = nk_propertyf(ctx, "#B:", 0, app->bg.b, 1.0f, 0.01f,0.005f);
+            app->bg.a = nk_propertyf(ctx, "#A:", 0, app->bg.a, 1.0f, 0.01f,0.005f);
+            nk_combo_end(ctx);
+        }
+    }
+    nk_end(ctx);
+
+    /* -------------- EXAMPLES ---------------- */
+    #ifdef INCLUDE_CALCULATOR
+        calculator(ctx);
+    #endif
+    #ifdef INCLUDE_CANVAS
+        canvas(ctx);
+    #endif
+    #ifdef INCLUDE_OVERVIEW
+        overview(ctx);
+    #endif
+    #ifdef INCLUDE_CONFIGURATOR
+        style_configurator(ctx, color_table);
+    #endif
+    #ifdef INCLUDE_NODE_EDITOR
+        node_editor(ctx);
+    #endif
+    /* ----------------------------------------- */
+
+    SDL_SetRenderDrawColorFloat(app->renderer, app->bg.r, app->bg.g, app->bg.b, app->bg.a);
+    SDL_RenderClear(app->renderer);
+
+    nk_sdl_render(ctx, app->AA);
+    nk_sdl_update_TextInput(ctx);
+
+    /* show if TextInput is active for debug purpose. Feel free to remove this. */
+    SDL_SetRenderDrawColor(app->renderer, 0xFF, 0xFF, 0xFF, 0xFF);
+    SDL_RenderDebugTextFormat(app->renderer, 10, 10, "TextInputActive? %s",
+                              SDL_TextInputActive(app->window) ? "Yes" : "No");
+
+    SDL_RenderPresent(app->renderer);
+
+    nk_input_begin(ctx);
+    return SDL_APP_CONTINUE;
+}
+
+void
+SDL_AppQuit(void* appstate, SDL_AppResult result)
+{
+    struct nk_sdl_app* app = (struct nk_sdl_app*)appstate;
+    NK_UNUSED(result);
+
+    if (app) {
+        nk_sdl_shutdown(app->ctx);
+        SDL_DestroyRenderer(app->renderer);
+        SDL_DestroyWindow(app->window);
+        SDL_free(app);
+    }
+}
+

+ 707 - 0
demo/sdl3_renderer/nuklear_sdl3_renderer.h

@@ -0,0 +1,707 @@
+/* nuklear - public domain */
+
+/*
+ * ==============================================================
+ *
+ *                              API
+ *
+ * ===============================================================
+ */
+
+#ifndef NK_SDL3_RENDERER_H_
+#define NK_SDL3_RENDERER_H_
+
+#if SDL_MAJOR_VERSION < 3
+    #error "nk_sdl3_renderer requires at least SDL 3.0.0"
+#endif
+#ifndef NK_INCLUDE_COMMAND_USERDATA
+    #error "nk_sdl3_renderer requires the NK_INCLUDE_COMMAND_USERDATA define"
+#endif
+#ifndef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+    #error "nk_sdl3_renderer requires the NK_INCLUDE_VERTEX_BUFFER_OUTPUT define"
+#endif
+
+/* We have to redefine it because demos do not include any headers
+ * This is the same default value as the one from "src/nuklear_internal.h" */
+#ifndef NK_BUFFER_DEFAULT_INITIAL_SIZE
+    #define NK_BUFFER_DEFAULT_INITIAL_SIZE (4*1024)
+#endif
+
+NK_API struct nk_context*   nk_sdl_init(SDL_Window *win, SDL_Renderer *renderer, struct nk_allocator allocator);
+#ifdef NK_INCLUDE_FONT_BAKING
+NK_API struct nk_font_atlas* nk_sdl_font_stash_begin(struct nk_context* ctx);
+NK_API void                 nk_sdl_font_stash_end(struct nk_context* ctx);
+#endif
+NK_API int                  nk_sdl_handle_event(struct nk_context* ctx, SDL_Event *evt);
+NK_API void                 nk_sdl_render(struct nk_context* ctx, enum nk_anti_aliasing);
+NK_API void                 nk_sdl_update_TextInput(struct nk_context* ctx);
+NK_API void                 nk_sdl_shutdown(struct nk_context* ctx);
+NK_API nk_handle            nk_sdl_get_userdata(struct nk_context* ctx);
+NK_API void                 nk_sdl_set_userdata(struct nk_context* ctx, nk_handle userdata);
+NK_API void                 nk_sdl_style_set_debug_font(struct nk_context* ctx);
+NK_API struct nk_allocator  nk_sdl_allocator(void);
+
+#endif /* NK_SDL3_RENDERER_H_ */
+
+/*
+ * ==============================================================
+ *
+ *                          IMPLEMENTATION
+ *
+ * ===============================================================
+ */
+#ifdef NK_SDL3_RENDERER_IMPLEMENTATION
+#ifndef NK_SDL3_RENDERER_IMPLEMENTATION_ONCE
+#define NK_SDL3_RENDERER_IMPLEMENTATION_ONCE
+
+#ifndef NK_SDL_DOUBLE_CLICK_LO
+#define NK_SDL_DOUBLE_CLICK_LO 0.02
+#endif
+#ifndef NK_SDL_DOUBLE_CLICK_HI
+#define NK_SDL_DOUBLE_CLICK_HI 0.2
+#endif
+
+struct nk_sdl_device {
+    struct nk_buffer cmds;
+    struct nk_draw_null_texture tex_null;
+    SDL_Texture *font_tex;
+};
+
+struct nk_sdl_vertex {
+    float position[2];
+    float uv[2];
+    float col[4];
+};
+
+struct nk_sdl {
+    SDL_Window *win;
+    SDL_Renderer *renderer;
+    struct nk_user_font* debug_font;
+    struct nk_sdl_device ogl;
+    struct nk_context ctx;
+#ifdef NK_INCLUDE_FONT_BAKING
+    struct nk_font_atlas atlas;
+#endif
+    struct nk_allocator allocator;
+    nk_handle userdata;
+    Uint64 last_left_click;
+    Uint64 last_render;
+    bool insert_toggle;
+    bool edit_was_active;
+};
+
+NK_API nk_handle
+nk_sdl_get_userdata(struct nk_context* ctx) {
+    struct nk_sdl* sdl;
+    NK_ASSERT(ctx);
+    sdl = (struct nk_sdl*)ctx->userdata.ptr;
+    NK_ASSERT(sdl);
+    return sdl->userdata;
+}
+
+NK_API void
+nk_sdl_set_userdata(struct nk_context* ctx, nk_handle userdata) {
+    struct nk_sdl* sdl;
+    NK_ASSERT(ctx);
+    sdl = (struct nk_sdl*)ctx->userdata.ptr;
+    NK_ASSERT(sdl);
+    sdl->userdata = userdata;
+}
+
+NK_INTERN void *
+nk_sdl_alloc(nk_handle user, void *old, nk_size size)
+{
+    NK_UNUSED(user);
+    /* FIXME: nk_sdl_alloc should use SDL_realloc here, not SDL_malloc
+     * but this could cause a double-free due to bug within Nuklear, see:
+     * https://github.com/Immediate-Mode-UI/Nuklear/issues/768
+     * */
+#if 0
+    return SDL_realloc(old, size);
+#else
+    NK_UNUSED(old);
+    return SDL_malloc(size);
+#endif
+}
+
+NK_INTERN void
+nk_sdl_free(nk_handle user, void *old)
+{
+    NK_UNUSED(user);
+    SDL_free(old);
+}
+
+NK_API struct nk_allocator
+nk_sdl_allocator()
+{
+    struct nk_allocator allocator;
+    allocator.userdata.ptr = 0;
+    allocator.alloc = nk_sdl_alloc;
+    allocator.free = nk_sdl_free;
+    return allocator;
+}
+
+NK_INTERN void
+nk_sdl_device_upload_atlas(struct nk_context* ctx, const void *image, int width, int height)
+{
+    struct nk_sdl* sdl;
+    NK_ASSERT(ctx);
+    NK_ASSERT(image);
+    NK_ASSERT(width > 0);
+    NK_ASSERT(height > 0);
+
+    sdl = (struct nk_sdl*)ctx->userdata.ptr;
+    NK_ASSERT(sdl);
+
+    /* Clean up if the texture already exists. */
+    if (sdl->ogl.font_tex != NULL) {
+        SDL_DestroyTexture(sdl->ogl.font_tex);
+        sdl->ogl.font_tex = NULL;
+    }
+
+    sdl->ogl.font_tex = SDL_CreateTexture(sdl->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, width, height);
+    NK_ASSERT(sdl->ogl.font_tex);
+    SDL_UpdateTexture(sdl->ogl.font_tex, NULL, image, 4 * width);
+    SDL_SetTextureBlendMode(sdl->ogl.font_tex, SDL_BLENDMODE_BLEND);
+}
+
+NK_API void
+nk_sdl_update_TextInput(struct nk_context* ctx)
+{
+    struct nk_sdl* sdl;
+    bool active;
+    NK_ASSERT(ctx);
+    sdl = (struct nk_sdl*)ctx->userdata.ptr;
+    NK_ASSERT(sdl);
+
+    /* Determine if Nuklear is using any top-level "edit" widget.
+     * Popups take higher priority because they block any incomming input.
+     * This will not work, if the widget is not updating context state properly. */
+    if (!ctx->active)
+        active = false;
+    else if (ctx->active->popup.win)
+        active = ctx->active->popup.win->edit.active;
+    else
+        active = ctx->active->edit.active;
+
+    /* decide, if TextInputActive should be unchanged/stoped/started
+     * and change its state accordingly for owned SDL Window */
+    if (active != sdl->edit_was_active)
+    {
+        const bool window_edit_active = SDL_TextInputActive(sdl->win);
+
+        /* If you ever hit this check, it means that the demo and your app
+         * (or something else) are all trying to manage TextInputActive state.
+         * This can cause subtle bugs where the state won't be what you expect.
+         * You can safely remove this assert and the demo will keep working,
+         * but make sure it does not cause any issues for you */
+        NK_ASSERT(window_edit_active == sdl->edit_was_active && "something else changed TextInputActive state for this Window");
+
+        if (!window_edit_active && !sdl->edit_was_active && active)
+            SDL_StartTextInput(sdl->win);
+        else if (window_edit_active && sdl->edit_was_active && !active)
+            SDL_StopTextInput(sdl->win);
+        sdl->edit_was_active = active;
+    }
+
+    /* FIXME:
+     * for full SDL3 integration, you also need to find current edit widget
+     * bounds and the text cursor offset, and pass this data into SDL_SetTextInputArea.
+     * This is currently not possible to do safely as Nuklear does not support it.
+     * https://wiki.libsdl.org/SDL3/SDL_SetTextInputArea
+     * https://github.com/Immediate-Mode-UI/Nuklear/pull/857
+     */
+}
+
+NK_API void
+nk_sdl_render(struct nk_context* ctx, enum nk_anti_aliasing AA)
+{
+    /* setup global state */
+    struct nk_sdl* sdl;
+    NK_ASSERT(ctx);
+    sdl = (struct nk_sdl*)ctx->userdata.ptr;
+    NK_ASSERT(sdl);
+
+    { /* setup internal delta time that Nuklear needs for animations */
+        const Uint64 ticks = SDL_GetTicks();
+        ctx->delta_time_seconds = (float)(ticks - sdl->last_render) / 1000.0f;
+        sdl->last_render = ticks;
+    }
+
+    {
+        SDL_Rect saved_clip;
+        bool clipping_enabled;
+        int vs = sizeof(struct nk_sdl_vertex);
+        size_t vp = NK_OFFSETOF(struct nk_sdl_vertex, position);
+        size_t vt = NK_OFFSETOF(struct nk_sdl_vertex, uv);
+        size_t vc = NK_OFFSETOF(struct nk_sdl_vertex, col);
+
+        /* convert from command queue into draw list and draw to screen */
+        const struct nk_draw_command *cmd;
+        const nk_draw_index *offset = NULL;
+        struct nk_buffer vbuf, ebuf;
+
+        /* fill converting configuration */
+        struct nk_convert_config config;
+        static const struct nk_draw_vertex_layout_element vertex_layout[] = {
+            {NK_VERTEX_POSITION,    NK_FORMAT_FLOAT,                NK_OFFSETOF(struct nk_sdl_vertex, position)},
+            {NK_VERTEX_TEXCOORD,    NK_FORMAT_FLOAT,                NK_OFFSETOF(struct nk_sdl_vertex, uv)},
+            {NK_VERTEX_COLOR,       NK_FORMAT_R32G32B32A32_FLOAT,   NK_OFFSETOF(struct nk_sdl_vertex, col)},
+            {NK_VERTEX_LAYOUT_END}
+        };
+        NK_MEMSET(&config, 0, sizeof(config));
+        config.vertex_layout = vertex_layout;
+        config.vertex_size = sizeof(struct nk_sdl_vertex);
+        config.vertex_alignment = NK_ALIGNOF(struct nk_sdl_vertex);
+        config.tex_null = sdl->ogl.tex_null;
+        config.circle_segment_count = 22;
+        config.curve_segment_count = 22;
+        config.arc_segment_count = 22;
+        config.global_alpha = 1.0f;
+        config.shape_AA = AA;
+        config.line_AA = AA;
+
+        /* convert shapes into vertexes */
+        nk_buffer_init(&vbuf, &sdl->allocator, NK_BUFFER_DEFAULT_INITIAL_SIZE);
+        nk_buffer_init(&ebuf, &sdl->allocator, NK_BUFFER_DEFAULT_INITIAL_SIZE);
+        nk_convert(&sdl->ctx, &sdl->ogl.cmds, &vbuf, &ebuf, &config);
+
+        /* iterate over and execute each draw command */
+        offset = (const nk_draw_index*)nk_buffer_memory_const(&ebuf);
+
+        clipping_enabled = SDL_RenderClipEnabled(sdl->renderer);
+        SDL_GetRenderClipRect(sdl->renderer, &saved_clip);
+
+        nk_draw_foreach(cmd, &sdl->ctx, &sdl->ogl.cmds)
+        {
+            if (!cmd->elem_count) continue;
+
+            {
+                SDL_Rect r;
+                r.x = cmd->clip_rect.x;
+                r.y = cmd->clip_rect.y;
+                r.w = cmd->clip_rect.w;
+                r.h = cmd->clip_rect.h;
+                SDL_SetRenderClipRect(sdl->renderer, &r);
+            }
+
+            {
+                const void *vertices = nk_buffer_memory_const(&vbuf);
+
+                SDL_RenderGeometryRaw(
+                        sdl->renderer,
+                        (SDL_Texture *)cmd->texture.ptr,
+                        (const float*)((const nk_byte*)vertices + vp), vs,
+                        (const SDL_FColor*)((const nk_byte*)vertices + vc), vs,
+                        (const float*)((const nk_byte*)vertices + vt), vs,
+                        (vbuf.needed / vs),
+                        (void *) offset, cmd->elem_count, 2);
+
+                offset += cmd->elem_count;
+            }
+        }
+
+        SDL_SetRenderClipRect(sdl->renderer, &saved_clip);
+        if (!clipping_enabled) {
+            SDL_SetRenderClipRect(sdl->renderer, NULL);
+        }
+
+        nk_clear(&sdl->ctx);
+        nk_buffer_clear(&sdl->ogl.cmds);
+        nk_buffer_free(&vbuf);
+        nk_buffer_free(&ebuf);
+    }
+}
+
+NK_INTERN void
+nk_sdl_clipboard_paste(nk_handle usr, struct nk_text_edit *edit)
+{
+    char *text;
+    int len;
+    NK_UNUSED(usr);
+
+    /* this function returns empty string on failure, not NULL */
+    text = SDL_GetClipboardText();
+    NK_ASSERT(text);
+
+    if (text[0] != '\0') {
+        /* FIXME: there is a bug in Nuklear that affects UTF8 clipboard handling
+         * "len" should be a buffer length, but due to bug it must be a glyph count
+         * see: https://github.com/Immediate-Mode-UI/Nuklear/pull/841 */
+#if 0
+        len = nk_strlen(text);
+#else
+        len = SDL_utf8strlen(text);
+#endif
+        nk_textedit_paste(edit, text, len);
+    }
+    SDL_free(text);
+}
+
+NK_INTERN void
+nk_sdl_clipboard_copy(nk_handle usr, const char *text, int len)
+{
+    const char *ptext;
+    char *str;
+    size_t buflen;
+    int i;
+    struct nk_sdl* sdl = (struct nk_sdl*)usr.ptr;
+    NK_ASSERT(sdl);
+    if (len <= 0 || text == NULL) return;
+
+    /* FIXME: there is a bug in Nuklear that affects UTF8 clipboard handling
+     * "len" is expected to be a buffer length, but due to bug it actually is a glyph count
+     * see: https://github.com/Immediate-Mode-UI/Nuklear/pull/841 */
+#if 0
+    buflen = len + 1;
+    NK_UNUSED(ptext);
+#else
+    ptext = text;
+    for (i = len; i > 0; i--)
+        (void)SDL_StepUTF8(&ptext, NULL);
+    buflen = (size_t)(ptext - text) + 1;
+#endif
+
+    str = sdl->allocator.alloc(sdl->allocator.userdata, 0, buflen);
+    if (!str) return;
+    SDL_strlcpy(str, text, buflen);
+    SDL_SetClipboardText(str);
+    sdl->allocator.free(sdl->allocator.userdata, str);
+}
+
+NK_API struct nk_context*
+nk_sdl_init(SDL_Window *win, SDL_Renderer *renderer, struct nk_allocator allocator)
+{
+    struct nk_sdl* sdl;
+    NK_ASSERT(win);
+    NK_ASSERT(renderer);
+    NK_ASSERT(allocator.alloc);
+    NK_ASSERT(allocator.free);
+    sdl = allocator.alloc(allocator.userdata, 0, sizeof(*sdl));
+    NK_ASSERT(sdl);
+    SDL_zerop(sdl);
+    sdl->allocator.userdata = allocator.userdata;
+    sdl->allocator.alloc = allocator.alloc;
+    sdl->allocator.free = allocator.free;
+    sdl->win = win;
+    sdl->renderer = renderer;
+    nk_init(&sdl->ctx, &sdl->allocator, 0);
+    sdl->ctx.userdata = nk_handle_ptr((void*)sdl);
+    sdl->ctx.clip.copy = nk_sdl_clipboard_copy;
+    sdl->ctx.clip.paste = nk_sdl_clipboard_paste;
+    sdl->ctx.clip.userdata = nk_handle_ptr((void*)sdl);
+    nk_buffer_init(&sdl->ogl.cmds, &sdl->allocator, NK_BUFFER_DEFAULT_INITIAL_SIZE);
+    sdl->last_left_click = 0;
+    sdl->edit_was_active = false;
+    sdl->insert_toggle = false;
+    return &sdl->ctx;
+}
+
+#ifdef NK_INCLUDE_FONT_BAKING
+NK_API struct nk_font_atlas*
+nk_sdl_font_stash_begin(struct nk_context* ctx)
+{
+    struct nk_sdl* sdl;
+    NK_ASSERT(ctx);
+    sdl = (struct nk_sdl*)ctx->userdata.ptr;
+    NK_ASSERT(sdl);
+    nk_font_atlas_init(&sdl->atlas, &sdl->allocator);
+    nk_font_atlas_begin(&sdl->atlas);
+    return &sdl->atlas;
+}
+#endif
+
+#ifdef NK_INCLUDE_FONT_BAKING
+NK_API void
+nk_sdl_font_stash_end(struct nk_context* ctx)
+{
+    struct nk_sdl* sdl;
+    const void *image; int w, h;
+    NK_ASSERT(ctx);
+    sdl = (struct nk_sdl*)ctx->userdata.ptr;
+    NK_ASSERT(sdl);
+    image = nk_font_atlas_bake(&sdl->atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
+    NK_ASSERT(image);
+    nk_sdl_device_upload_atlas(&sdl->ctx, image, w, h);
+    nk_font_atlas_end(&sdl->atlas, nk_handle_ptr(sdl->ogl.font_tex), &sdl->ogl.tex_null);
+    if (sdl->atlas.default_font) {
+        nk_style_set_font(&sdl->ctx, &sdl->atlas.default_font->handle);
+    }
+}
+#endif
+
+NK_API int
+nk_sdl_handle_event(struct nk_context* ctx, SDL_Event *evt)
+{
+    struct nk_sdl* sdl;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(evt);
+
+    sdl = (struct nk_sdl*)ctx->userdata.ptr;
+    NK_ASSERT(sdl);
+
+    /* We only care about Window currently used by Nuklear */
+    if (sdl->win != SDL_GetWindowFromEvent(evt)) {
+        return 0;
+    }
+
+    switch(evt->type)
+    {
+        case SDL_EVENT_KEY_UP: /* KEYUP & KEYDOWN share same routine */
+        case SDL_EVENT_KEY_DOWN:
+            {
+                int down = evt->type == SDL_EVENT_KEY_DOWN;
+                int ctrl_down = evt->key.mod & (SDL_KMOD_LCTRL | SDL_KMOD_RCTRL);
+
+                /* In 99% of the time, you want to use scancodes, not real key codes,
+                 * see: https://wiki.libsdl.org/SDL3/BestKeyboardPractices */
+                switch(evt->key.scancode)
+                {
+                    case SDL_SCANCODE_RSHIFT: /* RSHIFT & LSHIFT share same routine */
+                    case SDL_SCANCODE_LSHIFT:    nk_input_key(ctx, NK_KEY_SHIFT, down); break;
+                    case SDL_SCANCODE_DELETE:    nk_input_key(ctx, NK_KEY_DEL, down); break;
+                    case SDL_SCANCODE_RETURN:    nk_input_key(ctx, NK_KEY_ENTER, down); break;
+                    case SDL_SCANCODE_TAB:       nk_input_key(ctx, NK_KEY_TAB, down); break;
+                    case SDL_SCANCODE_BACKSPACE: nk_input_key(ctx, NK_KEY_BACKSPACE, down); break;
+                    case SDL_SCANCODE_HOME:      nk_input_key(ctx, NK_KEY_TEXT_START, down);
+                                                 nk_input_key(ctx, NK_KEY_SCROLL_START, down); break;
+                    case SDL_SCANCODE_END:       nk_input_key(ctx, NK_KEY_TEXT_END, down);
+                                                 nk_input_key(ctx, NK_KEY_SCROLL_END, down); break;
+                    case SDL_SCANCODE_PAGEDOWN:  nk_input_key(ctx, NK_KEY_SCROLL_DOWN, down); break;
+                    case SDL_SCANCODE_PAGEUP:    nk_input_key(ctx, NK_KEY_SCROLL_UP, down); break;
+                    case SDL_SCANCODE_A:         nk_input_key(ctx, NK_KEY_TEXT_SELECT_ALL, down && ctrl_down); break;
+                    case SDL_SCANCODE_Z:         nk_input_key(ctx, NK_KEY_TEXT_UNDO, down && ctrl_down); break;
+                    case SDL_SCANCODE_R:         nk_input_key(ctx, NK_KEY_TEXT_REDO, down && ctrl_down); break;
+                    case SDL_SCANCODE_C:         nk_input_key(ctx, NK_KEY_COPY, down && ctrl_down); break;
+                    case SDL_SCANCODE_V:         nk_input_key(ctx, NK_KEY_PASTE, down && ctrl_down); break;
+                    case SDL_SCANCODE_X:         nk_input_key(ctx, NK_KEY_CUT, down && ctrl_down); break;
+                    case SDL_SCANCODE_B:         nk_input_key(ctx, NK_KEY_TEXT_LINE_START, down && ctrl_down); break;
+                    case SDL_SCANCODE_E:         nk_input_key(ctx, NK_KEY_TEXT_LINE_END, down && ctrl_down); break;
+                    case SDL_SCANCODE_UP:        nk_input_key(ctx, NK_KEY_UP, down); break;
+                    case SDL_SCANCODE_DOWN:      nk_input_key(ctx, NK_KEY_DOWN, down); break;
+                    case SDL_SCANCODE_ESCAPE:    nk_input_key(ctx, NK_KEY_TEXT_RESET_MODE, down); break;
+                    case SDL_SCANCODE_INSERT:
+                        if (down) sdl->insert_toggle = !sdl->insert_toggle;
+                        if (sdl->insert_toggle) {
+                            nk_input_key(ctx, NK_KEY_TEXT_INSERT_MODE, down);
+                        } else {
+                            nk_input_key(ctx, NK_KEY_TEXT_REPLACE_MODE, down);
+                        }
+                        break;
+                    case SDL_SCANCODE_LEFT:
+                        if (ctrl_down)
+                            nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT, down);
+                        else
+                            nk_input_key(ctx, NK_KEY_LEFT, down);
+                        break;
+                    case SDL_SCANCODE_RIGHT:
+                        if (ctrl_down)
+                            nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT, down);
+                        else
+                            nk_input_key(ctx, NK_KEY_RIGHT, down);
+                        break;
+                    default:
+                        return 0;
+                }
+                return 1;
+            }
+
+        case SDL_EVENT_MOUSE_BUTTON_UP: /* MOUSEBUTTONUP & MOUSEBUTTONDOWN share same routine */
+        case SDL_EVENT_MOUSE_BUTTON_DOWN:
+            {
+                const int x = evt->button.x, y = evt->button.y;
+                const int down = evt->button.down;
+                const double dt = (double)(evt->button.timestamp - sdl->last_left_click) / 1000000000.0;
+                switch(evt->button.button)
+                {
+                    case SDL_BUTTON_LEFT:
+                        nk_input_button(ctx, NK_BUTTON_LEFT, x, y, down);
+                        nk_input_button(ctx, NK_BUTTON_DOUBLE, x, y,
+                                down && dt > NK_SDL_DOUBLE_CLICK_LO && dt < NK_SDL_DOUBLE_CLICK_HI);
+                        sdl->last_left_click = evt->button.timestamp;
+                        break;
+                    case SDL_BUTTON_MIDDLE: nk_input_button(ctx, NK_BUTTON_MIDDLE, x, y, down); break;
+                    case SDL_BUTTON_RIGHT:  nk_input_button(ctx, NK_BUTTON_RIGHT, x, y, down); break;
+                    default:
+                        return 0;
+                }
+            }
+            return 1;
+
+        case SDL_EVENT_MOUSE_MOTION:
+            ctx->input.mouse.pos.x = evt->motion.x;
+            ctx->input.mouse.pos.y = evt->motion.y;
+            ctx->input.mouse.delta.x = ctx->input.mouse.pos.x - ctx->input.mouse.prev.x;
+            ctx->input.mouse.delta.y = ctx->input.mouse.pos.y - ctx->input.mouse.prev.y;
+            return 1;
+
+        case SDL_EVENT_TEXT_INPUT:
+            {
+                nk_glyph glyph;
+                nk_size len;
+                NK_ASSERT(evt->text.text);
+                len = SDL_strlen(evt->text.text);
+                NK_ASSERT(len <= NK_UTF_SIZE);
+                NK_MEMCPY(glyph, evt->text.text, len);
+                nk_input_glyph(ctx, glyph);
+            }
+            return 1;
+
+        case SDL_EVENT_MOUSE_WHEEL:
+            nk_input_scroll(ctx, nk_vec2(evt->wheel.x, evt->wheel.y));
+            return 1;
+    }
+    return 0;
+}
+
+NK_API
+void nk_sdl_shutdown(struct nk_context* ctx)
+{
+    struct nk_sdl* sdl;
+    NK_ASSERT(ctx);
+    sdl = (struct nk_sdl*)ctx->userdata.ptr;
+    NK_ASSERT(sdl);
+
+#ifdef NK_INCLUDE_FONT_BAKING
+    if (sdl->atlas.font_num > 0)
+        nk_font_atlas_clear(&sdl->atlas);
+#endif
+
+    nk_buffer_free(&sdl->ogl.cmds);
+
+    if (sdl->ogl.font_tex != NULL) {
+        SDL_DestroyTexture(sdl->ogl.font_tex);
+        sdl->ogl.font_tex = NULL;
+    }
+
+    nk_free(ctx);
+    sdl->allocator.free(sdl->allocator.userdata, sdl->debug_font);
+    sdl->allocator.free(sdl->allocator.userdata, sdl);
+}
+
+/* Debug Font Width/Height of internal texture atlas
+ * This is a result of: ceil(sqrt('~' - ' '))
+ * There is a sanity check for this value in nk_sdl_style_set_debug_font */
+#define NK_SDL_DFWH (10)
+
+NK_INTERN float
+nk_sdl_query_debug_font_width(nk_handle handle, float height,
+                              const char *text, int len)
+{
+    NK_UNUSED(handle);
+    return nk_utf_len(text, len) * height;
+}
+
+NK_INTERN void
+nk_sdl_query_debug_font_glypth(nk_handle handle, float height,
+                               struct nk_user_font_glyph *glyph,
+                               nk_rune codepoint, nk_rune next_codepoint)
+{
+    char ascii;
+    int idx, x, y;
+    NK_UNUSED(next_codepoint);
+    NK_UNUSED(handle);
+
+    /* replace non-ASCII characters with question mark */
+    ascii = (codepoint < (nk_rune)' ' || codepoint > (nk_rune)'~')
+            ? '?' : (char)codepoint;
+    NK_ASSERT(ascii >= ' ' && ascii <= '~');
+
+    idx = (int)(ascii - ' ');
+    x = idx / NK_SDL_DFWH;
+    y = idx % NK_SDL_DFWH;
+    NK_ASSERT(x >= 0 && x < NK_SDL_DFWH);
+    NK_ASSERT(y >= 0 && y < NK_SDL_DFWH);
+
+    glyph->height = height;
+    glyph->width = height;
+    glyph->xadvance = height;
+    glyph->uv[0].x = (float)(x + 0) / NK_SDL_DFWH;
+    glyph->uv[0].y = (float)(y + 0) / NK_SDL_DFWH;
+    glyph->uv[1].x = (float)(x + 1) / NK_SDL_DFWH;
+    glyph->uv[1].y = (float)(y + 1) / NK_SDL_DFWH;
+    glyph->offset.x = 0.0f;
+    glyph->offset.y = 0.0f;
+}
+
+NK_API void
+nk_sdl_style_set_debug_font(struct nk_context* ctx)
+{
+    struct nk_user_font* font;
+    struct nk_sdl* sdl;
+    SDL_Surface *surface;
+    SDL_Renderer *renderer;
+    char buf[2];
+    int x, y;
+    bool success;
+    NK_ASSERT(ctx);
+
+    sdl = (struct nk_sdl*)ctx->userdata.ptr;
+    NK_ASSERT(sdl);
+
+    if (sdl->debug_font) {
+        sdl->allocator.free(sdl->allocator.userdata, sdl->debug_font);
+        sdl->debug_font = 0;
+    }
+
+    /* sanity check: formal proof of NK_SDL_DFWH value (which is 10) */
+    NK_ASSERT(SDL_ceil(SDL_sqrt('~' - ' ')) == NK_SDL_DFWH);
+
+    /* We use another Software Renderer just to make sure
+     * that we won't mutate any state in the main Renderer. */
+    surface = SDL_CreateSurface(
+            NK_SDL_DFWH * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE,
+            NK_SDL_DFWH * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE,
+            SDL_PIXELFORMAT_RGBA32);
+    NK_ASSERT(surface);
+    renderer = SDL_CreateSoftwareRenderer(surface);
+    NK_ASSERT(renderer);
+    success = SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
+    NK_ASSERT(success);
+
+    /* SPACE is the first printable ASCII character */
+    NK_MEMCPY(buf, " ", sizeof(buf));
+    for (x = 0; x < NK_SDL_DFWH; x++)
+    {
+        for (y = 0; y < NK_SDL_DFWH; y++)
+        {
+            success = SDL_RenderDebugText(
+                    renderer,
+                    (float)(x * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE),
+                    (float)(y * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE),
+                    buf);
+            NK_ASSERT(success);
+            buf[0]++;
+
+            /* TILDE is the last printable ASCII character */
+            if (buf[0] > '~')
+                break;
+        }
+    }
+    success = SDL_RenderPresent(renderer);
+    NK_ASSERT(success);
+
+    font = sdl->allocator.alloc(sdl->allocator.userdata, 0, sizeof(*font));
+    NK_ASSERT(font);
+    font->userdata.ptr = sdl;
+    font->height = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE;
+    font->width = &nk_sdl_query_debug_font_width;
+    font->query = &nk_sdl_query_debug_font_glypth;
+
+    /* HACK: nk_sdl_device_upload_atlas turns pixels into SDL_Texture
+     *       and sets said Texture into sdl->ogl.font_tex
+     *       then nk_sdl_render expects same Texture at font->texture */
+    nk_sdl_device_upload_atlas(ctx, surface->pixels, surface->w, surface->h);
+    font->texture.ptr = sdl->ogl.font_tex;
+
+    sdl->debug_font = font;
+    nk_style_set_font(ctx, font);
+
+    SDL_DestroyRenderer(renderer);
+    SDL_DestroySurface(surface);
+}
+
+#endif /* NK_SDL3_RENDERER_IMPLEMENTATION_ONCE */
+#endif /* NK_SDL3_RENDERER_IMPLEMENTATION */
+

+ 2 - 1
nuklear.h

@@ -30731,7 +30731,8 @@ nk_tooltipfv(struct nk_context *ctx, const char *fmt, va_list args)
 ///   - [y]: Minor version with non-breaking API and library changes
 ///   - [y]: Minor version with non-breaking API and library changes
 ///   - [z]: Patch version with no direct changes to the API
 ///   - [z]: Patch version with no direct changes to the API
 ///
 ///
-/// - 2025/11/08 (4.13.0) - Fix: nk_property not updating 'win->edit.active'
+/// - 2025/11/15 (4.13.0) - Fix: nk_property not updating 'win->edit.active'
+///                         Add new updated demo: sdl3_renderer
 /// - 2025/10/08 (4.12.8) - Fix nk_widget_text to use NK_TEXT_ALIGN_LEFT by default,
 /// - 2025/10/08 (4.12.8) - Fix nk_widget_text to use NK_TEXT_ALIGN_LEFT by default,
 ///                         instead of silently failing when no x-axis alignment is provided,
 ///                         instead of silently failing when no x-axis alignment is provided,
 ///                         and refactor this function to keep the code style consistent
 ///                         and refactor this function to keep the code style consistent

+ 2 - 1
src/CHANGELOG

@@ -7,7 +7,8 @@
 ///   - [y]: Minor version with non-breaking API and library changes
 ///   - [y]: Minor version with non-breaking API and library changes
 ///   - [z]: Patch version with no direct changes to the API
 ///   - [z]: Patch version with no direct changes to the API
 ///
 ///
-/// - 2025/11/08 (4.13.0) - Fix: nk_property not updating 'win->edit.active'
+/// - 2025/11/15 (4.13.0) - Fix: nk_property not updating 'win->edit.active'
+///                         Add new updated demo: sdl3_renderer
 /// - 2025/10/08 (4.12.8) - Fix nk_widget_text to use NK_TEXT_ALIGN_LEFT by default,
 /// - 2025/10/08 (4.12.8) - Fix nk_widget_text to use NK_TEXT_ALIGN_LEFT by default,
 ///                         instead of silently failing when no x-axis alignment is provided,
 ///                         instead of silently failing when no x-axis alignment is provided,
 ///                         and refactor this function to keep the code style consistent
 ///                         and refactor this function to keep the code style consistent