/* * mii_nuklear.c * * Copyright (C) 2023 Michel Pollet * * SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include "mii.h" #include "mii_bank.h" #include "mii_sw.h" #define NK_INCLUDE_FIXED_TYPES #define NK_INCLUDE_STANDARD_IO #define NK_INCLUDE_STANDARD_VARARGS #define NK_INCLUDE_DEFAULT_ALLOCATOR #define NK_INCLUDE_VERTEX_BUFFER_OUTPUT #define NK_INCLUDE_FONT_BAKING #define NK_INCLUDE_DEFAULT_FONT #define NK_BUTTON_TRIGGER_ON_RELEASE #include "nuklear.h" #include "nuklear_xlib_gl3.h" //#include "stb_image_write.h" #include #include #define _NK_RGBA(_r,_g,_b,_a) {.r=_r,.g=_g,.b=_b,.a=_a} static const struct nk_color style[NK_COLOR_COUNT] = { [NK_COLOR_TEXT] = _NK_RGBA(0, 0, 0, 255), [NK_COLOR_WINDOW] = _NK_RGBA(175, 175, 175, 255), [NK_COLOR_HEADER] = _NK_RGBA(175, 175, 175, 255), [NK_COLOR_BORDER] = _NK_RGBA(0, 0, 0, 255), [NK_COLOR_BUTTON] = _NK_RGBA(185, 185, 185, 255), [NK_COLOR_BUTTON_HOVER] = _NK_RGBA(170, 170, 170, 255), [NK_COLOR_BUTTON_ACTIVE] = _NK_RGBA(160, 160, 160, 255), [NK_COLOR_TOGGLE] = _NK_RGBA(150, 150, 150, 255), [NK_COLOR_TOGGLE_HOVER] = _NK_RGBA(120, 120, 120, 255), [NK_COLOR_TOGGLE_CURSOR] = _NK_RGBA(175, 175, 175, 255), [NK_COLOR_SELECT] = _NK_RGBA(190, 190, 190, 255), [NK_COLOR_SELECT_ACTIVE] = _NK_RGBA(175, 175, 175, 255), [NK_COLOR_SLIDER] = _NK_RGBA(190, 190, 190, 255), [NK_COLOR_SLIDER_CURSOR] = _NK_RGBA(80, 80, 80, 255), [NK_COLOR_SLIDER_CURSOR_HOVER] = _NK_RGBA(70, 70, 70, 255), [NK_COLOR_SLIDER_CURSOR_ACTIVE] = _NK_RGBA(60, 60, 60, 255), [NK_COLOR_PROPERTY] = _NK_RGBA(175, 175, 175, 255), [NK_COLOR_EDIT] = _NK_RGBA(150, 150, 150, 255), [NK_COLOR_EDIT_CURSOR] = _NK_RGBA(0, 0, 0, 255), [NK_COLOR_COMBO] = _NK_RGBA(175, 175, 175, 255), [NK_COLOR_CHART] = _NK_RGBA(160, 160, 160, 255), [NK_COLOR_CHART_COLOR] = _NK_RGBA(45, 45, 45, 255), [NK_COLOR_CHART_COLOR_HIGHLIGHT] = _NK_RGBA( 255, 0, 0, 255), [NK_COLOR_SCROLLBAR] = _NK_RGBA(180, 180, 180, 255), [NK_COLOR_SCROLLBAR_CURSOR] = _NK_RGBA(140, 140, 140, 255), [NK_COLOR_SCROLLBAR_CURSOR_HOVER] = _NK_RGBA(150, 150, 150, 255), [NK_COLOR_SCROLLBAR_CURSOR_ACTIVE] = _NK_RGBA(160, 160, 160, 255), [NK_COLOR_TAB_HEADER] = _NK_RGBA(180, 180, 180, 255), }; static GLuint screen_texture; static struct nk_image screen_nk; #include typedef uint64_t mii_time_t; enum { MII_TIME_RES = 1, MII_TIME_SECOND = 1000000, MII_TIME_MS = (MII_TIME_SECOND/1000), }; mii_time_t mii_get_time() { struct timespec tim; clock_gettime(CLOCK_MONOTONIC_RAW, &tim); uint64_t time = ((uint64_t)tim.tv_sec) * (1000000 / MII_TIME_RES) + tim.tv_nsec / (1000 * MII_TIME_RES); return time; } #include #include "fifo_declare.h" static pthread_t mii_thread; static bool mii_thread_running = false; //static mii_trace_t mii_trace = {}; float default_fps = 60; enum { SIGNAL_RESET, SIGNAL_STOP, SIGNAL_STEP, SIGNAL_RUN, }; typedef struct { uint8_t cmd; uint8_t data; } signal_t; DECLARE_FIFO(signal_t, signal_fifo, 16); DEFINE_FIFO(signal_t, signal_fifo); signal_fifo_t signal_fifo; static void *mii_thread_func(void *arg) { mii_t *mii = (mii_t *) arg; mii_thread_running = true; __uint128_t last_cycles = mii->cycles; uint32_t running = 1; unsigned long target_fps_us = 1000000 / default_fps; long sleep_time = target_fps_us; //mii_time_t base = mii_get_time(NULL); uint32_t last_frame = mii->video.frame_count; mii_time_t last_frame_stamp = mii_get_time(); while (mii_thread_running) { signal_t sig; while (!signal_fifo_isempty(&signal_fifo)) { sig = signal_fifo_read(&signal_fifo); switch (sig.cmd) { case SIGNAL_RESET: mii_reset(mii, sig.data); break; case SIGNAL_STOP: mii_dump_run_trace(mii); mii_dump_trace_state(mii); mii->state = MII_STOPPED; break; case SIGNAL_STEP: mii->state = MII_STEP; running = 1; break; case SIGNAL_RUN: mii->state = MII_RUNNING; last_frame_stamp = mii_get_time(); running = 1; break; } } if (mii->state != MII_STOPPED) mii_run(mii); switch (mii->state) { case MII_STOPPED: usleep(1000); break; case MII_STEP: if (running) { running--; mii_dump_trace_state(mii); usleep(1000); running = 1; if (mii->trace.step_inst) mii->trace.step_inst--; if (mii->trace.step_inst == 0) mii->state = MII_STOPPED; } break; case MII_RUNNING: break; case MII_TERMINATE: mii_thread_running = false; break; } if (mii->video.frame_count != last_frame) { sleep_time = target_fps_us; mii_time_t now = mii_get_time(); if (mii->state == MII_RUNNING) { mii_time_t delta = now - last_frame_stamp; // printf("frame time %d/%d sleep time %d\n", // (int)delta, (int)target_fps_us, // (int)target_fps_us - delta); sleep_time = target_fps_us - delta; if (sleep_time < 0) sleep_time = 0; last_frame = mii->video.frame_count; while (last_frame_stamp <= now) last_frame_stamp += target_fps_us; // calculate the MHz __uint128_t cycles = mii->cycles; __uint128_t delta_cycles = cycles - last_cycles; last_cycles = cycles; mii->speed_current = delta_cycles / (float)target_fps_us; } usleep(sleep_time); } } mii_dispose(mii); return NULL; } extern struct nk_font *nk_main_font; struct nk_font *nk_mono_font = NULL; extern const unsigned char mii_proggy_data[]; extern const unsigned int mii_proggy_size; extern const unsigned char mii_droid_data[]; extern const unsigned int mii_droid_size; void mii_nuklear_init( mii_t *mii, struct nk_context *ctx) { nk_style_from_table(ctx, style); { struct nk_font_atlas *atlas; nk_x11_font_stash_begin(&atlas); struct nk_font_config cfg = nk_font_config(0); #if 0 nk_rune ranges[] = { 0x0020, 0x007E, /* Ascii */ 0x00A1, 0x00FF, /* Symbols + Umlaute */ 0 }; #endif cfg.range = nk_font_default_glyph_ranges(); cfg.oversample_h = cfg.oversample_v = 1; cfg.pixel_snap = true; struct nk_font *nkf = nk_font_atlas_add_from_memory(atlas, (void*)mii_proggy_data, mii_proggy_size, 20, &cfg); nk_x11_font_stash_end(); nk_mono_font = nkf; } glGenTextures(1, &screen_texture); glBindTexture(GL_TEXTURE_2D, screen_texture); glTexImage2D(GL_TEXTURE_2D, 0, 4, MII_VRAM_WIDTH, MII_VRAM_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, mii->video.pixels); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // printf("%s texture created %d\n", __func__, screen_texture); // display opengl error GLenum err = glGetError(); if (err != GL_NO_ERROR) { printf("Error creating texture: %d\n", err); } screen_nk = nk_subimage_id( screen_texture, MII_VRAM_WIDTH, MII_VRAM_HEIGHT, nk_rect(0, 0, MII_VIDEO_WIDTH, MII_VIDEO_HEIGHT)); /* start the thread */ pthread_create(&mii_thread, NULL, mii_thread_func, mii); } extern int disk2_debug; int show_zero_page = 0; static void mii_nuklear_handle_input( mii_t *mii, struct nk_context *ctx) { struct nk_input *in = &ctx->input; if (in->keyboard.text_len) { // printf("INPUT %d %s\n", in->keyboard.text_len, in->keyboard.text); if (in->keyboard.text_len < 4) { mii_keypress(mii, in->keyboard.text[0]); } else if (in->keyboard.text_len > 1) { uint32_t *raw = ((uint32_t *) in->keyboard.text); for (int ki = 0; ki < in->keyboard.text_len / 4; ki ++) { uint32_t key = (raw[ki] >> 16) & 0xffff; uint8_t down = raw[ki] & 0xff; printf("KEY %04x %s\n", key, down ? "down" : "up"); if (down) { if (key == 0xffc9) { // F12 if (nk_input_is_key_down(in, NK_KEY_CTRL)) { signal_t sig = { .cmd = SIGNAL_RESET, .data = nk_input_is_key_down(in, NK_KEY_SHIFT) }; signal_fifo_write(&signal_fifo, sig); printf("RESET\n"); } } else if (key == 0xffc8) { // F11 if (nk_input_is_key_down(in, NK_KEY_CTRL)) { signal_t sig = { .cmd = SIGNAL_STOP, }; signal_fifo_write(&signal_fifo, sig); printf("STOP\n"); } } else if (key == 0xffc7) { // F10 if (nk_input_is_key_down(in, NK_KEY_CTRL)) { signal_t sig = { .cmd = SIGNAL_STEP, }; signal_fifo_write(&signal_fifo, sig); printf("STEP\n"); } } else if (key == 0xffc6) { // F9 if (nk_input_is_key_down(in, NK_KEY_CTRL)) { signal_t sig = { .cmd = SIGNAL_RUN, }; signal_fifo_write(&signal_fifo, sig); printf("RUN\n"); } } else if (key == 0xffc2) { // F5 mii->speed = 1.0; printf("Speed Normal (1MHz)\n"); } else if (key == 0xffc3) { // F6 mii->speed = 4; printf("Speed Fast (4MHz)\n"); } } if (key == 0xffeb || key == 0xffec) { // super left/right key -= 0xffeb; mii_bank_t *bank = &mii->bank[MII_BANK_MAIN]; uint8_t old = mii_bank_peek(bank, 0xc061 + key); mii_bank_poke(bank, 0xc061 + key, down ? 0x80 : 0); if (!!down != !!old) { printf("Apple %s %s\n", key ? "Open" : "Close", down ? "down" : "up"); } } } } in->keyboard.text_len = 0; } else { signal_t sig = {.cmd = -1 }; if (nk_input_is_key_pressed(in, NK_KEY_ENTER)) sig.data = 0x0d; else if (nk_input_is_key_pressed(in, NK_KEY_BACKSPACE)) sig.data = 0x08; else if (nk_input_is_key_pressed(in, NK_KEY_DEL)) sig.data = 0x7f; else if (nk_input_is_key_pressed(in, NK_KEY_UP)) sig.data = 'K' - 'A' + 1; else if (nk_input_is_key_pressed(in, NK_KEY_DOWN)) sig.data = 'J' - 'A' + 1; else if (nk_input_is_key_pressed(in, NK_KEY_LEFT)) sig.data = 'H' - 'A' + 1; else if (nk_input_is_key_pressed(in, NK_KEY_RIGHT)) sig.data = 'U' - 'A' + 1; else if (nk_input_is_key_pressed(in, NK_KEY_ESCAPE)) sig.data = 0x1b; if (sig.data) { // signal_fifo_write(&signal_fifo, sig); // printf("Key %d\n", sig.data); mii_keypress(mii, sig.data); } } } void mii_nuklear( mii_t *mii, struct nk_context *ctx) { if (mii->video.frame_count != mii->video.frame_drawn) { mii->video.frame_drawn = mii->video.frame_count; // update texture with new pixels; we only need 192 lines, the others // are padding for the power of 2 texture glBindTexture(GL_TEXTURE_2D, screen_texture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, MII_VRAM_WIDTH, MII_VIDEO_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, mii->video.pixels); } mii_nuklear_handle_input(mii, ctx); int height = 720 - 8; int width = MII_VIDEO_WIDTH * (height / (float)MII_VIDEO_HEIGHT); int xpos = 1280 / 2 - width / 2; { struct nk_style *s = &ctx->style; nk_style_push_color(ctx, &s->window.background, nk_rgba(0,0,0, 255)); nk_style_push_style_item(ctx, &s->window.fixed_background, nk_style_item_color(nk_rgba(0,0,0, 255))); if (nk_begin(ctx, "Apple //e Enhanced", nk_rect(xpos, 0, width + 10, height + 20), NK_WINDOW_NO_SCROLLBAR)) { nk_layout_row_static(ctx, height, width, 1); static int was_in = -1; if (nk_widget_is_hovered(ctx) && mii->mouse.enabled) { if (was_in != 1) { was_in = 1; ctx->input.mouse.grab = 1; // printf("IN\n"); } struct nk_rect bounds = nk_widget_bounds(ctx); // normalize mouse coordinates double x = ctx->input.mouse.pos.x - bounds.x; double y = ctx->input.mouse.pos.y - bounds.y; // get mouse button state int button = ctx->input.mouse.buttons[NK_BUTTON_LEFT].down; // clamp coordinates inside bounds double vw = bounds.w; double vh = bounds.h; double mw = mii->mouse.max_x - mii->mouse.min_x; double mh = mii->mouse.max_y - mii->mouse.min_y; mii->mouse.x = mii->mouse.min_x + (x * mw / vw) + 0.5; mii->mouse.y = mii->mouse.min_y + (y * mh / vh) + 0.5; mii->mouse.button = button; // printf("Mouse is %d %d\n", (int)mii->mouse.x, (int)mii->mouse.y); } else { if (was_in == 1) { was_in = 0; ctx->input.mouse.ungrab = 1; // printf("OUT\n"); } } nk_image(ctx, screen_nk); nk_end(ctx); nk_style_pop_color(ctx); nk_style_pop_style_item(ctx); } } struct nk_rect bounds = { .x = 0, .y = 0, .w = xpos, .h = height }; // nk_window_get_bounds(ctx); bool in = nk_input_is_mouse_hovering_rect(&ctx->input, bounds); static bool menu_open = false; if (in || menu_open) { struct nk_style *s = &ctx->style; nk_style_push_color(ctx, &s->window.background, nk_rgba(175, 175, 175, 255)); nk_style_push_style_item(ctx, &s->window.fixed_background, nk_style_item_color(nk_rgba(175, 175, 175, 255))); if (nk_begin(ctx, "Left Bar", nk_rect(0, 0, xpos, height + 20), NK_WINDOW_NO_SCROLLBAR)) { #if 0 nk_menubar_begin(ctx); nk_layout_row_begin(ctx, NK_STATIC, 25, 2); nk_layout_row_push(ctx, 60); bool menu = false; if (nk_menu_begin_label(ctx, "MII", NK_TEXT_LEFT, nk_vec2(140, 200))) { static size_t prog = 40; static float slider = 0.5; static int check = nk_true; nk_layout_row_dynamic(ctx, 25, 1); if (nk_menu_item_label(ctx, "Hide", NK_TEXT_LEFT)) ;//show_menu = nk_false; if (nk_menu_item_label(ctx, "About", NK_TEXT_LEFT)) ;//show_app_about = nk_true; nk_progress(ctx, &prog, 100, NK_MODIFIABLE); nk_slider_float(ctx, 0.01, &slider, 1.0, 0.05); nk_checkbox_label(ctx, "Mute", &check); nk_menu_end(ctx); menu = true; } menu_open = menu; nk_menubar_end(ctx); nk_layout_space_end(ctx); #endif int rw = xpos - 8; // nk_layout_row_dynamic(ctx, 4, 1); // nk_spacing(ctx, 1); nk_layout_row_static(ctx, 30, rw, 1); if (nk_button_label(ctx, "C-RESET")) mii_reset(mii, false); if (nk_button_label(ctx, "C-OA-RESET")) mii_reset(mii, true); // nk_layout_row_dynamic(ctx, 0, 1); nk_layout_row_begin(ctx, NK_STATIC, 30, 2); nk_layout_row_push(ctx, 20); nk_label(ctx, "V:", NK_TEXT_LEFT); nk_layout_row_push(ctx, rw - 20 - 4); const char *video_modes[] = { "Color", "Green", "Amber", }; mii->video.color_mode = nk_combo(ctx, video_modes, NK_LEN(video_modes), mii->video.color_mode, 25, nk_vec2(80,200)); nk_layout_space_end(ctx); nk_layout_row_begin(ctx, NK_STATIC, 30, 1); nk_layout_row_push(ctx, rw); const char *speed[] = { "1 MHz", "Slow", "Fast", }; nk_label(ctx, "Speed:", NK_TEXT_LEFT); nk_layout_row_push(ctx, 100); int spi = mii->speed > .95 && mii->speed < 1.05 ? 0 : mii->speed < 0.9 ? 1 : 2; spi = nk_combo(ctx, speed, NK_LEN(speed), spi, 25, nk_vec2(80,200)); mii->speed = spi == 0 ? 1.0 : spi == 1 ? 0.2 : 4.0; nk_layout_space_end(ctx); } nk_end(ctx); nk_style_pop_color(ctx); nk_style_pop_style_item(ctx); } if ( 0 && nk_begin(ctx, "Controls", nk_rect(width, 0, 350, 10 + 192 * 3), NK_WINDOW_NO_SCROLLBAR)) { nk_layout_row_static(ctx, 30, 110, 2); if (nk_button_label(ctx, "C-RESET")) mii_reset(mii, false); if (nk_button_label(ctx, "C-OA-RESET")) mii_reset(mii, true); #if 0 if (nk_button_label(ctx, "Screenshot")) { stbi_write_png("screen.png", MII_VIDEO_WIDTH, MII_VIDEO_HEIGHT, 4, mii->video.pixels, MII_VRAM_WIDTH * 4); printf("Screenshot taken\n"); } #endif nk_layout_row_dynamic(ctx, 30, 4); nk_label(ctx, "Speed:", NK_TEXT_CENTERED); if (nk_option_label(ctx, "Slow", mii->speed < 0.9)) mii->speed = 0.2; if (nk_option_label(ctx, "1 MHz", mii->speed > .95 && mii->speed < 1.05)) mii->speed = 1.0; if (nk_option_label(ctx, "Fast", mii->speed > 1.1 && mii->speed < 4.1)) mii->speed = 4.0; nk_layout_row_dynamic(ctx, 30, 4); nk_label(ctx, "Video:", NK_TEXT_CENTERED); if (nk_option_label(ctx, "Color", mii->video.color_mode == MII_VIDEO_COLOR)) mii->video.color_mode = MII_VIDEO_COLOR; if (nk_option_label(ctx, "Green", mii->video.color_mode == MII_VIDEO_GREEN)) mii->video.color_mode = MII_VIDEO_GREEN; if (nk_option_label(ctx, "Amber", mii->video.color_mode == MII_VIDEO_AMBER)) mii->video.color_mode = MII_VIDEO_AMBER; #if 0 nk_layout_row_dynamic(ctx, 20, 1); nk_style_set_font(ctx, &nk_mono_font->handle); struct nk_color save = ctx->style.window.background; ctx->style.window.background = (struct nk_color)_NK_RGBA(0, 0, 0, 255); struct nk_color fore = (struct nk_color)_NK_RGBA(0, 255, 0, 255); char label[64]; mii_dasm_t _dasm = {}; mii_dasm_t *dasm = &_dasm; mii_dasm_init(dasm, mii, 0); dasm->mii = mii; // display the last few cycles up to the PC for (int di = 0; di < 3; di++) { int pci = (mii_trace.idx + MII_PC_LOG_SIZE - 3 + di) % MII_PC_LOG_SIZE; dasm->pc = mii_trace.log[pci]; mii_dasm_step(dasm, label, sizeof(label)); if (di == 2) label[0] = '*'; nk_label_colored(ctx, label, NK_TEXT_LEFT, fore); } // and the (potentially) next instruction here mii_dasm_step(dasm, label, sizeof(label)); nk_label_colored(ctx, label, NK_TEXT_LEFT, fore); sprintf(label, "A:%02X X:%02X Y:%02X S:%02X", mii->cpu.A, mii->cpu.X, mii->cpu.Y, mii->cpu.S); nk_label_colored(ctx, label, NK_TEXT_CENTERED, fore); char n[] = {'C','Z','I','D','B','V','N'}; label[0] = 0; sprintf(label, "%04x ", mii->cpu.PC); for (int i = 0; i < 7; i++) sprintf(label + strlen(label), "%c%d ", n[i], mii->cpu.P.P[i]); nk_label_colored(ctx, label, NK_TEXT_CENTERED, fore); ctx->style.window.background = save; nk_style_set_font(ctx, &nk_main_font->handle); #endif nk_layout_row_static(ctx, 30, 30, 3); if (nk_button_symbol(ctx, NK_SYMBOL_RECT_SOLID)) { signal_fifo_write(&signal_fifo, (signal_t){.cmd = SIGNAL_STOP}); } if (nk_button_symbol(ctx, NK_SYMBOL_PLUS)) { signal_fifo_write(&signal_fifo, (signal_t){.cmd = SIGNAL_STEP}); } if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_RIGHT)) { signal_fifo_write(&signal_fifo, (signal_t){.cmd = SIGNAL_RUN}); } nk_layout_row_dynamic(ctx, 20, 3); { char label[32]; sprintf(label, "CPU: %.1fMHz", mii->speed_current); nk_label(ctx, label, NK_TEXT_CENTERED); } #if 0 nk_layout_row_dynamic(ctx, 20, 2); nk_checkbox_label(ctx, "Disk II Debug", &disk2_debug); nk_checkbox_label(ctx, "Slowmo", &mii_SLOW); // nk_checkbox_label(ctx, "Zero Page", &show_zero_page); #endif nk_end(ctx); } if (show_zero_page) { if (nk_begin(ctx, "Zero Page", nk_rect(0, 10 + 192 * 3, 600, 10 + 16 * 20), 0 )) { nk_layout_row_dynamic(ctx, 20, 1); nk_style_set_font(ctx, &nk_mono_font->handle); struct nk_color fore = (struct nk_color)_NK_RGBA(0, 255, 0, 255); uint8_t *zp = mii->bank[0].mem; char label[128]; for (int ri = 0; ri < (256 / 16); ri++) { sprintf(label, "%02x: ", ri * 16); for (int ci = 0; ci < 16; ci++) { sprintf(label + strlen(label), "%02X ", zp[ri * 16 + ci]); } nk_label_colored(ctx, label, NK_TEXT_LEFT, fore); } nk_style_set_font(ctx, &nk_main_font->handle); nk_end(ctx); } } }