Mercurial > repos > blastem
changeset 2693:46dba737b931
WIP Nuklear UI in VDP debug windows
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Thu, 19 Jun 2025 19:59:05 -0700 |
parents | effbb52ab3f0 |
children | a6a27d4aa8ab |
files | Makefile nuklear_ui/blastem_nuklear.c nuklear_ui/blastem_nuklear.h nuklear_ui/debug_ui.c nuklear_ui/debug_ui.h nuklear_ui/nuklear_sdl_gles2.h render.h render_sdl.c render_sdl.h vdp.c |
diffstat | 10 files changed, 392 insertions(+), 118 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile Sun Jun 15 15:46:34 2025 -0700 +++ b/Makefile Thu Jun 19 19:59:05 2025 -0700 @@ -266,7 +266,7 @@ endif AUDIOOBJS=ym2612.o ymf262.o ym_common.o psg.o wave.o flac.o vgm.o event_log.o render_audio.o rf5c164.o CONFIGOBJS=config.o tern.o util.o paths.o -NUKLEAROBJS=$(FONT) $(CHOOSER) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o +NUKLEAROBJS=$(FONT) $(CHOOSER) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o nuklear_ui/debug_ui.o RENDEROBJS=ppm.o controller_info.o ifdef USE_FBDEV RENDEROBJS+= render_fbdev.o
--- a/nuklear_ui/blastem_nuklear.c Sun Jun 15 15:46:34 2025 -0700 +++ b/nuklear_ui/blastem_nuklear.c Thu Jun 19 19:59:05 2025 -0700 @@ -2626,7 +2626,7 @@ render_framebuffer_updated(FRAMEBUFFER_UI, render_width()); } else { #ifndef DISABLE_OPENGL - nk_sdl_render(NK_ANTI_ALIASING_ON, 512 * 1024, 128 * 1024); + nk_sdl_render(context, NK_ANTI_ALIASING_ON, 512 * 1024, 128 * 1024); #endif } nk_input_begin(context); @@ -2668,7 +2668,7 @@ ui_exit(); #endif } -static void handle_event(SDL_Event *event) +static void handle_event(uint8_t which, SDL_Event *event) { SDL_Joystick *joy = render_get_joystick(selected_controller); if (event->type == SDL_KEYDOWN) { @@ -2695,14 +2695,14 @@ if (stick_nav_disabled && event->type == SDL_CONTROLLERAXISMOTION) { return; } - nk_sdl_handle_event(event); + nk_sdl_handle_event(context, event); } static void context_destroyed(void) { if (context) { - nk_sdl_shutdown(); + nk_sdl_shutdown(context); context = NULL; } } @@ -2737,14 +2737,14 @@ return nk_image_ptr(fbimg); } -static void texture_init(void) +static void font_init(struct nk_context *ctx) { struct nk_font_atlas *atlas; if (fb_context) { nk_rawfb_font_stash_begin(fb_context, &atlas); } else { #ifndef DISABLE_OPENGL - nk_sdl_font_stash_begin(&atlas); + nk_sdl_font_stash_begin(ctx, &atlas); #endif } uint32_t font_size; @@ -2758,10 +2758,14 @@ nk_rawfb_font_stash_end(fb_context); } else { #ifndef DISABLE_OPENGL - nk_sdl_font_stash_end(); + nk_sdl_font_stash_end(ctx); #endif } - nk_style_set_font(context, &def_font->handle); + nk_style_set_font(ctx, &def_font->handle); +} + +static void texture_init(void) +{ for (uint32_t i = 0; i < num_ui_images; i++) { #ifndef DISABLE_OPENGL @@ -2776,44 +2780,33 @@ } } -static void style_init(void) +static void style_init(struct nk_context *ctx) { - context->style.checkbox.padding.x = render_height() / 120; - context->style.checkbox.padding.y = render_height() / 120; - context->style.checkbox.border = render_height() / 240; - context->style.checkbox.cursor_normal.type = NK_STYLE_ITEM_COLOR; - context->style.checkbox.cursor_normal.data.color = (struct nk_color){ + ctx->style.checkbox.padding.x = render_height() / 120; + ctx->style.checkbox.padding.y = render_height() / 120; + ctx->style.checkbox.border = render_height() / 240; + ctx->style.checkbox.cursor_normal.type = NK_STYLE_ITEM_COLOR; + ctx->style.checkbox.cursor_normal.data.color = (struct nk_color){ .r = 255, .g = 128, .b = 0, .a = 255 }; - context->style.checkbox.cursor_hover = context->style.checkbox.cursor_normal; - context->style.property.inc_button.text_hover = (struct nk_color){ + ctx->style.checkbox.cursor_hover = ctx->style.checkbox.cursor_normal; + ctx->style.property.inc_button.text_hover = (struct nk_color){ .r = 255, .g = 128, .b = 0, .a = 255 }; - context->style.property.dec_button.text_hover = context->style.property.inc_button.text_hover; - context->style.combo.button.text_hover = context->style.property.inc_button.text_hover; + ctx->style.property.dec_button.text_hover = ctx->style.property.inc_button.text_hover; + ctx->style.combo.button.text_hover = ctx->style.property.inc_button.text_hover; } static void fb_resize(void) { nk_rawfb_resize_fb(fb_context, NULL, render_width(), render_height(), 0); - style_init(); + style_init(context); texture_init(); } static void context_created(void) { - context = nk_sdl_init(render_get_window()); -#ifndef DISABLE_OPENGL - if (render_has_gl()) { - nk_sdl_device_create(); - } else { -#endif - fb_context = nk_rawfb_init(NULL, context, render_width(), render_height(), 0); - render_set_ui_fb_resize_handler(fb_resize); -#ifndef DISABLE_OPENGL - } -#endif - style_init(); + context = shared_nuklear_init(FRAMEBUFFER_UI); texture_init(); } @@ -2897,12 +2890,21 @@ } } -void blastem_nuklear_init(uint8_t file_loaded) +struct nk_context *shared_nuklear_init(uint8_t window) { - context = nk_sdl_init(render_get_window()); + if (window >= FRAMEBUFFER_USER_START) { +#ifdef DISABLE_OPENGL + return NULL; +#else + if (!render_has_gl()) { + return NULL; + } +#endif + } + struct nk_context *ret = nk_sdl_init(render_get_window(window)); #ifndef DISABLE_OPENGL if (render_has_gl()) { - nk_sdl_device_create(); + nk_sdl_device_create(ret); } else { #endif fb_context = nk_rawfb_init(NULL, context, render_width(), render_height(), 0); @@ -2910,7 +2912,14 @@ #ifndef DISABLE_OPENGL } #endif - style_init(); + style_init(ret); + font_init(ret); + return ret; +} + +void blastem_nuklear_init(uint8_t file_loaded) +{ + context = shared_nuklear_init(FRAMEBUFFER_UI); controller_360 = load_ui_image("images/360.png"); controller_ps4 = load_ui_image("images/ps4.png"); @@ -2926,8 +2935,8 @@ current_view = view_menu; set_content_binding_state(0); } - render_set_ui_render_fun(blastem_nuklear_render); - render_set_event_handler(handle_event); + render_set_ui_render_fun(FRAMEBUFFER_UI, blastem_nuklear_render); + render_set_event_handler(FRAMEBUFFER_UI, handle_event); render_set_gl_context_handlers(context_destroyed, context_created); char *unf = tern_find_path(config, "ui\0use_native_filechooser\0", TVAL_PTR).ptrval; use_native_filechooser = unf && !strcmp(unf, "on");
--- a/nuklear_ui/blastem_nuklear.h Sun Jun 15 15:46:34 2025 -0700 +++ b/nuklear_ui/blastem_nuklear.h Thu Jun 19 19:59:05 2025 -0700 @@ -10,6 +10,7 @@ #include "nuklear.h" #include "nuklear_sdl_gles2.h" +struct nk_context *shared_nuklear_init(uint8_t window); void blastem_nuklear_init(uint8_t file_loaded); void show_pause_menu(void); void show_play_view(void);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nuklear_ui/debug_ui.c Thu Jun 19 19:59:05 2025 -0700 @@ -0,0 +1,162 @@ +#include <limits.h> +#include <string.h> +#include <stdlib.h> + +#include "blastem_nuklear.h" +#include "../blastem.h" +#include "../genesis.h" +#include "../sms.h" +#include "../coleco.h" + +typedef struct { + struct nk_context *context; + uint32_t tex_width; + uint32_t tex_height; + uint8_t win_idx; +} debug_window; + +static debug_window windows[NUM_DEBUG_TYPES]; + +static void debug_handle_event(uint8_t which, SDL_Event *event) +{ + for (int i = 0; i < NUM_DEBUG_TYPES; i++) + { + if (windows[i].win_idx == which) { + nk_sdl_handle_event(windows[i].context, event); + break; + } + } +} + +#ifndef DISABLE_OPENGL +vdp_context *get_vdp(void) +{ + if (!current_system) { + return NULL; + } + switch (current_system->type) + { + case SYSTEM_GENESIS: + case SYSTEM_SEGACD: + case SYSTEM_PICO: + case SYSTEM_COPERA: + return ((genesis_context *)current_system)->vdp; + case SYSTEM_SMS: + case SYSTEM_GAME_GEAR: + case SYSTEM_SG1000: + case SYSTEM_SC3000: + return ((sms_context *)current_system)->vdp; + case SYSTEM_COLECOVISION: + return ((coleco_context *)current_system)->vdp; + default: + return NULL; + } +} + +void write_cram_internal(vdp_context * context, uint16_t addr, uint16_t value); +static void cram_debug_ui(void) +{ + vdp_context *vdp = get_vdp(); + if (!vdp) { + return; + } + struct nk_context *context = windows[DEBUG_CRAM].context; + nk_input_end(context); + char buf[64]; + + struct nk_image main_image = nk_image_id((int)render_get_window_texture(windows[DEBUG_CRAM].win_idx)); + if (nk_begin(context, "CRAM Debug", nk_rect(0, 0, windows[DEBUG_CRAM].tex_width + 100 + 8, windows[DEBUG_CRAM].tex_height + 8), NK_WINDOW_NO_SCROLLBAR)) { + nk_layout_space_begin(context, NK_STATIC, windows[DEBUG_CRAM].tex_height, INT_MAX); + nk_layout_space_push(context, nk_rect(100, 0, windows[DEBUG_CRAM].tex_width, windows[DEBUG_CRAM].tex_height)); + nk_image(context, main_image); + struct nk_rect bounds = nk_layout_widget_bounds(context); + bounds.x += 100; + bounds.w -= 100; + bounds.h = (vdp->flags2 & FLAG2_REGION_PAL) ? 313 : 262; + if (nk_input_is_mouse_hovering_rect(&context->input, bounds)) { + int off_y = context->input.mouse.pos.y - bounds.y; + int off_x = context->input.mouse.pos.x - bounds.x; + pixel_t pix = vdp->debug_fbs[DEBUG_CRAM][off_y * vdp->debug_fb_pitch[DEBUG_CRAM] / sizeof(pixel_t) + off_x]; +#ifdef USE_GLES + pixel_t b = pix >> 20 & 0xE, g = pix >> 12 & 0xE, r = pix >> 4 & 0xE; +#else + pixel_t r = pix >> 20 & 0xE, g = pix >> 12 & 0xE, b = pix >> 4 & 0xE; +#endif + pix = b << 8 | g << 4 | r; + snprintf(buf, sizeof(buf), "Line: %d, Index: %d, Value: %03X", off_y- vdp->border_top, off_x >> 3, pix & 0xFFFFFF); + nk_layout_space_push(context, nk_rect(100, 512 - 32*5, windows[DEBUG_CRAM].tex_width, 32)); + nk_label(context, buf, NK_TEXT_LEFT); + } + bounds.y += 512-32*4; + bounds.h = 32*4; + if (nk_input_is_mouse_hovering_rect(&context->input, bounds)) { + int index = ((((int)(context->input.mouse.pos.y - bounds.y)) >> 1) & 0xF0) + (((int)(context->input.mouse.pos.x - bounds.x)) >> 5); + snprintf(buf, sizeof(buf), "Index: %2d, Value: %03X", index, vdp->cram[index]); + nk_layout_space_push(context, nk_rect(100, 512 - 32*5, windows[DEBUG_CRAM].tex_width, 32)); + nk_label(context, buf, NK_TEXT_LEFT); + } + + static struct nk_scroll scroll; + context->style.window.scrollbar_size.y = 0; + nk_layout_space_push(context, nk_rect(0, 0, 100, windows[DEBUG_CRAM].tex_height)); + if (nk_group_scrolled_begin(context, &scroll, "Entries", 0)) { + nk_layout_space_begin(context, NK_STATIC, windows[DEBUG_CRAM].tex_height * 4, INT_MAX); + for (int i = 0; i < 64; i++) + { + nk_layout_space_push(context, nk_rect(0, i *32, 25, 32)); + snprintf(buf, sizeof(buf), "%d", i); + nk_label(context, buf, NK_TEXT_RIGHT); + nk_layout_space_push(context, nk_rect(30, i *32, 50, 32)); + snprintf(buf, sizeof(buf), "%03X", vdp->cram[i] & 0xEEE); + nk_edit_string_zero_terminated(context, NK_EDIT_FIELD, buf, sizeof(buf), nk_filter_hex); + char *end; + long newv = strtol(buf, &end, 16); + if (end != buf && newv != vdp->cram[i]) { + write_cram_internal(vdp, i, newv & 0xEEE); + } + } + nk_layout_space_end(context); + nk_group_scrolled_end(context); + } + + nk_end(context); + } + nk_sdl_render(context, NK_ANTI_ALIASING_ON, 512 * 1024, 128 * 1024); + + nk_input_begin(context); +} +#endif + +uint8_t debug_create_window(uint8_t debug_type, char *caption, uint32_t width, uint32_t height, window_close_handler close_handler) +{ +#ifndef DISABLE_OPENGL + if (!render_has_gl()) { +#endif + return render_create_window(caption, width, height, close_handler); +#ifndef DISABLE_OPENGL + } + uint32_t win_width = width, win_height = height; + ui_render_fun render = NULL; + switch (debug_type) + { + case DEBUG_CRAM: + win_width += 100; + render = cram_debug_ui; + break; + } + if (render) { + //compensate for padding + win_width += 4 * 2; + win_height += 4 * 2; + windows[debug_type].win_idx = render_create_window_tex(caption, win_width, win_height, width, height, close_handler); + windows[debug_type].tex_width = width; + windows[debug_type].tex_height = width; + windows[debug_type].context = shared_nuklear_init(windows[debug_type].win_idx); + render_set_ui_render_fun(windows[debug_type].win_idx, render); + render_set_event_handler(windows[debug_type].win_idx, debug_handle_event); + return windows[debug_type].win_idx; + } else { + return render_create_window(caption, width, height, close_handler); + } +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nuklear_ui/debug_ui.h Thu Jun 19 19:59:05 2025 -0700 @@ -0,0 +1,8 @@ +#ifndef DEBUG_UI_H_ +#define DEBUG_UI_H_ + +#include "../render.h" + +uint8_t debug_create_window(uint8_t debug_type, char *caption, uint32_t width, uint32_t height, window_close_handler close_handler); + +#endif //DEBUG_UI_H_
--- a/nuklear_ui/nuklear_sdl_gles2.h Sun Jun 15 15:46:34 2025 -0700 +++ b/nuklear_ui/nuklear_sdl_gles2.h Thu Jun 19 19:59:05 2025 -0700 @@ -29,13 +29,13 @@ NK_API struct nk_context* nk_sdl_init(SDL_Window *win); -NK_API void nk_sdl_font_stash_begin(struct nk_font_atlas **atlas); -NK_API void nk_sdl_font_stash_end(void); -NK_API int nk_sdl_handle_event(SDL_Event *evt); -NK_API void nk_sdl_render(enum nk_anti_aliasing , int max_vertex_buffer, int max_element_buffer); -NK_API void nk_sdl_shutdown(void); -NK_API void nk_sdl_device_destroy(void); -NK_API void nk_sdl_device_create(void); +NK_API void nk_sdl_font_stash_begin(struct nk_context *ctx, struct nk_font_atlas **atlas); +NK_API void nk_sdl_font_stash_end(struct nk_context *ctx); +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 , int max_vertex_buffer, int max_element_buffer); +NK_API void nk_sdl_shutdown(struct nk_context *ctx); +NK_API void nk_sdl_device_destroy(struct nk_context *ctx); +NK_API void nk_sdl_device_create(struct nk_context *ctx); #endif @@ -75,14 +75,14 @@ }; #endif -static struct nk_sdl { +struct nk_sdl { + struct nk_context ctx; SDL_Window *win; #ifndef DISABLE_OPENGL struct nk_sdl_device ogl; #endif - struct nk_context ctx; struct nk_font_atlas atlas; -} sdl; +}; #ifdef USE_GLES #define NK_SHADER_VERSION "#version 100\n" @@ -94,9 +94,10 @@ #ifndef DISABLE_OPENGL NK_API void -nk_sdl_device_create(void) +nk_sdl_device_create(struct nk_context *ctx) { GLint status; + struct nk_sdl *sdl = (struct nk_sdl *)ctx; static const GLchar *vertex_shader = NK_SHADER_VERSION "uniform mat4 ProjMtx;\n" @@ -120,7 +121,7 @@ " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV);\n" "}\n"; - struct nk_sdl_device *dev = &sdl.ogl; + struct nk_sdl_device *dev = &sdl->ogl; nk_buffer_init_default(&dev->cmds); dev->prog = glCreateProgram(); @@ -162,9 +163,10 @@ } NK_INTERN void -nk_sdl_device_upload_atlas(const void *image, int width, int height) +nk_sdl_device_upload_atlas(struct nk_context *ctx, const void *image, int width, int height) { - struct nk_sdl_device *dev = &sdl.ogl; + struct nk_sdl *sdl = (struct nk_sdl *)ctx; + struct nk_sdl_device *dev = &sdl->ogl; glGenTextures(1, &dev->font_tex); glBindTexture(GL_TEXTURE_2D, dev->font_tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -178,9 +180,10 @@ } NK_API void -nk_sdl_device_destroy(void) +nk_sdl_device_destroy(struct nk_context *ctx) { - struct nk_sdl_device *dev = &sdl.ogl; + struct nk_sdl *sdl = (struct nk_sdl *)ctx; + struct nk_sdl_device *dev = &sdl->ogl; glDetachShader(dev->prog, dev->vert_shdr); glDetachShader(dev->prog, dev->frag_shdr); glDeleteShader(dev->vert_shdr); @@ -193,9 +196,10 @@ } NK_API void -nk_sdl_render(enum nk_anti_aliasing AA, int max_vertex_buffer, int max_element_buffer) +nk_sdl_render(struct nk_context *ctx, enum nk_anti_aliasing AA, int max_vertex_buffer, int max_element_buffer) { - struct nk_sdl_device *dev = &sdl.ogl; + struct nk_sdl *sdl = (struct nk_sdl *)ctx; + struct nk_sdl_device *dev = &sdl->ogl; int display_width, display_height; struct nk_vec2 scale; GLfloat ortho[4][4] = { @@ -204,7 +208,7 @@ {0.0f, 0.0f,-1.0f, 0.0f}, {-1.0f,1.0f, 0.0f, 1.0f}, }; - SDL_GL_GetDrawableSize(sdl.win, &display_width, &display_height); + SDL_GL_GetDrawableSize(sdl->win, &display_width, &display_height); ortho[0][0] /= (GLfloat)display_width; ortho[1][1] /= (GLfloat)display_height; @@ -277,7 +281,7 @@ /* setup buffers to load vertices and elements */ nk_buffer_init_fixed(&vbuf, vertices, (nk_size)max_vertex_buffer); nk_buffer_init_fixed(&ebuf, elements, (nk_size)max_element_buffer); - nk_convert(&sdl.ctx, &dev->cmds, &vbuf, &ebuf, &config); + nk_convert(ctx, &dev->cmds, &vbuf, &ebuf, &config); } glBufferSubData(GL_ARRAY_BUFFER, 0, (size_t)max_vertex_buffer, vertices); glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, (size_t)max_element_buffer, elements); @@ -285,7 +289,7 @@ free(elements); /* iterate over and execute each draw command */ - nk_draw_foreach(cmd, &sdl.ctx, &dev->cmds) { + nk_draw_foreach(cmd, ctx, &dev->cmds) { if (!cmd->elem_count) continue; glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id); glScissor((GLint)(cmd->clip_rect.x * scale.x), @@ -295,7 +299,7 @@ glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset); offset += cmd->elem_count; } - nk_clear(&sdl.ctx); + nk_clear(ctx); glDisableVertexAttribArray((GLuint)dev->attrib_pos); glDisableVertexAttribArray((GLuint)dev->attrib_uv); glDisableVertexAttribArray((GLuint)dev->attrib_col); @@ -335,40 +339,43 @@ NK_API struct nk_context* nk_sdl_init(SDL_Window *win) { - sdl.win = win; - nk_init_default(&sdl.ctx, 0); - sdl.ctx.clip.copy = nk_sdl_clipbard_copy; - sdl.ctx.clip.paste = nk_sdl_clipbard_paste; - sdl.ctx.clip.userdata = nk_handle_ptr(0); - return &sdl.ctx; + struct nk_sdl *sdl = calloc(1, sizeof(struct nk_sdl)); + sdl->win = win; + nk_init_default(&sdl->ctx, 0); + sdl->ctx.clip.copy = nk_sdl_clipbard_copy; + sdl->ctx.clip.paste = nk_sdl_clipbard_paste; + sdl->ctx.clip.userdata = nk_handle_ptr(0); + return &sdl->ctx; } #ifndef DISABLE_OPENGL NK_API void -nk_sdl_font_stash_begin(struct nk_font_atlas **atlas) +nk_sdl_font_stash_begin(struct nk_context *ctx, struct nk_font_atlas **atlas) { - nk_font_atlas_init_default(&sdl.atlas); - nk_font_atlas_begin(&sdl.atlas); - *atlas = &sdl.atlas; + struct nk_sdl *sdl = (struct nk_sdl *)ctx; + nk_font_atlas_init_default(&sdl->atlas); + nk_font_atlas_begin(&sdl->atlas); + *atlas = &sdl->atlas; } NK_API void -nk_sdl_font_stash_end(void) +nk_sdl_font_stash_end(struct nk_context *ctx) { + struct nk_sdl *sdl = (struct nk_sdl *)ctx; const void *image; int w, h; - image = nk_font_atlas_bake(&sdl.atlas, &w, &h, NK_FONT_ATLAS_RGBA32); - nk_sdl_device_upload_atlas(image, w, h); - nk_font_atlas_end(&sdl.atlas, nk_handle_id((int)sdl.ogl.font_tex), &sdl.ogl.null); - if (sdl.atlas.default_font) - nk_style_set_font(&sdl.ctx, &sdl.atlas.default_font->handle); + image = nk_font_atlas_bake(&sdl->atlas, &w, &h, NK_FONT_ATLAS_RGBA32); + nk_sdl_device_upload_atlas(ctx, image, w, h); + nk_font_atlas_end(&sdl->atlas, nk_handle_id((int)sdl->ogl.font_tex), &sdl->ogl.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(SDL_Event *evt) +nk_sdl_handle_event(struct nk_context *ctx, SDL_Event *evt) { - struct nk_context *ctx = &sdl.ctx; + struct nk_sdl *sdl = (struct nk_sdl *)ctx; if (evt->type == SDL_KEYUP || evt->type == SDL_KEYDOWN) { /* key events */ int down = evt->type == SDL_KEYDOWN; @@ -487,14 +494,15 @@ } NK_API -void nk_sdl_shutdown(void) +void nk_sdl_shutdown(struct nk_context *ctx) { - nk_font_atlas_clear(&sdl.atlas); - nk_free(&sdl.ctx); + struct nk_sdl *sdl = (struct nk_sdl *)ctx; + nk_font_atlas_clear(&sdl->atlas); + nk_free(&sdl->ctx); #ifndef DISABLE_OPENGL - nk_sdl_device_destroy(); + nk_sdl_device_destroy(ctx); #endif - memset(&sdl, 0, sizeof(sdl)); + free(sdl); } #endif
--- a/render.h Sun Jun 15 15:46:34 2025 -0700 +++ b/render.h Thu Jun 19 19:59:05 2025 -0700 @@ -106,7 +106,11 @@ uint8_t render_saving_video(void); void render_end_video(void); void render_save_video(char *path); +uint8_t render_create_window_tex(char *caption, uint32_t width, uint32_t height, uint32_t tex_width, uint32_t tex_height, window_close_handler close_handler); uint8_t render_create_window(char *caption, uint32_t width, uint32_t height, window_close_handler close_handler); +#ifndef DISABLE_OPENGL +uint32_t render_get_window_texture(uint8_t which); +#endif void render_destroy_window(uint8_t which); pixel_t *render_get_framebuffer(uint8_t which, int *pitch); void render_framebuffer_updated(uint8_t which, int width); @@ -142,7 +146,7 @@ uint8_t render_has_gl(void); void render_config_updated(void); void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create); -void render_set_ui_render_fun(ui_render_fun); +void render_set_ui_render_fun(uint8_t which, ui_render_fun); void render_set_ui_fb_resize_handler(ui_render_fun resize); void render_set_frame_presented_fun(ui_render_fun); void render_set_audio_full_fun(ui_render_fun);
--- a/render_sdl.c Sun Jun 15 15:46:34 2025 -0700 +++ b/render_sdl.c Thu Jun 19 19:59:05 2025 -0700 @@ -34,17 +34,21 @@ typedef struct { - SDL_Window *win; + SDL_Window *win; SDL_Renderer *renderer; SDL_Texture *sdl_texture; SDL_Texture **static_images; window_close_handler on_close; + ui_render_fun on_render; + event_handler on_event; uint32_t width; uint32_t height; uint8_t num_static; #ifndef DISABLE_OPENGL SDL_GLContext *gl_context; pixel_t *texture_buf; + uint32_t orig_tex_width; + uint32_t orig_tex_height; uint32_t tex_width; uint32_t tex_height; GLuint gl_texture[2]; @@ -825,9 +829,13 @@ } static event_handler custom_event_handler; -void render_set_event_handler(event_handler handler) +void render_set_event_handler(uint8_t which, event_handler handler) { - custom_event_handler = handler; + if (which < FRAMEBUFFER_USER_START) { + custom_event_handler = handler; + } else { + extras[which - FRAMEBUFFER_USER_START].on_event = handler; + } } int render_find_joystick_index(SDL_JoystickID instanceID) @@ -952,26 +960,34 @@ static int32_t handle_event(SDL_Event *event) { - if (custom_event_handler) { - custom_event_handler(event); - } + SDL_Window *event_win = NULL; switch (event->type) { case SDL_KEYDOWN: - handle_keydown(event->key.keysym.sym, scancode_map[event->key.keysym.scancode]); + event_win = SDL_GetWindowFromID(event->key.windowID); + if (event_win == main_window) { + handle_keydown(event->key.keysym.sym, scancode_map[event->key.keysym.scancode]); + } break; case SDL_KEYUP: - handle_keyup(event->key.keysym.sym, scancode_map[event->key.keysym.scancode]); + event_win = SDL_GetWindowFromID(event->key.windowID); + if (event_win == main_window) { + handle_keyup(event->key.keysym.sym, scancode_map[event->key.keysym.scancode]); + } break; case SDL_JOYBUTTONDOWN: + event_win = main_window; handle_joydown(render_find_joystick_index(event->jbutton.which), event->jbutton.button); break; case SDL_JOYBUTTONUP: + event_win = main_window; handle_joyup(lock_joystick_index(render_find_joystick_index(event->jbutton.which), -1), event->jbutton.button); break; case SDL_JOYHATMOTION: + event_win = main_window; handle_joy_dpad(lock_joystick_index(render_find_joystick_index(event->jhat.which), -1), event->jhat.hat, event->jhat.value); break; case SDL_JOYAXISMOTION: + event_win = main_window; handle_joy_axis(lock_joystick_index(render_find_joystick_index(event->jaxis.which), -1), event->jaxis.axis, event->jaxis.value); break; case SDL_JOYDEVICEADDED: { @@ -1013,19 +1029,30 @@ break; } case SDL_MOUSEMOTION: + event_win = SDL_GetWindowFromID(event->motion.windowID); handle_mouse_moved(event->motion.which, event->motion.x * ui_scale_x + 0.5f, event->motion.y * ui_scale_y + 0.5f + overscan_top[video_standard], event->motion.xrel, event->motion.yrel); break; case SDL_MOUSEBUTTONDOWN: + event_win = SDL_GetWindowFromID(event->button.windowID); handle_mousedown(event->button.which, event->button.button); break; case SDL_MOUSEBUTTONUP: + event_win = SDL_GetWindowFromID(event->button.windowID); handle_mouseup(event->button.which, event->button.button); break; + case SDL_MOUSEWHEEL: + event_win = SDL_GetWindowFromID(event->wheel.windowID); + break; + case SDL_FINGERMOTION: + case SDL_FINGERDOWN: + case SDL_FINGERUP: + event_win = SDL_GetWindowFromID(event->tfinger.windowID); + break; case SDL_WINDOWEVENT: switch (event->window.event) { case SDL_WINDOWEVENT_SIZE_CHANGED: - if (!main_window) { + if (!main_window || SDL_GetWindowFromID(event->window.windowID) != main_window) { break; } need_ui_fb_resize = 1; @@ -1076,6 +1103,17 @@ break; } break; + case SDL_TEXTEDITING: + event_win = SDL_GetWindowFromID(event->edit.windowID); + break; +#if SDL_VERSION_ATLEAST(2, 0, 22) + case SDL_TEXTEDITING_EXT: + event_win = SDL_GetWindowFromID(event->editExt.windowID); + break; +#endif + case SDL_TEXTINPUT: + event_win = SDL_GetWindowFromID(event->text.windowID); + break; case SDL_DROPFILE: if (drag_drop_handler) { drag_drop_handler(strdup(event->drop.file)); @@ -1086,6 +1124,23 @@ puts(""); exit(0); } + if (event_win) { + if (event_win == main_window) { + if (custom_event_handler) { + custom_event_handler(FRAMEBUFFER_UI, event); + } + } else { + for (uint8_t i = 0; i < num_extras; i++) + { + if (extras[i].win == event_win) { + if (extras[i].on_event) { + extras[i].on_event(i + FRAMEBUFFER_USER_START, event); + } + break; + } + } + } + } return 0; } @@ -1546,14 +1601,15 @@ } } -SDL_Window *render_get_window(void) +SDL_Window *render_get_window(uint8_t which) { + SDL_Window *ret = which < FRAMEBUFFER_USER_START ? main_window : extras[which - FRAMEBUFFER_USER_START].win; #ifndef DISABLE_OPENGL if (render_gl) { - SDL_GL_MakeCurrent(main_window, main_context); + SDL_GL_MakeCurrent(ret, which < FRAMEBUFFER_USER_START ? main_context : extras[which - FRAMEBUFFER_USER_START].gl_context); } #endif - return main_window; + return ret; } uint32_t render_audio_syncs_per_sec(void) @@ -1662,7 +1718,7 @@ } #endif -uint8_t render_create_window(char *caption, uint32_t width, uint32_t height, window_close_handler close_handler) +uint8_t render_create_window_tex(char *caption, uint32_t width, uint32_t height, uint32_t tex_width, uint32_t tex_height, window_close_handler close_handler) { uint8_t win_idx = 0xFF; for (int i = 0; i < num_extras; i++) @@ -1718,12 +1774,12 @@ if (i) { glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, 1, 1, 0, SRC_FORMAT, SRC_TYPE, extras[win_idx].color); } else { - extras[win_idx].tex_width = width; - extras[win_idx].tex_height = height; + extras[win_idx].tex_width = extras[win_idx].orig_tex_width = tex_width; + extras[win_idx].tex_height = extras[win_idx].orig_tex_height = tex_height; char *npot_textures = tern_find_path_default(config, "video\0npot_textures\0", (tern_val){.ptrval = "off"}, TVAL_PTR).ptrval; if (strcmp(npot_textures, "on")) { - extras[win_idx].tex_width = nearest_pow2(width); - extras[win_idx].tex_height = nearest_pow2(height); + extras[win_idx].tex_width = nearest_pow2(tex_width); + extras[win_idx].tex_height = nearest_pow2(tex_height); } extras[win_idx].texture_buf = calloc(PITCH_PIXEL_T(extras[win_idx].tex_width) * extras[win_idx].tex_height, sizeof(pixel_t)); glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, extras[win_idx].tex_width, extras[win_idx].tex_height, 0, SRC_FORMAT, SRC_TYPE, extras[win_idx].texture_buf); @@ -1762,7 +1818,7 @@ if (!extras[win_idx].renderer) { goto fail_renderer; } - extras[win_idx].sdl_texture = SDL_CreateTexture(extras[win_idx].renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); + extras[win_idx].sdl_texture = SDL_CreateTexture(extras[win_idx].renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, tex_width, tex_height); if (!extras[win_idx].sdl_texture) { goto fail_texture; } @@ -1780,6 +1836,18 @@ return 0; } +uint8_t render_create_window(char *caption, uint32_t width, uint32_t height, window_close_handler close_handler) +{ + return render_create_window_tex(caption, width, height, width, height, close_handler); +} + +#ifndef DISABLE_OPENGL +uint32_t render_get_window_texture(uint8_t which) +{ + return extras[which - FRAMEBUFFER_USER_START].gl_texture[0]; +} +#endif + void render_destroy_window(uint8_t which) { uint8_t win_idx = which - FRAMEBUFFER_USER_START; @@ -2033,7 +2101,7 @@ return texture_buf; } else if (render_gl && which >= FRAMEBUFFER_USER_START) { uint8_t win_idx = which - FRAMEBUFFER_USER_START; - *pitch = PITCH_BYTES(extras[win_idx].width); + *pitch = PITCH_BYTES(extras[win_idx].tex_width); return extras[win_idx].texture_buf; } else { #endif @@ -2163,7 +2231,7 @@ uint8_t win_idx = which - FRAMEBUFFER_USER_START; SDL_GL_MakeCurrent(extras[win_idx].win, extras[win_idx].gl_context); glBindTexture(GL_TEXTURE_2D, extras[win_idx].gl_texture[0]); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, extras[win_idx].width, extras[win_idx].height, SRC_FORMAT, SRC_TYPE, buffer); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, extras[win_idx].orig_tex_width, extras[win_idx].orig_tex_height, SRC_FORMAT, SRC_TYPE, buffer); } else { #endif uint32_t shot_height = height; @@ -2230,14 +2298,17 @@ else { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glBindBuffer(GL_ARRAY_BUFFER, extras[win_idx].gl_buffers[0]); - extra_draw_quad( - extras + win_idx, - extras[win_idx].gl_texture[0], - (float)extras[win_idx].width / (float)extras[win_idx].tex_width, - (float)extras[win_idx].height / (float)extras[win_idx].tex_height - ); + if (extras[win_idx].on_render) { + extras[win_idx].on_render(); + } else { + glBindBuffer(GL_ARRAY_BUFFER, extras[win_idx].gl_buffers[0]); + extra_draw_quad( + extras + win_idx, + extras[win_idx].gl_texture[0], + (float)extras[win_idx].orig_tex_width / (float)extras[win_idx].tex_width, + (float)extras[win_idx].orig_tex_height / (float)extras[win_idx].tex_height + ); + } SDL_GL_SwapWindow(extras[win_idx].win); } @@ -2335,7 +2406,7 @@ void render_framebuffer_updated(uint8_t which, int width) { - if (sync_src == SYNC_AUDIO_THREAD || sync_src == SYNC_EXTERNAL) { + if (which < FRAMEBUFFER_USER_START && (sync_src == SYNC_AUDIO_THREAD || sync_src == SYNC_EXTERNAL)) { SDL_LockMutex(frame_mutex); while (frame_queue_len == 4) { SDL_CondSignal(frame_ready); @@ -2417,9 +2488,13 @@ } static ui_render_fun render_ui; -void render_set_ui_render_fun(ui_render_fun fun) +void render_set_ui_render_fun(uint8_t which, ui_render_fun fun) { - render_ui = fun; + if (which < FRAMEBUFFER_USER_START) { + render_ui = fun; + } else { + extras[which - FRAMEBUFFER_USER_START].on_render = fun; + } } static ui_render_fun frame_presented;
--- a/render_sdl.h Sun Jun 15 15:46:34 2025 -0700 +++ b/render_sdl.h Thu Jun 19 19:59:05 2025 -0700 @@ -3,9 +3,9 @@ #include <SDL.h> -SDL_Window *render_get_window(void); -typedef void (*event_handler)(SDL_Event *); -void render_set_event_handler(event_handler handler); +SDL_Window *render_get_window(uint8_t which); +typedef void (*event_handler)(uint8_t which, SDL_Event *); +void render_set_event_handler(uint8_t which, event_handler handler); SDL_Joystick *render_get_joystick(int index); SDL_GameController *render_get_controller(int index); int render_find_joystick_index(SDL_JoystickID instanceID);
--- a/vdp.c Sun Jun 15 15:46:34 2025 -0700 +++ b/vdp.c Thu Jun 19 19:59:05 2025 -0700 @@ -11,6 +11,9 @@ #include "util.h" #include "event_log.h" #include "terminal.h" +#ifndef DISABLE_NUKLEAR +#include "nuklear_ui/debug_ui.h" +#endif #define NTSC_INACTIVE_START 224 #define PAL_INACTIVE_START 240 @@ -6608,7 +6611,11 @@ return; } current_vdp = context; +#ifdef DISABLE_NUKLEAR context->debug_fb_indices[debug_type] = render_create_window(caption, width, height, vdp_debug_window_close); +#else + context->debug_fb_indices[debug_type] = debug_create_window(debug_type, caption, width, height, vdp_debug_window_close); +#endif if (context->debug_fb_indices[debug_type]) { context->enabled_debuggers |= 1 << debug_type; }