aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile61
-rwxr-xr-xbin/demobin0 -> 357760 bytes
-rw-r--r--bin/demo.d107
-rw-r--r--camera.c236
-rw-r--r--main.c395
-rw-r--r--nuklear_sdl3_renderer.h707
6 files changed, 1506 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..2f123bd
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,61 @@
+# this Makefile is specific to GNU Make and GCC'compatible compilers
+
+PKG_CONFIG := $(or\
+ $(shell pkg-config --version >/dev/null 2>/dev/null && echo pkg-config),\
+ $(shell command -v pkg-config 2>/dev/null),\
+ $(error missing pkg-config utility!))
+
+PKG_SDL3 := $(or\
+ $(and $(shell ${PKG_CONFIG} sdl3 --path 2>/dev/null),sdl3),\
+ $(shell ${PKG_CONFIG} sdl3 --exists 2>/dev/null && echo sdl3),\
+ $(error pkg-config was unable to find: sdl3))
+
+PKG_SDL3_IMAGE := $(or\
+ $(and $(shell ${PKG_CONFIG} sdl3-image --path 2>/dev/null),sdl3-image),\
+ $(shell ${PKG_CONFIG} sdl3-image --exists 2>/dev/null && echo sdl3-image),\
+ $(error pkg-config was unable to find: sdl3-image))
+
+OSNAME := $(or\
+ $(and ${EMSCRIPTEN}, Emscripten),\
+ ${OS},\
+ $(shell uname -s),\
+ $(error could not detect OSNAME))
+
+binext_Windows_NT := .exe
+binext_Emscripten := .html
+BINEXT := ${binext_${OSNAME}}
+
+TEMPDIR := ./bin
+BIN := ${TEMPDIR}/demo${BINEXT}
+
+cflags += -std=c99 -Wall -Wextra -Wpedantic
+cflags += -O2
+#cflags += -O0 -g
+#cflags += -fsanitize=address
+#cflags += -fsanitize=undefined
+cflags += ${CFLAGS}
+
+cppflags += $(shell ${PKG_CONFIG} ${PKG_SDL3} ${PKG_SDL3_IMAGE} --cflags --keep-system-cflags)
+cppflags += ${CPPFLAGS}
+
+ldflags += $(shell ${PKG_CONFIG} ${PKG_SDL3} ${PKG_SDL3_IMAGE} --libs-only-L --libs-only-other --keep-system-libs)
+ldflags += ${LDFLAGS}
+
+ldlibs += $(shell ${PKG_CONFIG} ${PKG_SDL3} ${PKG_SDL3_IMAGE} --libs-only-l --keep-system-libs)
+ldlibs += -lm -luvc
+ldlibs += ${LDLIBS}
+# HACK: this one is for compatibility with other demos
+ldlibs += ${LIBS}
+
+DEP := ${TEMPDIR}/$(notdir ${BIN}).d
+
+SRC := main.c
+
+${BIN}:
+ mkdir -p $(dir $@)
+ ${CC} ${SRC} -o $@ -MD -MF ${DEP} ${cppflags} ${ldflags} ${ldlibs} ${cflags}
+
+${BIN}: ${SRC}
+
+-include ${DEP}
+
diff --git a/bin/demo b/bin/demo
new file mode 100755
index 0000000..e1594c4
--- /dev/null
+++ b/bin/demo
Binary files differ
diff --git a/bin/demo.d b/bin/demo.d
new file mode 100644
index 0000000..7d509a5
--- /dev/null
+++ b/bin/demo.d
@@ -0,0 +1,107 @@
+bin/demo: main.c /usr/include/stdc-predef.h \
+ /usr/include/SDL3/SDL_render.h /usr/include/SDL3/SDL_stdinc.h \
+ /usr/include/SDL3/SDL_platform_defines.h \
+ /usr/lib/gcc/x86_64-linux-gnu/15/include/stdarg.h /usr/include/string.h \
+ /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
+ /usr/include/features.h /usr/include/features-time64.h \
+ /usr/include/x86_64-linux-gnu/bits/wordsize.h \
+ /usr/include/x86_64-linux-gnu/bits/timesize.h \
+ /usr/include/x86_64-linux-gnu/sys/cdefs.h \
+ /usr/include/x86_64-linux-gnu/bits/long-double.h \
+ /usr/include/x86_64-linux-gnu/gnu/stubs.h \
+ /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
+ /usr/lib/gcc/x86_64-linux-gnu/15/include/stddef.h /usr/include/wchar.h \
+ /usr/include/x86_64-linux-gnu/bits/floatn.h \
+ /usr/include/x86_64-linux-gnu/bits/floatn-common.h \
+ /usr/include/x86_64-linux-gnu/bits/wchar.h \
+ /usr/include/x86_64-linux-gnu/bits/types/wint_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/mbstate_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
+ /usr/lib/gcc/x86_64-linux-gnu/15/include/stdint.h /usr/include/stdint.h \
+ /usr/include/x86_64-linux-gnu/bits/types.h \
+ /usr/include/x86_64-linux-gnu/bits/typesizes.h \
+ /usr/include/x86_64-linux-gnu/bits/time64.h \
+ /usr/include/x86_64-linux-gnu/bits/stdint-intn.h \
+ /usr/include/x86_64-linux-gnu/bits/stdint-uintn.h \
+ /usr/include/x86_64-linux-gnu/bits/stdint-least.h \
+ /usr/include/inttypes.h \
+ /usr/lib/gcc/x86_64-linux-gnu/15/include/stdbool.h \
+ /usr/include/SDL3/SDL_begin_code.h /usr/include/SDL3/SDL_close_code.h \
+ /usr/include/SDL3/SDL_blendmode.h /usr/include/SDL3/SDL_error.h \
+ /usr/include/SDL3/SDL_events.h /usr/include/SDL3/SDL_audio.h \
+ /usr/include/SDL3/SDL_endian.h /usr/include/endian.h \
+ /usr/include/x86_64-linux-gnu/bits/endian.h \
+ /usr/include/x86_64-linux-gnu/bits/endianness.h \
+ /usr/include/SDL3/SDL_mutex.h /usr/include/SDL3/SDL_atomic.h \
+ /usr/include/SDL3/SDL_thread.h /usr/include/SDL3/SDL_properties.h \
+ /usr/include/SDL3/SDL_iostream.h /usr/include/SDL3/SDL_camera.h \
+ /usr/include/SDL3/SDL_pixels.h /usr/include/SDL3/SDL_surface.h \
+ /usr/include/SDL3/SDL_rect.h /usr/include/SDL3/SDL_gamepad.h \
+ /usr/include/SDL3/SDL_guid.h /usr/include/SDL3/SDL_joystick.h \
+ /usr/include/SDL3/SDL_power.h /usr/include/SDL3/SDL_sensor.h \
+ /usr/include/SDL3/SDL_keyboard.h /usr/include/SDL3/SDL_keycode.h \
+ /usr/include/SDL3/SDL_scancode.h /usr/include/SDL3/SDL_video.h \
+ /usr/include/SDL3/SDL_mouse.h /usr/include/SDL3/SDL_pen.h \
+ /usr/include/SDL3/SDL_touch.h /usr/include/SDL3/SDL_gpu.h \
+ /usr/include/stdio.h /usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
+ /usr/include/x86_64-linux-gnu/bits/stdio.h /usr/include/stdlib.h \
+ /usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h \
+ /usr/include/x86_64-linux-gnu/bits/stdlib-float.h /usr/include/math.h \
+ /usr/include/x86_64-linux-gnu/bits/math-vector.h \
+ /usr/include/x86_64-linux-gnu/bits/libm-simd-decl-stubs.h \
+ /usr/include/x86_64-linux-gnu/bits/flt-eval-method.h \
+ /usr/include/x86_64-linux-gnu/bits/fp-logb.h \
+ /usr/include/x86_64-linux-gnu/bits/fp-fast.h \
+ /usr/include/x86_64-linux-gnu/bits/mathcalls-macros.h \
+ /usr/include/x86_64-linux-gnu/bits/mathcalls-helper-functions.h \
+ /usr/include/x86_64-linux-gnu/bits/mathcalls.h /usr/include/assert.h \
+ /usr/lib/gcc/x86_64-linux-gnu/15/include/limits.h \
+ /usr/lib/gcc/x86_64-linux-gnu/15/include/syslimits.h \
+ /usr/include/limits.h /usr/include/x86_64-linux-gnu/bits/posix1_lim.h \
+ /usr/include/x86_64-linux-gnu/bits/local_lim.h \
+ /usr/include/linux/limits.h \
+ /usr/include/x86_64-linux-gnu/bits/pthread_stack_min-dynamic.h \
+ /usr/include/x86_64-linux-gnu/bits/pthread_stack_min.h \
+ /usr/include/x86_64-linux-gnu/bits/posix2_lim.h /usr/include/time.h \
+ /usr/include/x86_64-linux-gnu/bits/time.h \
+ /usr/include/x86_64-linux-gnu/bits/types/clock_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/time_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/struct_tm.h \
+ /usr/include/x86_64-linux-gnu/bits/types/struct_timespec.h \
+ /usr/include/x86_64-linux-gnu/bits/types/clockid_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/timer_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/struct_itimerspec.h \
+ /usr/include/SDL3/SDL.h /usr/include/SDL3/SDL_assert.h \
+ /usr/include/SDL3/SDL_asyncio.h /usr/include/SDL3/SDL_bits.h \
+ /usr/include/SDL3/SDL_clipboard.h /usr/include/SDL3/SDL_cpuinfo.h \
+ /usr/include/SDL3/SDL_dialog.h /usr/include/SDL3/SDL_dlopennote.h \
+ /usr/include/SDL3/SDL_filesystem.h /usr/include/SDL3/SDL_haptic.h \
+ /usr/include/SDL3/SDL_hidapi.h /usr/include/SDL3/SDL_hints.h \
+ /usr/include/SDL3/SDL_init.h /usr/include/SDL3/SDL_loadso.h \
+ /usr/include/SDL3/SDL_locale.h /usr/include/SDL3/SDL_log.h \
+ /usr/include/SDL3/SDL_messagebox.h /usr/include/SDL3/SDL_metal.h \
+ /usr/include/SDL3/SDL_misc.h /usr/include/SDL3/SDL_platform.h \
+ /usr/include/SDL3/SDL_process.h /usr/include/SDL3/SDL_storage.h \
+ /usr/include/SDL3/SDL_system.h /usr/include/SDL3/SDL_time.h \
+ /usr/include/SDL3/SDL_timer.h /usr/include/SDL3/SDL_tray.h \
+ /usr/include/SDL3/SDL_version.h /usr/include/SDL3/SDL_oldnames.h \
+ /usr/include/SDL3_image/SDL_image.h /usr/include/SDL3/SDL_main.h \
+ /usr/include/SDL3/SDL_main_impl.h camera.c /usr/include/libuvc/libuvc.h \
+ /usr/include/x86_64-linux-gnu/sys/time.h \
+ /usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h \
+ /usr/include/x86_64-linux-gnu/sys/select.h \
+ /usr/include/x86_64-linux-gnu/bits/select.h \
+ /usr/include/x86_64-linux-gnu/bits/types/sigset_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h \
+ /usr/include/libuvc/libuvc_config.h /usr/include/unistd.h \
+ /usr/include/x86_64-linux-gnu/bits/posix_opt.h \
+ /usr/include/x86_64-linux-gnu/bits/confname.h \
+ /usr/include/x86_64-linux-gnu/bits/getopt_posix.h \
+ /usr/include/x86_64-linux-gnu/bits/getopt_core.h \
+ /usr/include/x86_64-linux-gnu/bits/unistd_ext.h ../nuklear/nuklear.h \
+ nuklear_sdl3_renderer.h
diff --git a/camera.c b/camera.c
new file mode 100644
index 0000000..fefa140
--- /dev/null
+++ b/camera.c
@@ -0,0 +1,236 @@
+#include "libuvc/libuvc.h"
+#include <stdio.h>
+#include <unistd.h>
+
+typedef struct camera {
+ uvc_context_t *ctx;
+ uvc_device_t *dev;
+ uvc_device_handle_t *devh;
+ uvc_stream_ctrl_t ctrl;
+ uvc_error_t res;
+ uvc_frame_t * latest_frame;
+} camera_t;
+
+/* This callback function runs once per frame. Use it to perform any
+ * quick processing you need, or have it put the frame into your application's
+ * input queue. If this function takes too long, you'll start losing frames. */
+void cb(uvc_frame_t *frame, void *ptr) {
+ uvc_frame_t *bgr;
+ uvc_error_t ret;
+ // enum uvc_frame_format *frame_format = (enum uvc_frame_format *)ptr;
+ /* FILE *fp;
+ * static int jpeg_count = 0;
+ * static const char *H264_FILE = "iOSDevLog.h264";
+ * static const char *MJPEG_FILE = ".jpeg";
+ * char filename[16]; */
+
+ /* We'll convert the image from YUV/JPEG to BGR, so allocate space */
+ bgr = uvc_allocate_frame(frame->width * frame->height * 3);
+ if (!bgr) {
+ printf("unable to allocate bgr frame!\n");
+ return;
+ }
+
+ // printf("callback! frame_format = %d, width = %d, height = %d, length = %lu, ptr = %p\n",
+ // frame->frame_format, frame->width, frame->height, frame->data_bytes, ptr);
+
+ switch (frame->frame_format) {
+ case UVC_FRAME_FORMAT_H264:
+ /* use `ffplay H264_FILE` to play */
+ /* fp = fopen(H264_FILE, "a");
+ * fwrite(frame->data, 1, frame->data_bytes, fp);
+ * fclose(fp); */
+ break;
+ case UVC_COLOR_FORMAT_MJPEG:
+ /* sprintf(filename, "%d%s", jpeg_count++, MJPEG_FILE);
+ * fp = fopen(filename, "w");
+ * fwrite(frame->data, 1, frame->data_bytes, fp);
+ * fclose(fp); */
+ break;
+ case UVC_COLOR_FORMAT_YUYV:
+ /* Do the BGR conversion */
+ ret = uvc_any2bgr(frame, bgr);
+ if (ret) {
+ uvc_perror(ret, "uvc_any2bgr");
+ uvc_free_frame(bgr);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (frame->sequence % 30 == 0) {
+ printf(" * got image %u, %dx%d\n", frame->sequence, frame->width, frame->height);
+ }
+
+ /* Call a user function:
+ *
+ * my_type *my_obj = (*my_type) ptr;
+ * my_user_function(ptr, bgr);
+ * my_other_function(ptr, bgr->data, bgr->width, bgr->height);
+ */
+
+ /* Call a C++ method:
+ *
+ * my_type *my_obj = (*my_type) ptr;
+ * my_obj->my_func(bgr);
+ */
+ // bgr->width = frame->width;
+ // bgr->height = frame->height;
+ // bgr->step = frame->step;
+ camera_t * camera = (camera_t *) ptr;
+ camera->latest_frame = frame;
+
+ /* Use opencv.highgui to display the image:
+ *
+ * cvImg = cvCreateImageHeader(
+ * cvSize(bgr->width, bgr->height),
+ * IPL_DEPTH_8U,
+ * 3);
+ *
+ * cvSetData(cvImg, bgr->data, bgr->width * 3);
+ *
+ * cvNamedWindow("Test", CV_WINDOW_AUTOSIZE);
+ * cvShowImage("Test", cvImg);
+ * cvWaitKey(10);
+ *
+ * cvReleaseImageHeader(&cvImg);
+ */
+
+ uvc_free_frame(bgr);
+}
+
+int get_camera_devices(camera_t * camera) {
+ /* Initialize a UVC service context. Libuvc will set up its own libusb
+ * context. Replace NULL with a libusb_context pointer to run libuvc
+ * from an existing libusb context. */
+ camera->res = uvc_init(&camera->ctx, NULL);
+
+ if (camera->res < 0) {
+ uvc_perror(camera->res, "uvc_init");
+ return camera->res;
+ }
+
+ puts("UVC initialized");
+
+ /* Locates the first attached UVC device, stores in dev */
+ camera->res = uvc_find_device(
+ camera->ctx, &camera->dev,
+ 0, 0, NULL); /* filter devices: vendor_id, product_id, "serial_num" */
+
+ if (camera->res < 0) {
+ uvc_perror(camera->res, "uvc_find_device"); /* no devices found */
+ } else {
+ puts("Device found");
+
+ /* Try to open the device: requires exclusive access */
+ camera->res = uvc_open(camera->dev, &camera->devh);
+
+ if (camera->res < 0) {
+ uvc_perror(camera->res, "uvc_open"); /* unable to open device */
+ } else {
+ puts("Device opened");
+
+ /* Print out a message containing all the information that libuvc
+ * knows about the device */
+ uvc_print_diag(camera->devh, stderr);
+
+ const uvc_format_desc_t *format_desc = uvc_get_format_descs(camera->devh);
+ const uvc_frame_desc_t *frame_desc = format_desc->frame_descs;
+ enum uvc_frame_format frame_format;
+ int width = 640;
+ int height = 480;
+ int fps = 30;
+
+ switch (format_desc->bDescriptorSubtype) {
+ case UVC_VS_FORMAT_MJPEG:
+ frame_format = UVC_COLOR_FORMAT_MJPEG;
+ break;
+ case UVC_VS_FORMAT_FRAME_BASED:
+ frame_format = UVC_FRAME_FORMAT_H264;
+ break;
+ default:
+ frame_format = UVC_FRAME_FORMAT_YUYV;
+ break;
+ }
+
+ if (frame_desc) {
+ width = frame_desc->wWidth;
+ height = frame_desc->wHeight;
+ fps = 10000000 / frame_desc->dwDefaultFrameInterval;
+ }
+
+ printf("\nFirst format: (%4s) %dx%d %dfps\n", format_desc->fourccFormat, width, height, fps);
+
+ /* Try to negotiate first stream profile */
+ camera->res = uvc_get_stream_ctrl_format_size(
+ camera->devh, &camera->ctrl, /* result stored in ctrl */
+ frame_format,
+ width, height, fps /* width, height, fps */
+ );
+
+ /* Print out the result */
+ uvc_print_stream_ctrl(&camera->ctrl, stderr);
+
+ if (camera->res < 0) {
+ uvc_perror(camera->res, "get_mode"); /* device doesn't provide a matching stream */
+ }
+ }
+ }
+ return 0;
+}
+
+void start_camera_stream(camera_t * camera) {
+ /* Start the video stream. The library will call user function cb:
+ * cb(frame, (void *) 12345)
+ */
+ camera->res = uvc_start_streaming(camera->devh, &camera->ctrl, cb, camera, 0);
+
+ if (camera->res < 0) {
+ uvc_perror(camera->res, "start_streaming"); /* unable to start stream */
+ } else {
+ puts("Streaming...");
+
+ /* enable auto exposure - see uvc_set_ae_mode documentation */
+ puts("Enabling auto exposure ...");
+ const uint8_t UVC_AUTO_EXPOSURE_MODE_AUTO = 2;
+ camera->res = uvc_set_ae_mode(camera->devh, UVC_AUTO_EXPOSURE_MODE_AUTO);
+ if (camera->res == UVC_SUCCESS) {
+ puts(" ... enabled auto exposure");
+ } else if (camera->res == UVC_ERROR_PIPE) {
+ /* this error indicates that the camera does not support the full AE mode;
+ * try again, using aperture priority mode (fixed aperture, variable exposure time) */
+ puts(" ... full AE not supported, trying aperture priority mode");
+ const uint8_t UVC_AUTO_EXPOSURE_MODE_APERTURE_PRIORITY = 8;
+ camera->res = uvc_set_ae_mode(camera->devh, UVC_AUTO_EXPOSURE_MODE_APERTURE_PRIORITY);
+ if (camera->res < 0) {
+ uvc_perror(camera->res, " ... uvc_set_ae_mode failed to enable aperture priority mode");
+ } else {
+ puts(" ... enabled aperture priority auto exposure mode");
+ }
+ } else {
+ uvc_perror(camera->res, " ... uvc_set_ae_mode failed to enable auto exposure mode");
+ }
+ }
+}
+
+void stop_camera_stream(camera_t * camera) {
+ /* End the stream. Blocks until last callback is serviced */
+ uvc_stop_streaming(camera->devh);
+ puts("Done streaming.");
+}
+
+void close_camera(camera_t * camera) {
+ /* Release our handle on the device */
+ uvc_close(camera->devh);
+ puts("Device closed");
+
+ /* Release the device descriptor */
+ uvc_unref_device(camera->dev);
+
+ /* Close the UVC context. This closes and cleans up any existing device handles,
+ * and it closes the libusb context if one was not provided. */
+ uvc_exit(camera->ctx);
+ puts("UVC exited");
+}
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..2a37839
--- /dev/null
+++ b/main.c
@@ -0,0 +1,395 @@
+/* nuklear - public domain */
+#include <SDL3/SDL_render.h>
+#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>
+#include <SDL3_image/SDL_image.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>
+
+#include "./camera.c"
+
+/* ===============================================================
+ *
+ * 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
+
+/* We can reuse the `bool` symbol because SDL3 guarantees its existence */
+/*#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)
+
+/* SDL3 does not provide "dtoa" (only integer versions)
+ * but we can emulate it with SDL_snprintf */
+static char* nk_sdl_dtoa(char *str, double d);
+#define NK_DTOA(str, d) nk_sdl_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/nuklear.h"
+#define NK_SDL3_RENDERER_IMPLEMENTATION
+#include "nuklear_sdl3_renderer.h"
+
+#define WINDOW_WIDTH 1200
+#define WINDOW_HEIGHT 800
+
+/* ===============================================================
+ *
+ * DEMO
+ *
+ * ===============================================================*/
+
+struct nk_sdl_app {
+ SDL_Window* window;
+ SDL_Renderer* renderer;
+ struct nk_context * ctx;
+ struct nk_colorf bg;
+ enum nk_anti_aliasing AA;
+ camera_t* camera;
+};
+
+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;
+ camera_t * camera;
+ 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();
+ }
+
+ app->camera = malloc(sizeof(*camera));
+ app->camera->latest_frame = NULL;
+ get_camera_devices(app->camera);
+
+ 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, 16 * 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
+
+ nk_input_begin(ctx);
+
+ 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;
+ int wwidth = 0;
+ int wheight = 0;
+ static char text[9][64];
+ static int text_len[9];
+ SDL_GetWindowSizeInPixels(app->window, &wwidth, &wheight);
+
+ nk_input_end(ctx);
+
+ /* GUI */
+ if (nk_begin(ctx, "Recent", nk_rect(0, 0, 300, wheight),
+ NK_WINDOW_TITLE|NK_TEXT_CENTERED))
+ {
+
+ }
+ nk_end(ctx);
+
+ /* GUI */
+ if (nk_begin(ctx, "Main", nk_rect(300, 0, wwidth-200-300, wheight),
+ NK_WINDOW_BORDER))
+ {
+ nk_layout_row_dynamic(ctx, 30, 2);
+ nk_label(ctx, "Loaf Key", 0);
+ nk_edit_string(ctx, NK_EDIT_SIMPLE, text[0], &text_len[0], 64, nk_filter_default);
+ if(nk_button_label(ctx, "Action!")) {
+ start_camera_stream(app->camera);
+ }
+ if(nk_button_label(ctx, "Cut!")) {
+ stop_camera_stream(app->camera);
+ }
+ nk_spacer(ctx);
+ nk_spacer(ctx);
+ }
+
+ if (nk_begin(ctx, "Main", nk_rect(300, 0, wwidth-200-300, wheight),
+ NK_WINDOW_BORDER))
+ {
+ if (app->camera->latest_frame != NULL && app->camera->latest_frame->data_bytes > 0) {
+ const uvc_frame_t * frame = app->camera->latest_frame;
+ printf("Texture WxH: %dx%d, Bytes: %zd\n", frame->width, frame->height, frame->data_bytes);
+ SDL_Texture * texture = SDL_CreateTexture(app->renderer, SDL_PIXELFORMAT_MJPG, SDL_TEXTUREACCESS_STREAMING, frame->width, frame->height);
+ if (texture == NULL) {
+ puts("ERROR Creating Texture!");
+ const char * err = SDL_GetError();
+ puts(err);
+ puts("---");
+ }
+ bool upres = SDL_UpdateTexture(texture, NULL, frame->data, frame->data_bytes);
+ if (!upres) {
+ puts("Failed to add frame to Texture");
+ const char * err = SDL_GetError();
+ puts(err);
+ puts("---");
+ }
+
+ struct nk_image camera_image;
+ camera_image = nk_image_ptr(texture);
+ struct nk_command_buffer * canvas = nk_window_get_canvas(ctx);
+ struct nk_rect total_space = nk_window_get_content_region(ctx);
+ const struct nk_color grid_color = nk_rgba(255, 255, 255, 255);
+ nk_draw_image(canvas, total_space, &camera_image, grid_color);
+ }
+ }
+ nk_end(ctx);
+
+ 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, "APP WINDOW W:%d, H:%d", wwidth, wheight);
+ 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) {
+ close_camera(app->camera);
+ nk_input_end(app->ctx);
+ nk_sdl_shutdown(app->ctx);
+ SDL_DestroyRenderer(app->renderer);
+ SDL_DestroyWindow(app->window);
+ SDL_free(app);
+ }
+}
+
+static char*
+nk_sdl_dtoa(char *str, double d)
+{
+ NK_ASSERT(str);
+ if (!str) return NULL;
+ (void)SDL_snprintf(str, 99999, "%.17g", d);
+ return str;
+}
+
diff --git a/nuklear_sdl3_renderer.h b/nuklear_sdl3_renderer.h
new file mode 100644
index 0000000..25698ea
--- /dev/null
+++ b/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 */
+