From: Stefan Reinauer Date: Thu, 4 Jun 2026 14:57:54 +0000 (-0700) Subject: od-unix: add SDL runtime backend X-Git-Url: https://git.unchartedbackwaters.co.uk/w/?a=commitdiff_plain;h=a326a478c47671339b888ed644ff9dd2b374708d;p=francis%2Fwinuae.git od-unix: add SDL runtime backend Add the SDL-backed video, input, audio, sampler, and drive-click runtime implementation for the Unix target. This is the runtime path used by the standalone emulator window outside the Qt configuration frontend. --- diff --git a/od-unix/driveclick.cpp b/od-unix/driveclick.cpp new file mode 100644 index 00000000..b99d5bef --- /dev/null +++ b/od-unix/driveclick.cpp @@ -0,0 +1,188 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#ifdef DRIVESOUND + +#include "driveclick.h" +#include "uae.h" +#include "zfile.h" + +#include +#include + +#ifdef __APPLE__ +#include +#endif + +#ifndef WINUAE_UNIX_INSTALL_DATA_DIR +#define WINUAE_UNIX_INSTALL_DATA_DIR WINUAE_UNIX_SOURCE_DIR +#endif + +#ifndef WINUAE_UNIX_INSTALL_DATADIR_RELATIVE +#define WINUAE_UNIX_INSTALL_DATADIR_RELATIVE "share/winuae" +#endif + +int driveclick_pcdrivemask; +int driveclick_pcdrivenum; + +static const struct { + const TCHAR *name; + int slot; +} builtin_samples[] = { + { _T("drive_click.wav"), DS_CLICK }, + { _T("drive_spin.wav"), DS_SPIN }, + { _T("drive_spinnd.wav"), DS_SPINND }, + { _T("drive_startup.wav"), DS_START }, + { _T("drive_snatch.wav"), DS_SNATCH }, + { NULL, -1 } +}; + +static bool load_sample_file(const TCHAR *path, struct drvsample *sample) +{ + struct zfile *zf = zfile_fopen(path, _T("rb"), ZFD_NORMAL); + if (!zf) { + return false; + } + const uae_s64 size = zfile_size(zf); + if (size <= 0 || size > 16 * 1024 * 1024) { + zfile_fclose(zf); + return false; + } + uae_u8 *data = xmalloc(uae_u8, (size_t)size); + if (!data) { + zfile_fclose(zf); + return false; + } + const size_t got = zfile_fread(data, 1, (size_t)size, zf); + zfile_fclose(zf); + if (got != (size_t)size) { + xfree(data); + return false; + } + int decoded_len = (int)size; + sample->p = decodewav(data, &decoded_len); + sample->len = decoded_len; + xfree(data); + return sample->p != NULL && sample->len > 0; +} + +static std::string dirname_copy(const std::string &path) +{ + size_t slash = path.find_last_of('/'); + if (slash == std::string::npos) { + return "."; + } + if (slash == 0) { + return "/"; + } + return path.substr(0, slash); +} + +static std::string join_path(const std::string &dir, const std::string &name) +{ + if (dir.empty() || dir == ".") { + return name; + } + if (dir[dir.size() - 1] == '/') { + return dir + name; + } + return dir + "/" + name; +} + +static std::string executable_dir() +{ +#ifdef __APPLE__ + char path[MAX_DPATH]; + uint32_t size = sizeof path; + if (_NSGetExecutablePath(path, &size) == 0) { + return dirname_copy(path); + } +#elif defined(__linux__) + char path[MAX_DPATH]; + ssize_t len = readlink("/proc/self/exe", path, sizeof path - 1); + if (len > 0) { + path[len] = 0; + return dirname_copy(path); + } +#endif + return std::string(); +} + +static bool load_builtin_sample(const TCHAR *name, struct drvsample *sample) +{ + TCHAR path[MAX_DPATH]; + std::vector dirs; + dirs.push_back(WINUAE_UNIX_SOURCE_DIR "/od-win32/resources/"); + dirs.push_back(WINUAE_UNIX_SOURCE_DIR "/resources/"); + dirs.push_back(WINUAE_UNIX_INSTALL_DATA_DIR "/od-win32/resources/"); + if (start_path_data[0]) { + dirs.push_back(start_path_data); + } + if (start_path_data_exe[0]) { + dirs.push_back(start_path_data_exe); + } + + const std::string exedir = executable_dir(); + if (!exedir.empty()) { + dirs.push_back(join_path(exedir, "../Resources/od-win32/resources")); + dirs.push_back(join_path(exedir, "../" WINUAE_UNIX_INSTALL_DATADIR_RELATIVE "/od-win32/resources")); + dirs.push_back(join_path(exedir, "od-win32/resources")); + } + + for (size_t i = 0; i < dirs.size(); i++) { + if (dirs[i].empty()) { + continue; + } + const char *dir = dirs[i].c_str(); + snprintf(path, sizeof path, "%s%s%s", dir, dir[strlen(dir) - 1] == '/' ? "" : "/", name); + if (load_sample_file(path, sample)) { + return true; + } + } + return false; +} + +int driveclick_loadresource(struct drvsample *sp, int) +{ + bool ok = true; + for (int i = 0; builtin_samples[i].name; i++) { + struct drvsample *sample = sp + builtin_samples[i].slot; + if (!load_builtin_sample(builtin_samples[i].name, sample)) { + write_log(_T("Unix driveclick: missing built-in sample '%s'\n"), builtin_samples[i].name); + ok = false; + } + } + if (ok) { + write_log(_T("Unix driveclick: loaded built-in A500 sample set\n")); + } + return ok ? 1 : 0; +} + +void driveclick_fdrawcmd_seek(int, int) +{ +} + +void driveclick_fdrawcmd_motor(int, int) +{ +} + +void driveclick_fdrawcmd_vsync(void) +{ +} + +void driveclick_fdrawcmd_close(int) +{ +} + +int driveclick_fdrawcmd_open(int) +{ + return 0; +} + +void driveclick_fdrawcmd_detect(void) +{ + driveclick_pcdrivemask = 0; + driveclick_pcdrivenum = 0; +} + +#endif diff --git a/od-unix/graphics.cpp b/od-unix/graphics.cpp new file mode 100644 index 00000000..51e73167 --- /dev/null +++ b/od-unix/graphics.cpp @@ -0,0 +1,1186 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include "custom.h" +#ifdef AVIOUTPUT +#include "avioutput.h" +#endif +#include "xwin.h" +#include "drawing.h" +#include "options.h" +#include "memory.h" +#include "picasso96.h" +#include "uae.h" +#include "video.h" +#include "host.h" +#include "devices.h" +#include "gfxboard.h" + +#include +#include +#include +#include +#include +#include + +extern int pause_emulation; +extern void picasso_trigger_vblank(void); +extern void unix_rtg_overlay_sprite(int monid, uae_u32 *dst, int width, int height, int rowpixels); + +uae_u32 p96_rgbx16[65536]; +bool gfx_hdr; +int flashscreen; +struct picasso96_state_struct picasso96_state[MAX_AMIGAMONITORS]; +struct picasso_vidbuf_description picasso_vidinfo[MAX_AMIGAMONITORS]; + +static bool unix_graphics_initialized; +static bool unix_video_debug; +static bool unix_rtg_render_has_output[MAX_AMIGAMONITORS]; +static void unix_rtg_stop_render_thread(void); + +enum { + UNIX_PICASSO_STATE_SETDISPLAY = 1, + UNIX_PICASSO_STATE_SETPANNING = 2, + UNIX_PICASSO_STATE_SETGC = 4, + UNIX_PICASSO_STATE_SETDAC = 8, + UNIX_PICASSO_STATE_SETSWITCH = 16, + UNIX_PICASSO_STATE_SPRITE = 32 +}; + +static int unix_picasso_bytes_per_pixel(RGBFTYPE rgbfmt) +{ + switch (rgbfmt) { + case RGBFB_CLUT: + case RGBFB_Y4U1V1: + return 1; + case RGBFB_R5G6B5: + case RGBFB_R5G5B5: + case RGBFB_R5G6B5PC: + case RGBFB_R5G5B5PC: + case RGBFB_B5G6R5PC: + case RGBFB_B5G5R5PC: + case RGBFB_Y4U2V2: + return 2; + case RGBFB_R8G8B8: + case RGBFB_B8G8R8: + return 3; + case RGBFB_A8R8G8B8: + case RGBFB_A8B8G8R8: + case RGBFB_R8G8B8A8: + case RGBFB_B8G8R8A8: + return 4; + default: + return 0; + } +} + +static uae_u16 unix_picasso_load_host_u16(const uae_u8 *src) +{ + uae_u16 value; + memcpy(&value, src, sizeof value); + return value; +} + +static void unix_init_colors(void) +{ + alloc_colors64k(0, 8, 8, 8, 16, 8, 0, 8, 24, 1, 0); + notice_new_xcolors(); + alloc_colors_picasso(8, 8, 8, 16, 8, 0, RGBFB_R8G8B8A8, p96_rgbx16); +} + +static void unix_alloc_buffer(int monid, struct vidbuffer *buffer, int width, int height) +{ + if (buffer->realbufmem && buffer->width_allocated >= width && buffer->height_allocated >= height && + buffer->pixbytes == 4) { + return; + } + + freevidbuffer(monid, buffer); + allocvidbuffer(monid, buffer, width, height, 32); + buffer->initialized = true; +} + +static void unix_init_display_buffers(void) +{ + struct vidbuf_description *vidinfo = &adisplays[0].gfxvidinfo; + + vidinfo->gfx_resolution_reserved = RES_MAX; + vidinfo->gfx_vresolution_reserved = VRES_MAX; + vidinfo->xchange = 1; + vidinfo->ychange = 1; + + unix_alloc_buffer(0, &vidinfo->drawbuffer, 1920, 1280); + unix_alloc_buffer(0, &vidinfo->tempbuffer, 2048, 2048); + + vidinfo->drawbuffer.monitor_id = 0; + vidinfo->tempbuffer.monitor_id = 0; + vidinfo->outbuffer = &vidinfo->drawbuffer; + vidinfo->inbuffer = &vidinfo->drawbuffer; +} + +static int unix_apmode_index(int monid) +{ + if (monid >= 0 && monid < MAX_AMIGADISPLAYS && + (adisplays[monid].picasso_on || picasso_vidinfo[monid].picasso_active)) { + return APMODE_RTG; + } + return APMODE_NATIVE; +} + +static int unix_fullscreen_state(int fullscreen) +{ + if (fullscreen == GFX_FULLSCREEN) { + return 1; + } + if (fullscreen == GFX_FULLWINDOW) { + return -1; + } + return 0; +} + +static enum unix_video_window_mode unix_video_mode_from_prefs(int fullscreen) +{ + if (fullscreen == GFX_FULLSCREEN) { + return UNIX_VIDEO_FULLSCREEN; + } + if (fullscreen == GFX_FULLWINDOW) { + return UNIX_VIDEO_FULLWINDOW; + } + return UNIX_VIDEO_WINDOWED; +} + +static void unix_apply_video_mode_from_prefs(struct uae_prefs *prefs, int monid) +{ + if (monid < 0 || monid >= MAX_AMIGADISPLAYS) { + return; + } + + fixup_prefs_dimensions(prefs); + int idx = unix_apmode_index(monid); + struct apmode *ap = &prefs->gfx_apmode[idx]; + struct wh *size = ap->gfx_fullscreen == GFX_WINDOW + ? &prefs->gfx_monitor[monid].gfx_size_win + : &prefs->gfx_monitor[monid].gfx_size_fs; + + prefs->gfx_monitor[monid].gfx_size = *size; + int width = size->special == WH_NATIVE ? 0 : size->width; + int height = size->special == WH_NATIVE ? 0 : size->height; + unix_video_set_window_mode(unix_video_mode_from_prefs(ap->gfx_fullscreen), + ap->gfx_display, width, height, ap->gfx_refreshrate); +} + +static bool unix_runtime_graphics_prefs_changed(int monid) +{ + int idx = unix_apmode_index(monid); + + return currprefs.gfx_apmode[APMODE_NATIVE].gfx_fullscreen != changed_prefs.gfx_apmode[APMODE_NATIVE].gfx_fullscreen || + currprefs.gfx_apmode[APMODE_RTG].gfx_fullscreen != changed_prefs.gfx_apmode[APMODE_RTG].gfx_fullscreen || + currprefs.gfx_apmode[APMODE_NATIVE].gfx_display != changed_prefs.gfx_apmode[APMODE_NATIVE].gfx_display || + currprefs.gfx_apmode[APMODE_RTG].gfx_display != changed_prefs.gfx_apmode[APMODE_RTG].gfx_display || + currprefs.gfx_apmode[APMODE_NATIVE].gfx_backbuffers != changed_prefs.gfx_apmode[APMODE_NATIVE].gfx_backbuffers || + currprefs.gfx_apmode[APMODE_RTG].gfx_backbuffers != changed_prefs.gfx_apmode[APMODE_RTG].gfx_backbuffers || + currprefs.gfx_apmode[idx].gfx_refreshrate != changed_prefs.gfx_apmode[idx].gfx_refreshrate || + currprefs.gfx_monitor[monid].gfx_size_fs.width != changed_prefs.gfx_monitor[monid].gfx_size_fs.width || + currprefs.gfx_monitor[monid].gfx_size_fs.height != changed_prefs.gfx_monitor[monid].gfx_size_fs.height || + currprefs.gfx_monitor[monid].gfx_size_fs.special != changed_prefs.gfx_monitor[monid].gfx_size_fs.special || + currprefs.gfx_monitor[monid].gfx_size_win.width != changed_prefs.gfx_monitor[monid].gfx_size_win.width || + currprefs.gfx_monitor[monid].gfx_size_win.height != changed_prefs.gfx_monitor[monid].gfx_size_win.height || + currprefs.gfx_monitor[monid].gfx_size_win.special != changed_prefs.gfx_monitor[monid].gfx_size_win.special; +} + +static void unix_copy_runtime_graphics_prefs(int monid) +{ + currprefs.gfx_apmode[APMODE_NATIVE].gfx_fullscreen = changed_prefs.gfx_apmode[APMODE_NATIVE].gfx_fullscreen; + currprefs.gfx_apmode[APMODE_RTG].gfx_fullscreen = changed_prefs.gfx_apmode[APMODE_RTG].gfx_fullscreen; + currprefs.gfx_apmode[APMODE_NATIVE].gfx_display = changed_prefs.gfx_apmode[APMODE_NATIVE].gfx_display; + currprefs.gfx_apmode[APMODE_RTG].gfx_display = changed_prefs.gfx_apmode[APMODE_RTG].gfx_display; + currprefs.gfx_apmode[APMODE_NATIVE].gfx_backbuffers = changed_prefs.gfx_apmode[APMODE_NATIVE].gfx_backbuffers; + currprefs.gfx_apmode[APMODE_RTG].gfx_backbuffers = changed_prefs.gfx_apmode[APMODE_RTG].gfx_backbuffers; + currprefs.gfx_apmode[APMODE_NATIVE].gfx_refreshrate = changed_prefs.gfx_apmode[APMODE_NATIVE].gfx_refreshrate; + currprefs.gfx_apmode[APMODE_RTG].gfx_refreshrate = changed_prefs.gfx_apmode[APMODE_RTG].gfx_refreshrate; + currprefs.gfx_monitor[monid].gfx_size_fs = changed_prefs.gfx_monitor[monid].gfx_size_fs; + currprefs.gfx_monitor[monid].gfx_size_win = changed_prefs.gfx_monitor[monid].gfx_size_win; +} + +int graphics_setup(void) +{ + unix_video_debug = getenv("WINUAE_UNIX_VIDEO_DEBUG") != NULL; + unix_init_colors(); + if (unix_video_debug) { + write_log(_T("Unix video colors: direct_rgb=%d black=%08x white=%08x r255=%08x g255=%08x b255=%08x\n"), + direct_rgb ? 1 : 0, xcolors[0], xcolors[0xfff], xredcolors[255], xgreencolors[255], xbluecolors[255]); + } + InitPicasso96(0); + return 1; +} + +int graphics_init(bool) +{ + unix_init_display_buffers(); + struct vidbuffer *vb = &adisplays[0].gfxvidinfo.drawbuffer; + if (!unix_video_init(vb->outwidth, vb->outheight, vb->pixbytes)) { + write_log(_T("Unix video: no window presenter available, continuing headless\n")); + } + unix_apply_video_mode_from_prefs(&currprefs, 0); + unix_graphics_initialized = true; + return 1; +} + +void graphics_leave(void) +{ +#ifdef AVIOUTPUT + AVIOutput_Release(); +#endif + unix_rtg_stop_render_thread(); + struct vidbuf_description *vidinfo = &adisplays[0].gfxvidinfo; + freevidbuffer(0, &vidinfo->drawbuffer); + freevidbuffer(0, &vidinfo->tempbuffer); + unix_video_shutdown(); + unix_graphics_initialized = false; +} + +void graphics_reset(bool) {} + +bool handle_events(void) +{ + handle_msgpump(false); + return pause_emulation != 0; +} + +int handle_msgpump(bool) +{ + unix_host_check_quit(); + bool quit_requested = false; + int got = unix_video_poll(&quit_requested); + if (quit_requested) { + uae_quit(); + } + unix_host_check_quit(); + return got; +} + +int check_prefs_changed_gfx(void) +{ + int flags = config_changed_flags; + bool changed = unix_runtime_graphics_prefs_changed(0); + + if (!unix_graphics_initialized || (!changed && !flags)) { + return 0; + } + + config_changed_flags = 0; + if (changed) { + unix_copy_runtime_graphics_prefs(0); + unix_apply_video_mode_from_prefs(&currprefs, 0); + } + return 1; +} + +int isfullscreen(void) +{ + int idx = unix_apmode_index(0); + return unix_fullscreen_state(currprefs.gfx_apmode[idx].gfx_fullscreen); +} + +void toggle_fullscreen(int monid, int mode) +{ + if (monid < 0 || monid >= MAX_AMIGADISPLAYS) { + return; + } + + int idx = unix_apmode_index(monid); + int v = changed_prefs.gfx_apmode[idx].gfx_fullscreen; + static int wasfs[2]; + + if (mode < 0) { + if (v == GFX_FULLWINDOW) { + wasfs[idx] = -1; + v = GFX_WINDOW; + } else if (v == GFX_WINDOW) { + v = wasfs[idx] >= 0 ? GFX_FULLSCREEN : GFX_FULLWINDOW; + } else if (v == GFX_FULLSCREEN) { + wasfs[idx] = 1; + v = GFX_WINDOW; + } + } else if (mode == 0) { + v = v == GFX_FULLSCREEN ? GFX_WINDOW : GFX_FULLSCREEN; + } else if (mode == 1) { + v = v == GFX_FULLSCREEN ? GFX_FULLWINDOW : GFX_FULLSCREEN; + } else if (mode == 2) { + v = v == GFX_FULLWINDOW ? GFX_WINDOW : GFX_FULLWINDOW; + } else if (mode == 10) { + v = GFX_WINDOW; + } + + changed_prefs.gfx_apmode[idx].gfx_fullscreen = v; + devices_unsafeperiod(); + set_config_changed(); +} +bool toggle_rtg(int monid, int) +{ + return monid >= 0 && monid < MAX_AMIGAMONITORS && currprefs.rtgboards[0].rtgmem_size > 0; +} +void close_rtg(int, bool) {} +void toggle_mousegrab(void) { unix_video_toggle_mouse_grab(); } +void setmouseactivexy(int, int, int, int) {} + +void desktop_coords(int, int *dw, int *dh, int *x, int *y, int *w, int *h) +{ + unix_video_get_desktop(dw, dh, x, y, w, h); +} + +bool vsync_switchmode(int, int) { return false; } +void vsync_clear(void) {} +int vsync_isdone(frame_time_t*) { return 1; } +void doflashscreen(void) {} +void updatedisplayarea(int) {} +void flush_line(struct vidbuffer*, int) {} +void flush_block(struct vidbuffer*, int, int) {} +void flush_screen(struct vidbuffer*, int, int) {} +void flush_clear_screen(struct vidbuffer*) {} +bool render_screen(int, int, bool) +{ + set_custom_limits(-1, -1, -1, -1, false); + return true; +} + +static void unix_log_video_frame(const struct vidbuffer *vb) +{ + static int frames; + + if (!unix_video_debug || !vb || !vb->bufmem || vb->pixbytes != 4) { + return; + } + + frames++; + if (frames > 1 && (frames % 50) != 0) { + return; + } + + int nonblack = 0; + int firstx = -1; + int firsty = -1; + int lastx = -1; + int lasty = -1; + uae_u32 first = 0; + uae_u32 last = 0; + + int scan_width = vb->width_allocated > 0 ? vb->width_allocated : vb->outwidth; + int scan_height = vb->height_allocated > 0 ? vb->height_allocated : vb->outheight; + for (int y = 0; y < scan_height; y++) { + const uae_u32 *row = (const uae_u32 *)(vb->bufmem + y * vb->rowbytes); + for (int x = 0; x < scan_width; x++) { + uae_u32 pixel = row[x]; + if ((pixel & 0x00ffffff) != 0) { + if (!nonblack) { + firstx = x; + firsty = y; + first = pixel; + } + lastx = x; + lasty = y; + last = pixel; + nonblack++; + } + } + } + + write_log(_T("Unix video frame %d: out=%dx%d alloc=%dx%d pitch=%d xoff=%d yoff=%d nonblack=%d first=%d,%d:%08x last=%d,%d:%08x\n"), + frames, vb->outwidth, vb->outheight, vb->width_allocated, vb->height_allocated, vb->rowbytes, vb->xoffset, vb->yoffset, + nonblack, firstx, firsty, first, lastx, lasty, last); +} + +void show_screen(int monid, int) +{ + if (!unix_graphics_initialized || monid < 0 || monid >= MAX_AMIGADISPLAYS) { + return; + } + + struct vidbuf_description *vidinfo = &adisplays[monid].gfxvidinfo; + struct vidbuffer *vb = vidinfo->inbuffer ? vidinfo->inbuffer : &vidinfo->drawbuffer; + if (!vb->bufmem || vb->outwidth <= 0 || vb->outheight <= 0) { + return; + } + + struct unix_video_frame frame; + frame.pixels = vb->bufmem; + frame.width = vb->outwidth; + frame.height = vb->outheight; + frame.rowbytes = vb->rowbytes; + frame.pixbytes = vb->pixbytes; + frame.filter_index = adisplays[monid].gf_index; + frame.monitor_id = monid; + frame.backbuffers = currprefs.gfx_apmode[unix_apmode_index(monid)].gfx_backbuffers; + unix_log_video_frame(vb); + unix_video_present(&frame); +} + +bool show_screen_maybe(int monid, bool) +{ + show_screen(monid, 0); + return true; +} + +int lockscr(struct vidbuffer *vb, bool, bool) +{ + if (!vb || !vb->bufmem) { + return 0; + } + vb->locked = true; + return 1; +} + +void unlockscr(struct vidbuffer *vb, int, int) +{ + if (vb) { + vb->locked = false; + } +} + +bool target_graphics_buffer_update(int, bool) { return true; } +float target_adjust_vblank_hz(int, float hz) { return hz; } +int target_get_display_scanline(int) { return -1; } +void target_spin(int) +{ + static int spin_counter; + if ((spin_counter++ & 31) == 0 || unix_host_quit_requested()) { + handle_msgpump(false); + } +} + +void getgfxoffset(int, float *dxp, float *dyp, float *mxp, float *myp) +{ + if (dxp) *dxp = 0; + if (dyp) *dyp = 0; + if (mxp) *mxp = 1; + if (myp) *myp = 1; +} + +float target_getcurrentvblankrate(int monid) +{ + int idx = unix_apmode_index(monid); + float rate = unix_video_get_display_refresh_rate(currprefs.gfx_apmode[idx].gfx_display); + return rate > 0.0f ? rate : 60.0f; +} +int debuggable(void) { return 0; } + +void refreshtitle(void) +{ + unix_video_set_title(_T(WINUAE_UNIX_WINDOW_TITLE)); +} + +void InitPicasso96(int monid) +{ + if (monid < 0 || monid >= MAX_AMIGAMONITORS) { + return; + } + + memset(&picasso96_state[monid], 0, sizeof picasso96_state[monid]); + memset(&picasso_vidinfo[monid], 0, sizeof picasso_vidinfo[monid]); + unix_rtg_render_has_output[monid] = false; + picasso_vidinfo[monid].rgbformat = RGBFB_B8G8R8A8; + picasso_vidinfo[monid].selected_rgbformat = RGBFB_B8G8R8A8; + picasso_vidinfo[monid].host_mode = RGBFB_B8G8R8A8; + picasso_vidinfo[monid].pixbytes = 4; + for (int i = 0; i < 256; i++) { + picasso96_state[monid].CLUT[i].Red = i; + picasso96_state[monid].CLUT[i].Green = i; + picasso96_state[monid].CLUT[i].Blue = i; + picasso_vidinfo[monid].clut[i] = 0xff000000 | (i << 16) | (i << 8) | i; + } +} + +static bool unix_picasso_ensure_buffer(int monid) +{ + struct picasso96_state_struct *state = &picasso96_state[monid]; + struct picasso_vidbuf_description *pvidinfo = &picasso_vidinfo[monid]; + struct vidbuf_description *vidinfo = &adisplays[monid].gfxvidinfo; + int width = state->Width > 0 ? state->Width : 640; + int height = state->Height > 0 ? state->Height : 480; + + unix_alloc_buffer(monid, &vidinfo->drawbuffer, width, height); + vidinfo->drawbuffer.outwidth = width; + vidinfo->drawbuffer.outheight = height; + vidinfo->drawbuffer.pixbytes = 4; + vidinfo->drawbuffer.monitor_id = monid; + vidinfo->inbuffer = &vidinfo->drawbuffer; + vidinfo->outbuffer = &vidinfo->drawbuffer; + + pvidinfo->width = width; + pvidinfo->height = height; + pvidinfo->depth = state->GC_Depth ? (state->GC_Depth + 7) / 8 : 0; + pvidinfo->pixbytes = 4; + pvidinfo->rowbytes = vidinfo->drawbuffer.rowbytes; + pvidinfo->maxwidth = vidinfo->drawbuffer.width_allocated; + pvidinfo->maxheight = vidinfo->drawbuffer.height_allocated; + pvidinfo->rgbformat = state->RGBFormat; + pvidinfo->selected_rgbformat = state->RGBFormat; + pvidinfo->host_mode = RGBFB_B8G8R8A8; + + return vidinfo->drawbuffer.bufmem != NULL; +} + +static uae_u32 unix_picasso_convert_pixel(const uae_u8 *src, RGBFTYPE fmt, + const uae_u32 *clut, const uae_u32 *rgbx16 = p96_rgbx16) +{ + switch (fmt) { + case RGBFB_CLUT: + return clut[src[0]]; + case RGBFB_R8G8B8: + return 0xff000000 | ((uae_u32)src[0] << 16) | ((uae_u32)src[1] << 8) | src[2]; + case RGBFB_B8G8R8: + return 0xff000000 | ((uae_u32)src[2] << 16) | ((uae_u32)src[1] << 8) | src[0]; + case RGBFB_R8G8B8A8: + return ((uae_u32)src[3] << 24) | ((uae_u32)src[0] << 16) | ((uae_u32)src[1] << 8) | src[2]; + case RGBFB_B8G8R8A8: + return ((uae_u32)src[3] << 24) | ((uae_u32)src[2] << 16) | ((uae_u32)src[1] << 8) | src[0]; + case RGBFB_A8R8G8B8: + return ((uae_u32)src[0] << 24) | ((uae_u32)src[1] << 16) | ((uae_u32)src[2] << 8) | src[3]; + case RGBFB_A8B8G8R8: + return ((uae_u32)src[0] << 24) | ((uae_u32)src[3] << 16) | ((uae_u32)src[2] << 8) | src[1]; + case RGBFB_R5G6B5: + case RGBFB_R5G5B5: + case RGBFB_R5G6B5PC: + case RGBFB_R5G5B5PC: + case RGBFB_B5G6R5PC: + case RGBFB_B5G5R5PC: + return rgbx16[unix_picasso_load_host_u16(src)]; + default: + return 0xff000000; + } +} + +enum { + RGBFB_A8R8G8B8_32 = 1, + RGBFB_A8B8G8R8_32, + RGBFB_R8G8B8A8_32, + RGBFB_B8G8R8A8_32, + RGBFB_R8G8B8_32, + RGBFB_B8G8R8_32, + RGBFB_R5G6B5PC_32, + RGBFB_R5G5B5PC_32, + RGBFB_R5G6B5_32, + RGBFB_R5G5B5_32, + RGBFB_B5G6R5PC_32, + RGBFB_B5G5R5PC_32, + RGBFB_CLUT_RGBFB_32, + RGBFB_Y4U2V2_32, + RGBFB_Y4U1V1_32, +}; + +static void unix_picasso_render_pixels(const uae_u8 *srcbase, int width, int height, + int srcrowbytes, int srcpixbytes, RGBFTYPE rgbfmt, const uae_u32 *clut, + uae_u8 *dstbase, int dstrowbytes, const uae_u32 *rgbx16 = p96_rgbx16); + +int getconvert(int rgbformat) +{ + switch (rgbformat) { + case RGBFB_CLUT: + return RGBFB_CLUT_RGBFB_32; + case RGBFB_B5G6R5PC: + return RGBFB_B5G6R5PC_32; + case RGBFB_R5G6B5PC: + return RGBFB_R5G6B5PC_32; + case RGBFB_R5G5B5PC: + return RGBFB_R5G5B5PC_32; + case RGBFB_R5G6B5: + return RGBFB_R5G6B5_32; + case RGBFB_R5G5B5: + return RGBFB_R5G5B5_32; + case RGBFB_B5G5R5PC: + return RGBFB_B5G5R5PC_32; + case RGBFB_A8R8G8B8: + return RGBFB_A8R8G8B8_32; + case RGBFB_R8G8B8: + return RGBFB_R8G8B8_32; + case RGBFB_B8G8R8: + return RGBFB_B8G8R8_32; + case RGBFB_A8B8G8R8: + return RGBFB_A8B8G8R8_32; + case RGBFB_B8G8R8A8: + return RGBFB_B8G8R8A8_32; + case RGBFB_R8G8B8A8: + return RGBFB_R8G8B8A8_32; + case RGBFB_Y4U2V2: + return RGBFB_Y4U2V2_32; + case RGBFB_Y4U1V1: + return RGBFB_Y4U1V1_32; + default: + return 0; + } +} + +static uae_u16 unix_yuv_to_rgb16(uae_u8 yx, uae_u8 ux, uae_u8 vx) +{ + int y = yx - 16; + int u = ux - 128; + int v = vx - 128; + int r = (298 * y + 409 * v + 128) >> (8 + 3); + int g = (298 * y - 100 * u - 208 * v + 128) >> (8 + 3); + int b = (298 * y + 516 * u + 128) >> (8 + 3); + if (r < 0) { + r = 0; + } else if (r > 31) { + r = 31; + } + if (g < 0) { + g = 0; + } else if (g > 31) { + g = 31; + } + if (b < 0) { + b = 0; + } else if (b > 31) { + b = 31; + } + return (r << 10) | (g << 5) | b; +} + +static bool unix_picasso_colorkey_matches(const uae_u8 *screen, int dx, int screenpixbytes, + uae_u32 colorkey) +{ + if (!screen || dx < 0 || screenpixbytes <= 0) { + return false; + } + switch (screenpixbytes) { + case 1: + return screen[dx] == (uae_u8)colorkey; + case 2: + return do_get_mem_word((uae_u16 *)(screen + dx * 2)) == (uae_u16)colorkey; + case 3: + return screen[dx * 3 + 0] == (uae_u8)(colorkey >> 16) && + screen[dx * 3 + 1] == (uae_u8)(colorkey >> 8) && + screen[dx * 3 + 2] == (uae_u8)colorkey; + case 4: + return do_get_mem_long((uae_u32 *)(screen + dx * 4)) == colorkey; + default: + return false; + } +} + +static void unix_picasso_store_scaled_pixel(uae_u8 *dst, int dx, int dstpixbytes, uae_u32 color) +{ + switch (dstpixbytes) { + case 1: + dst[dx] = (uae_u8)color; + break; + case 2: + do_put_mem_word((uae_u16 *)(dst + dx * 2), (uae_u16)color); + break; + case 3: + dst[dx * 3 + 0] = (uae_u8)(color >> 16); + dst[dx * 3 + 1] = (uae_u8)(color >> 8); + dst[dx * 3 + 2] = (uae_u8)color; + break; + case 4: + do_put_mem_long((uae_u32 *)(dst + dx * 4), color); + break; + } +} + +static uae_u32 unix_picasso_convert_scaled_pixel(const uae_u8 *src, int x, int sxfrac, + int convert_mode, const uae_u32 *rgbx16, const uae_u32 *clut, bool yuv_swap) +{ + switch (convert_mode) { + case RGBFB_R8G8B8_32: + return 0xff000000 | ((uae_u32)src[x * 3 + 0] << 16) | + ((uae_u32)src[x * 3 + 1] << 8) | src[x * 3 + 2]; + case RGBFB_B8G8R8_32: + return 0xff000000 | ((uae_u32)src[x * 3 + 2] << 16) | + ((uae_u32)src[x * 3 + 1] << 8) | src[x * 3 + 0]; + case RGBFB_R8G8B8A8_32: + return 0xff000000 | ((uae_u32)src[x * 4 + 0] << 16) | + ((uae_u32)src[x * 4 + 1] << 8) | src[x * 4 + 2]; + case RGBFB_A8R8G8B8_32: + return ((uae_u32)src[x * 4 + 0] << 24) | ((uae_u32)src[x * 4 + 1] << 16) | + ((uae_u32)src[x * 4 + 2] << 8) | src[x * 4 + 3]; + case RGBFB_A8B8G8R8_32: + return ((uae_u32)src[x * 4 + 0] << 24) | ((uae_u32)src[x * 4 + 3] << 16) | + ((uae_u32)src[x * 4 + 2] << 8) | src[x * 4 + 1]; + case RGBFB_B8G8R8A8_32: + return ((uae_u32)src[x * 4 + 3] << 24) | ((uae_u32)src[x * 4 + 2] << 16) | + ((uae_u32)src[x * 4 + 1] << 8) | src[x * 4 + 0]; + case RGBFB_R5G6B5PC_32: + case RGBFB_R5G5B5PC_32: + case RGBFB_R5G6B5_32: + case RGBFB_R5G5B5_32: + case RGBFB_B5G6R5PC_32: + case RGBFB_B5G5R5PC_32: + return rgbx16 ? rgbx16[unix_picasso_load_host_u16(src + x * 2)] : 0xff000000; + case RGBFB_CLUT_RGBFB_32: + return clut ? clut[src[x]] : (0xff000000 | src[x] | ((uae_u32)src[x] << 8) | + ((uae_u32)src[x] << 16)); + case RGBFB_Y4U2V2_32: + { + uae_u32 val = do_get_mem_long((uae_u32 *)(src + x * 4)); + if (yuv_swap) { + val = ((val & 0xff00ff00) >> 8) | ((val & 0x00ff00ff) << 8); + } + uae_u8 y = (sxfrac & 0x80) ? (uae_u8)(val >> 24) : (uae_u8)(val >> 8); + uae_u8 u = (uae_u8)val; + uae_u8 v = (uae_u8)(val >> 16); + uae_u16 rgb = unix_yuv_to_rgb16(y, u, v); + return rgbx16 ? rgbx16[rgb] : 0xff000000; + } + case RGBFB_Y4U1V1_32: + { + uae_u32 val = do_get_mem_long((uae_u32 *)(src + x * 4)); + if (yuv_swap) { + val = ((val & 0xff00ff00) >> 8) | ((val & 0x00ff00ff) << 8); + } + uae_u8 y[4] = { + (uae_u8)(val >> 24), (uae_u8)(val >> 18), + (uae_u8)(val >> 12), (uae_u8)(val >> 6) + }; + uae_u8 u = (uae_u8)((val >> 3) & 7); + uae_u8 v = (uae_u8)(val & 7); + uae_u16 rgb = unix_yuv_to_rgb16(y[(sxfrac >> 6) & 3], u << 5, v << 5); + return rgbx16 ? rgbx16[rgb] : 0xff000000; + } + default: + return 0xff000000; + } +} + +void copyrow_scale(int, uae_u8 *src, uae_u8 *src_screen, uae_u8 *dst, + int sx, int sy, int sxadd, int width, int srcbytesperrow, int, + int screenbytesperrow, int screenpixbytes, + int dx, int dy, int dstwidth, int dstheight, int dstbytesperrow, int dstpixbytes, + bool ck, uae_u32 colorkey, int convert_mode, uae_u32 *p96_rgbx16p, + uae_u32 *clut, bool yuv_swap) +{ + if (!src || !dst || sxadd <= 0 || width <= 0 || dx < 0 || dy < 0 || + dx >= dstwidth || dy >= dstheight || dstpixbytes <= 0) { + return; + } + + uae_u8 *srcrow = src + sy * srcbytesperrow; + uae_u8 *dstrow = dst + dy * dstbytesperrow; + uae_u8 *screenrow = src_screen ? src_screen + dy * screenbytesperrow : NULL; + int endx = (sx + width) << 8; + + if (convert_mode == RGBFB_Y4U2V2_32) { + endx /= 2; + sxadd /= 2; + } else if (convert_mode == RGBFB_Y4U1V1_32) { + endx /= 4; + sxadd /= 4; + } + + while (sx < endx && dx < dstwidth) { + int x = sx >> 8; + if (!ck || unix_picasso_colorkey_matches(screenrow, dx, screenpixbytes, colorkey)) { + uae_u32 color = unix_picasso_convert_scaled_pixel(srcrow, x, sx & 255, + convert_mode, p96_rgbx16p, clut, yuv_swap); + unix_picasso_store_scaled_pixel(dstrow, dx, dstpixbytes, color); + } + sx += sxadd; + dx++; + } +} + +uae_u8 *uaegfx_getrtgbuffer(int monid, int *widthp, int *heightp, int *pitch, int *depth, + uae_u8 *palette) +{ + if (monid < 0 || monid >= MAX_AMIGAMONITORS) { + monid = 0; + } + int bankid = monid < MAX_RTG_BOARDS && gfxmem_banks[monid] ? monid : 0; + addrbank *bank = gfxmem_banks[bankid]; + struct picasso96_state_struct *state = &picasso96_state[monid]; + struct picasso_vidbuf_description *pvidinfo = &picasso_vidinfo[monid]; + int width = state->VirtualWidth ? state->VirtualWidth : state->Width; + int height = state->VirtualHeight ? state->VirtualHeight : state->Height; + int srcpixbytes = unix_picasso_bytes_per_pixel((RGBFTYPE)state->RGBFormat); + int dstpixbytes = state->BytesPerPixel == 1 && palette ? 1 : 4; + + if (!bank || !bank->baseaddr || width <= 0 || height <= 0 || srcpixbytes <= 0 || + state->BytesPerRow <= 0 || dstpixbytes <= 0 || (uae_u32)state->XYOffset < bank->start) { + return NULL; + } + uae_u32 offset = (uae_u32)state->XYOffset - bank->start; + uae_u32 needed = offset + (height - 1) * state->BytesPerRow + width * srcpixbytes; + if (needed > bank->allocated_size) { + return NULL; + } + + uae_u8 *dst = xmalloc(uae_u8, (size_t)width * (size_t)height * (size_t)dstpixbytes); + if (!dst) { + return NULL; + } + if (dstpixbytes == 1) { + for (int y = 0; y < height; y++) { + memcpy(dst + (size_t)y * (size_t)width, + bank->baseaddr + offset + (size_t)y * (size_t)state->BytesPerRow, + (size_t)width); + } + for (int i = 0; i < 256; i++) { + palette[i * 3 + 0] = state->CLUT[i].Red; + palette[i * 3 + 1] = state->CLUT[i].Green; + palette[i * 3 + 2] = state->CLUT[i].Blue; + } + } else { + alloc_colors_picasso(8, 8, 8, 16, 8, 0, (RGBFTYPE)state->RGBFormat, p96_rgbx16); + unix_picasso_render_pixels(bank->baseaddr + offset, width, height, state->BytesPerRow, + srcpixbytes, (RGBFTYPE)state->RGBFormat, pvidinfo->clut, dst, width * dstpixbytes); + } + + *widthp = width; + *heightp = height; + *pitch = width * dstpixbytes; + *depth = dstpixbytes * 8; + return dst; +} + +void uaegfx_freertgbuffer(int, uae_u8 *dst) +{ + xfree(dst); +} + +static void unix_picasso_render_pixels(const uae_u8 *srcbase, int width, int height, + int srcrowbytes, int srcpixbytes, RGBFTYPE rgbfmt, const uae_u32 *clut, + uae_u8 *dstbase, int dstrowbytes, const uae_u32 *rgbx16) +{ + for (int y = 0; y < height; y++) { + const uae_u8 *src = srcbase + y * srcrowbytes; + uae_u32 *dst = (uae_u32 *)(dstbase + y * dstrowbytes); + for (int x = 0; x < width; x++) { + dst[x] = unix_picasso_convert_pixel(src + x * srcpixbytes, rgbfmt, clut, rgbx16); + } + } +} + +struct unix_rtg_render_job +{ + int monid; + int width; + int height; + int srcrowbytes; + int srcpixbytes; + RGBFTYPE rgbfmt; + uae_u32 clut[256]; + std::vector src; + std::vector rgbx16; +}; + +struct unix_rtg_render_result +{ + int monid; + int width; + int height; + std::vector pixels; +}; + +static std::thread unix_rtg_render_thread; +static std::mutex unix_rtg_render_mutex; +static std::condition_variable unix_rtg_render_cv; +static bool unix_rtg_render_stop; +static bool unix_rtg_render_has_job; +static bool unix_rtg_render_busy; +static bool unix_rtg_render_ready; +static unix_rtg_render_job unix_rtg_pending_job; +static unix_rtg_render_result unix_rtg_ready_result; + +static void unix_rtg_render_worker(void) +{ + for (;;) { + unix_rtg_render_job job; + { + std::unique_lock lock(unix_rtg_render_mutex); + unix_rtg_render_cv.wait(lock, [] { + return unix_rtg_render_stop || unix_rtg_render_has_job; + }); + if (unix_rtg_render_stop && !unix_rtg_render_has_job) { + return; + } + job = std::move(unix_rtg_pending_job); + unix_rtg_render_has_job = false; + unix_rtg_render_busy = true; + } + + unix_rtg_render_result result; + result.monid = job.monid; + result.width = job.width; + result.height = job.height; + result.pixels.assign((size_t)job.width * (size_t)job.height * sizeof(uae_u32), 0); + unix_picasso_render_pixels(job.src.data(), job.width, job.height, job.srcrowbytes, + job.srcpixbytes, job.rgbfmt, job.clut, result.pixels.data(), + job.width * (int)sizeof(uae_u32), + job.rgbx16.empty() ? p96_rgbx16 : job.rgbx16.data()); + + { + std::lock_guard lock(unix_rtg_render_mutex); + unix_rtg_ready_result = std::move(result); + unix_rtg_render_ready = true; + unix_rtg_render_busy = false; + } + } +} + +static bool unix_rtg_start_render_thread_locked(void) +{ + if (unix_rtg_render_thread.joinable()) { + return true; + } + try { + unix_rtg_render_stop = false; + unix_rtg_render_thread = std::thread(unix_rtg_render_worker); + } catch (...) { + return false; + } + return true; +} + +static void unix_rtg_stop_render_thread(void) +{ + { + std::lock_guard lock(unix_rtg_render_mutex); + unix_rtg_render_stop = true; + unix_rtg_render_has_job = false; + } + unix_rtg_render_cv.notify_all(); + if (unix_rtg_render_thread.joinable()) { + unix_rtg_render_thread.join(); + } + { + std::lock_guard lock(unix_rtg_render_mutex); + unix_rtg_pending_job = unix_rtg_render_job(); + unix_rtg_ready_result = unix_rtg_render_result(); + unix_rtg_render_ready = false; + unix_rtg_render_busy = false; + } + memset(unix_rtg_render_has_output, 0, sizeof unix_rtg_render_has_output); +} + +static bool unix_rtg_collect_render_result(int monid, struct vidbuffer *vb) +{ + unix_rtg_render_result result; + { + std::lock_guard lock(unix_rtg_render_mutex); + if (!unix_rtg_render_ready || unix_rtg_ready_result.monid != monid) { + return false; + } + result = std::move(unix_rtg_ready_result); + unix_rtg_render_ready = false; + } + if (!vb || !vb->bufmem || result.width != vb->outwidth || result.height != vb->outheight) { + return false; + } + for (int y = 0; y < result.height; y++) { + memcpy(vb->bufmem + y * vb->rowbytes, + result.pixels.data() + (size_t)y * (size_t)result.width * sizeof(uae_u32), + (size_t)result.width * sizeof(uae_u32)); + } + unix_rtg_overlay_sprite(monid, (uae_u32 *)vb->bufmem, result.width, result.height, + vb->rowbytes / sizeof(uae_u32)); + unix_rtg_render_has_output[monid] = true; + return true; +} + +static bool unix_rtg_submit_render_job(int monid, const uae_u8 *srcbase, int width, int height, + int srcrowbytes, int srcpixbytes, RGBFTYPE rgbfmt, const uae_u32 *clut) +{ + if (!srcbase || width <= 0 || height <= 0 || srcrowbytes <= 0 || srcpixbytes <= 0) { + return false; + } + + unix_rtg_render_job job; + job.monid = monid; + job.width = width; + job.height = height; + job.srcrowbytes = width * srcpixbytes; + job.srcpixbytes = srcpixbytes; + job.rgbfmt = rgbfmt; + memcpy(job.clut, clut, sizeof job.clut); + if (srcpixbytes == 2) { + job.rgbx16.assign(p96_rgbx16, p96_rgbx16 + 65536); + } + job.src.assign((size_t)job.srcrowbytes * (size_t)height, 0); + for (int y = 0; y < height; y++) { + memcpy(job.src.data() + (size_t)y * (size_t)job.srcrowbytes, + srcbase + (size_t)y * (size_t)srcrowbytes, (size_t)job.srcrowbytes); + } + + { + std::lock_guard lock(unix_rtg_render_mutex); + if (!unix_rtg_start_render_thread_locked() || unix_rtg_render_has_job || + unix_rtg_render_busy || unix_rtg_render_ready) { + return false; + } + unix_rtg_pending_job = std::move(job); + unix_rtg_render_has_job = true; + } + unix_rtg_render_cv.notify_one(); + return true; +} + +static void unix_picasso_render(int monid) +{ + if (monid < 0 || monid >= MAX_AMIGAMONITORS || !unix_picasso_ensure_buffer(monid)) { + return; + } + + struct picasso96_state_struct *state = &picasso96_state[monid]; + struct picasso_vidbuf_description *pvidinfo = &picasso_vidinfo[monid]; + struct vidbuffer *vb = &adisplays[monid].gfxvidinfo.drawbuffer; + addrbank *bank = gfxmem_banks[0]; + int srcpixbytes = unix_picasso_bytes_per_pixel((RGBFTYPE)state->RGBFormat); + + if (!bank || !bank->baseaddr || !srcpixbytes || !state->Address || !state->BytesPerRow || + !state->Width || !state->Height) { + return; + } + if ((uae_u32)state->XYOffset < bank->start) { + return; + } + + uae_u32 offset = (uae_u32)state->XYOffset - bank->start; + uae_u32 needed = offset + (state->Height - 1) * state->BytesPerRow + state->Width * srcpixbytes; + if (needed > bank->allocated_size) { + return; + } + + alloc_colors_picasso(8, 8, 8, 16, 8, 0, (RGBFTYPE)state->RGBFormat, p96_rgbx16); + const uae_u8 *srcbase = bank->baseaddr + offset; + if (currprefs.rtg_multithread) { + bool collected = unix_rtg_collect_render_result(monid, vb); + bool submitted = unix_rtg_submit_render_job(monid, srcbase, state->Width, state->Height, + state->BytesPerRow, srcpixbytes, (RGBFTYPE)state->RGBFormat, pvidinfo->clut); + if (collected || submitted) { + if (unix_rtg_render_has_output[monid]) { + return; + } + } + } + unix_picasso_render_pixels(srcbase, state->Width, state->Height, state->BytesPerRow, + srcpixbytes, (RGBFTYPE)state->RGBFormat, pvidinfo->clut, vb->bufmem, vb->rowbytes); + unix_rtg_overlay_sprite(monid, (uae_u32 *)vb->bufmem, state->Width, state->Height, vb->rowbytes / sizeof(uae_u32)); + unix_rtg_render_has_output[monid] = true; +} + +void picasso_enablescreen(int monid, int on) +{ + if (monid < 0 || monid >= MAX_AMIGAMONITORS) { + return; + } + picasso_vidinfo[monid].picasso_active = on != 0; + unix_apply_video_mode_from_prefs(&currprefs, monid); + if (on) { + picasso_refresh(monid); + } +} + +void picasso_refresh(int monid) +{ + if (monid < 0 || monid >= MAX_AMIGAMONITORS) { + return; + } + if (currprefs.rtgboards[0].rtgmem_type >= GFXBOARD_HARDWARE) { + gfxboard_refresh(monid); + return; + } + unix_picasso_render(monid); + if (adisplays[monid].picasso_on || picasso_vidinfo[monid].picasso_active) { +#ifdef AVIOUTPUT + frame_drawn(monid); +#endif + show_screen(monid, 0); + } +} +void init_hz_p96(int) {} + +void gfx_set_picasso_modeinfo(int monid, RGBFTYPE rgbfmt) +{ + if (monid < 0 || monid >= MAX_AMIGAMONITORS) { + return; + } + struct picasso96_state_struct *state = &picasso96_state[monid]; + picasso_vidinfo[monid].rgbformat = rgbfmt; + picasso_vidinfo[monid].selected_rgbformat = rgbfmt; + picasso_vidinfo[monid].depth = state->GC_Depth ? (state->GC_Depth + 7) / 8 : 0; + picasso_vidinfo[monid].picasso_changed = true; + unix_picasso_ensure_buffer(monid); +} + +void gfx_set_picasso_colors(int monid, RGBFTYPE rgbfmt) +{ + if (monid >= 0 && monid < MAX_AMIGAMONITORS) { + picasso_vidinfo[monid].rgbformat = rgbfmt; + } +} + +void gfx_set_picasso_state(int monid, int on) +{ + if (monid >= 0 && monid < MAX_AMIGAMONITORS) { + picasso_vidinfo[monid].picasso_active = on != 0; + } +} + +uae_u8 *gfx_lock_picasso(int monid, bool) +{ + if (monid < 0 || monid >= MAX_AMIGAMONITORS || !unix_picasso_ensure_buffer(monid)) { + return NULL; + } + struct vidbuffer *vb = &adisplays[monid].gfxvidinfo.drawbuffer; + vb->locked = true; + return vb->bufmem; +} + +void gfx_unlock_picasso(int monid, bool dorender) +{ + if (monid < 0 || monid >= MAX_AMIGAMONITORS) { + return; + } + struct vidbuffer *vb = &adisplays[monid].gfxvidinfo.drawbuffer; + vb->locked = false; + if (dorender) { + show_screen(monid, 0); + } +} +void picasso_invalidate(int, int, int, int, int) {} + +void picasso_handle_vsync(void) +{ + for (int monid = 0; monid < MAX_AMIGAMONITORS; monid++) { + struct amigadisplay *ad = &adisplays[monid]; + struct picasso_vidbuf_description *vidinfo = &picasso_vidinfo[monid]; + int state = vidinfo->picasso_state_change; + bool uaegfx = currprefs.rtgboards[0].rtgmem_type < GFXBOARD_HARDWARE; + + if (state) { + atomic_and(&vidinfo->picasso_state_change, ~state); + if (state & UNIX_PICASSO_STATE_SETGC) { + gfx_set_picasso_modeinfo(monid, (RGBFTYPE)picasso96_state[monid].RGBFormat); + } + if (state & UNIX_PICASSO_STATE_SETSWITCH) { + vidinfo->picasso_active = ad->picasso_requested_on; + } + if (state & (UNIX_PICASSO_STATE_SETGC | UNIX_PICASSO_STATE_SETPANNING | + UNIX_PICASSO_STATE_SETDAC | UNIX_PICASSO_STATE_SETDISPLAY)) { + vidinfo->full_refresh = 1; + } + } + if (!uaegfx) { + continue; + } + + if (ad->picasso_on || vidinfo->picasso_active) { + picasso_trigger_vblank(); + picasso_refresh(monid); + } + } + gfxboard_vsync_handler(false, true); +} + +void fb_copyrow(int monid, uae_u8 *src, uae_u8 *dst, int x, int, int width, int srcpixbytes, int dy) +{ + if (!src || !dst || monid < 0 || monid >= MAX_AMIGAMONITORS) { + return; + } + int rowbytes = picasso_vidinfo[monid].rowbytes; + int pixbytes = picasso_vidinfo[monid].pixbytes ? picasso_vidinfo[monid].pixbytes : srcpixbytes; + if (rowbytes <= 0 || pixbytes <= 0 || width <= 0) { + return; + } + memcpy(dst + dy * rowbytes + x * pixbytes, src, (size_t)width * (size_t)srcpixbytes); +} diff --git a/od-unix/input.cpp b/od-unix/input.cpp new file mode 100644 index 00000000..71889a1d --- /dev/null +++ b/od-unix/input.cpp @@ -0,0 +1,1425 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#ifdef UAE_UNIX_WITH_SDL3 +#define SDL_MAIN_HANDLED +#include +#include +#endif + +#include "options.h" +#include "traps.h" +#include "gui.h" +#include "inputdevice.h" +#include "input.h" + +int pause_emulation; +int tablet_log; +int key_swap_hack; + +static TCHAR empty_friendly[] = _T("Unix placeholder"); +static TCHAR empty_unique_name[] = _T("unix.placeholder"); +static TCHAR mouse_friendly[] = _T("Unix Mouse"); +static TCHAR mouse_unique_name[] = _T("unix.mouse"); +static TCHAR mouse_axis_names[][16] = { + _T("X Axis"), + _T("Y Axis"), + _T("Wheel") +}; +static TCHAR mouse_button_names[][16] = { + _T("Button 1"), + _T("Button 2"), + _T("Button 3") +}; +static TCHAR keyboard_friendly[] = _T("Unix Keyboard"); +static TCHAR keyboard_unique_name[] = _T("unix.keyboard"); +static bool mouse_active; +static bool keyboard_state[512]; +static int capslockstate; +static int host_capslockstate; +static int host_numlockstate; +static int host_scrolllockstate; + +#ifdef UAE_UNIX_WITH_SDL3 +enum { + UNIX_AXIS_SDL, + UNIX_AXIS_GAMEPAD_DPAD_X, + UNIX_AXIS_GAMEPAD_DPAD_Y, + UNIX_AXIS_HAT_X, + UNIX_AXIS_HAT_Y +}; + +struct unix_joystick_device { + SDL_JoystickID instance_id; + SDL_Gamepad *gamepad; + SDL_Joystick *joystick; + bool is_gamepad; + TCHAR friendly[128]; + TCHAR unique[160]; + int axis_count; + int button_count; + int axis_kind[ID_AXIS_TOTAL]; + int axis_code[ID_AXIS_TOTAL]; + int button_code[ID_BUTTON_TOTAL]; + int axis_state[ID_AXIS_TOTAL]; + bool button_state[ID_BUTTON_TOTAL]; +}; + +static unix_joystick_device unix_joysticks[MAX_INPUT_DEVICES]; +static int unix_joystick_count; +static bool unix_joystick_sdl_initialized; + +static const SDL_GamepadAxis unix_gamepad_axes[] = { + SDL_GAMEPAD_AXIS_LEFTX, + SDL_GAMEPAD_AXIS_LEFTY, + SDL_GAMEPAD_AXIS_RIGHTX, + SDL_GAMEPAD_AXIS_RIGHTY, + SDL_GAMEPAD_AXIS_LEFT_TRIGGER, + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER +}; + +static const TCHAR *const unix_gamepad_axis_names[] = { + _T("Left X Axis"), + _T("Left Y Axis"), + _T("Right X Axis"), + _T("Right Y Axis"), + _T("Left Trigger"), + _T("Right Trigger") +}; + +static const SDL_GamepadButton unix_gamepad_buttons[] = { + SDL_GAMEPAD_BUTTON_SOUTH, + SDL_GAMEPAD_BUTTON_EAST, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_NORTH, + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + SDL_GAMEPAD_BUTTON_START, + SDL_GAMEPAD_BUTTON_BACK, + SDL_GAMEPAD_BUTTON_LEFT_STICK, + SDL_GAMEPAD_BUTTON_RIGHT_STICK, + SDL_GAMEPAD_BUTTON_GUIDE, + SDL_GAMEPAD_BUTTON_MISC1, + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, + SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, + SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, + SDL_GAMEPAD_BUTTON_TOUCHPAD, + SDL_GAMEPAD_BUTTON_MISC2, + SDL_GAMEPAD_BUTTON_MISC3, + SDL_GAMEPAD_BUTTON_MISC4, + SDL_GAMEPAD_BUTTON_MISC5, + SDL_GAMEPAD_BUTTON_MISC6 +}; + +static const TCHAR *const unix_gamepad_button_names[] = { + _T("South Button"), + _T("East Button"), + _T("West Button"), + _T("North Button"), + _T("Left Shoulder"), + _T("Right Shoulder"), + _T("Start Button"), + _T("Back Button"), + _T("Left Stick Button"), + _T("Right Stick Button"), + _T("Guide Button"), + _T("Misc Button 1"), + _T("Right Paddle 1"), + _T("Left Paddle 1"), + _T("Right Paddle 2"), + _T("Left Paddle 2"), + _T("Touchpad Button"), + _T("Misc Button 2"), + _T("Misc Button 3"), + _T("Misc Button 4"), + _T("Misc Button 5"), + _T("Misc Button 6") +}; +#endif + +enum { + UKEY_A = 4, + UKEY_B = 5, + UKEY_C = 6, + UKEY_D = 7, + UKEY_E = 8, + UKEY_F = 9, + UKEY_G = 10, + UKEY_H = 11, + UKEY_I = 12, + UKEY_J = 13, + UKEY_K = 14, + UKEY_L = 15, + UKEY_M = 16, + UKEY_N = 17, + UKEY_O = 18, + UKEY_P = 19, + UKEY_Q = 20, + UKEY_R = 21, + UKEY_S = 22, + UKEY_T = 23, + UKEY_U = 24, + UKEY_V = 25, + UKEY_W = 26, + UKEY_X = 27, + UKEY_Y = 28, + UKEY_Z = 29, + UKEY_1 = 30, + UKEY_2 = 31, + UKEY_3 = 32, + UKEY_4 = 33, + UKEY_5 = 34, + UKEY_6 = 35, + UKEY_7 = 36, + UKEY_8 = 37, + UKEY_9 = 38, + UKEY_0 = 39, + UKEY_RETURN = 40, + UKEY_ESCAPE = 41, + UKEY_BACKSPACE = 42, + UKEY_TAB = 43, + UKEY_SPACE = 44, + UKEY_MINUS = 45, + UKEY_EQUALS = 46, + UKEY_LEFTBRACKET = 47, + UKEY_RIGHTBRACKET = 48, + UKEY_BACKSLASH = 49, + UKEY_NONUSHASH = 50, + UKEY_SEMICOLON = 51, + UKEY_APOSTROPHE = 52, + UKEY_GRAVE = 53, + UKEY_COMMA = 54, + UKEY_PERIOD = 55, + UKEY_SLASH = 56, + UKEY_CAPSLOCK = 57, + UKEY_F1 = 58, + UKEY_F2 = 59, + UKEY_F3 = 60, + UKEY_F4 = 61, + UKEY_F5 = 62, + UKEY_F6 = 63, + UKEY_F7 = 64, + UKEY_F8 = 65, + UKEY_F9 = 66, + UKEY_F10 = 67, + UKEY_F11 = 68, + UKEY_F12 = 69, + UKEY_PRINTSCREEN = 70, + UKEY_SCROLLLOCK = 71, + UKEY_PAUSE = 72, + UKEY_INSERT = 73, + UKEY_HOME = 74, + UKEY_PAGEUP = 75, + UKEY_DELETE = 76, + UKEY_END = 77, + UKEY_PAGEDOWN = 78, + UKEY_RIGHT = 79, + UKEY_LEFT = 80, + UKEY_DOWN = 81, + UKEY_UP = 82, + UKEY_NUMLOCKCLEAR = 83, + UKEY_KP_DIVIDE = 84, + UKEY_KP_MULTIPLY = 85, + UKEY_KP_MINUS = 86, + UKEY_KP_PLUS = 87, + UKEY_KP_ENTER = 88, + UKEY_KP_1 = 89, + UKEY_KP_2 = 90, + UKEY_KP_3 = 91, + UKEY_KP_4 = 92, + UKEY_KP_5 = 93, + UKEY_KP_6 = 94, + UKEY_KP_7 = 95, + UKEY_KP_8 = 96, + UKEY_KP_9 = 97, + UKEY_KP_0 = 98, + UKEY_KP_PERIOD = 99, + UKEY_NONUSBACKSLASH = 100, + UKEY_APPLICATION = 101, + UKEY_F13 = 104, + UKEY_F14 = 105, + UKEY_F15 = 106, + UKEY_AUDIOSTOP = 260, + UKEY_AUDIOPLAY = 261, + UKEY_AUDIOPREV = 259, + UKEY_AUDIONEXT = 258, + UKEY_LCTRL = 224, + UKEY_LSHIFT = 225, + UKEY_LALT = 226, + UKEY_LGUI = 227, + UKEY_RCTRL = 228, + UKEY_RSHIFT = 229, + UKEY_RALT = 230, + UKEY_RGUI = 231 +}; + +#define K(scancode, event) { scancode, { { event, 0 } } } +#define KF(scancode, event, flags) { scancode, { { event, flags } } } +#define K2(scancode, event1, flags1, event2, flags2) { scancode, { { event1, flags1 }, { event2, flags2 } } } +#define K3(scancode, event1, flags1, event2, flags2, event3, flags3) { scancode, { { event1, flags1 }, { event2, flags2 }, { event3, flags3 } } } +#define K4(scancode, event1, flags1, event2, flags2, event3, flags3, event4, flags4) { scancode, { { event1, flags1 }, { event2, flags2 }, { event3, flags3 }, { event4, flags4 } } } + +static uae_input_device_kbr_default keytrans_amiga[] = { + K(UKEY_ESCAPE, INPUTEVENT_KEY_ESC), + + K3(UKEY_F1, INPUTEVENT_KEY_F1, 0, INPUTEVENT_SPC_FLOPPY0, ID_FLAG_QUALIFIER_SPECIAL, INPUTEVENT_SPC_EFLOPPY0, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_SHIFT), + K3(UKEY_F2, INPUTEVENT_KEY_F2, 0, INPUTEVENT_SPC_FLOPPY1, ID_FLAG_QUALIFIER_SPECIAL, INPUTEVENT_SPC_EFLOPPY1, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_SHIFT), + K3(UKEY_F3, INPUTEVENT_KEY_F3, 0, INPUTEVENT_SPC_FLOPPY2, ID_FLAG_QUALIFIER_SPECIAL, INPUTEVENT_SPC_EFLOPPY2, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_SHIFT), + K3(UKEY_F4, INPUTEVENT_KEY_F4, 0, INPUTEVENT_SPC_FLOPPY3, ID_FLAG_QUALIFIER_SPECIAL, INPUTEVENT_SPC_EFLOPPY3, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_SHIFT), + K3(UKEY_F5, INPUTEVENT_KEY_F5, 0, INPUTEVENT_SPC_CD0, ID_FLAG_QUALIFIER_SPECIAL, INPUTEVENT_SPC_ECD0, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_SHIFT), + K3(UKEY_F6, INPUTEVENT_KEY_F6, 0, INPUTEVENT_SPC_STATERESTOREDIALOG, ID_FLAG_QUALIFIER_SPECIAL, INPUTEVENT_SPC_STATESAVEDIALOG, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_SHIFT), + K(UKEY_F7, INPUTEVENT_KEY_F7), + K(UKEY_F8, INPUTEVENT_KEY_F8), + K2(UKEY_F9, INPUTEVENT_KEY_F9, 0, INPUTEVENT_SPC_TOGGLERTG, ID_FLAG_QUALIFIER_SPECIAL), + K(UKEY_F10, INPUTEVENT_KEY_F10), + + K(UKEY_1, INPUTEVENT_KEY_1), + K(UKEY_2, INPUTEVENT_KEY_2), + K(UKEY_3, INPUTEVENT_KEY_3), + K(UKEY_4, INPUTEVENT_KEY_4), + K(UKEY_5, INPUTEVENT_KEY_5), + K(UKEY_6, INPUTEVENT_KEY_6), + K(UKEY_7, INPUTEVENT_KEY_7), + K(UKEY_8, INPUTEVENT_KEY_8), + K(UKEY_9, INPUTEVENT_KEY_9), + K(UKEY_0, INPUTEVENT_KEY_0), + + K(UKEY_TAB, INPUTEVENT_KEY_TAB), + K(UKEY_A, INPUTEVENT_KEY_A), + K(UKEY_B, INPUTEVENT_KEY_B), + K(UKEY_C, INPUTEVENT_KEY_C), + K(UKEY_D, INPUTEVENT_KEY_D), + K(UKEY_E, INPUTEVENT_KEY_E), + K(UKEY_F, INPUTEVENT_KEY_F), + K(UKEY_G, INPUTEVENT_KEY_G), + K(UKEY_H, INPUTEVENT_KEY_H), + K(UKEY_I, INPUTEVENT_KEY_I), + K2(UKEY_J, INPUTEVENT_KEY_J, 0, INPUTEVENT_SPC_SWAPJOYPORTS, ID_FLAG_QUALIFIER_SPECIAL), + K(UKEY_K, INPUTEVENT_KEY_K), + K(UKEY_L, INPUTEVENT_KEY_L), + K(UKEY_M, INPUTEVENT_KEY_M), + K(UKEY_N, INPUTEVENT_KEY_N), + K(UKEY_O, INPUTEVENT_KEY_O), + K(UKEY_P, INPUTEVENT_KEY_P), + K(UKEY_Q, INPUTEVENT_KEY_Q), + K(UKEY_R, INPUTEVENT_KEY_R), + K(UKEY_S, INPUTEVENT_KEY_S), + K(UKEY_T, INPUTEVENT_KEY_T), + K(UKEY_U, INPUTEVENT_KEY_U), + K(UKEY_V, INPUTEVENT_KEY_V), + K(UKEY_W, INPUTEVENT_KEY_W), + K(UKEY_X, INPUTEVENT_KEY_X), + K(UKEY_Y, INPUTEVENT_KEY_Y), + K(UKEY_Z, INPUTEVENT_KEY_Z), + + KF(UKEY_CAPSLOCK, INPUTEVENT_KEY_CAPS_LOCK, ID_FLAG_TOGGLE), + + K(UKEY_KP_1, INPUTEVENT_KEY_NP_1), + K(UKEY_KP_2, INPUTEVENT_KEY_NP_2), + K(UKEY_KP_3, INPUTEVENT_KEY_NP_3), + K(UKEY_KP_4, INPUTEVENT_KEY_NP_4), + K(UKEY_KP_5, INPUTEVENT_KEY_NP_5), + K(UKEY_KP_6, INPUTEVENT_KEY_NP_6), + K(UKEY_KP_7, INPUTEVENT_KEY_NP_7), + K(UKEY_KP_8, INPUTEVENT_KEY_NP_8), + K(UKEY_KP_9, INPUTEVENT_KEY_NP_9), + K(UKEY_KP_0, INPUTEVENT_KEY_NP_0), + K(UKEY_KP_PERIOD, INPUTEVENT_KEY_NP_PERIOD), + K4(UKEY_KP_PLUS, INPUTEVENT_KEY_NP_ADD, 0, INPUTEVENT_SPC_VOLUME_UP, ID_FLAG_QUALIFIER_SPECIAL, INPUTEVENT_SPC_MASTER_VOLUME_UP, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_CONTROL, INPUTEVENT_SPC_INCREASE_REFRESHRATE, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_SHIFT), + K4(UKEY_KP_MINUS, INPUTEVENT_KEY_NP_SUB, 0, INPUTEVENT_SPC_VOLUME_DOWN, ID_FLAG_QUALIFIER_SPECIAL, INPUTEVENT_SPC_MASTER_VOLUME_DOWN, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_CONTROL, INPUTEVENT_SPC_DECREASE_REFRESHRATE, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_SHIFT), + K3(UKEY_KP_MULTIPLY, INPUTEVENT_KEY_NP_MUL, 0, INPUTEVENT_SPC_VOLUME_MUTE, ID_FLAG_QUALIFIER_SPECIAL, INPUTEVENT_SPC_MASTER_VOLUME_MUTE, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_CONTROL), + K2(UKEY_KP_DIVIDE, INPUTEVENT_KEY_NP_DIV, 0, INPUTEVENT_SPC_STATEREWIND, ID_FLAG_QUALIFIER_SPECIAL), + K(UKEY_KP_ENTER, INPUTEVENT_KEY_ENTER), + + K(UKEY_MINUS, INPUTEVENT_KEY_SUB), + K(UKEY_EQUALS, INPUTEVENT_KEY_EQUALS), + K(UKEY_BACKSPACE, INPUTEVENT_KEY_BACKSPACE), + K(UKEY_RETURN, INPUTEVENT_KEY_RETURN), + K(UKEY_SPACE, INPUTEVENT_KEY_SPACE), + + K2(UKEY_LSHIFT, INPUTEVENT_KEY_SHIFT_LEFT, 0, INPUTEVENT_SPC_QUALIFIER_SHIFT, 0), + K2(UKEY_LCTRL, INPUTEVENT_KEY_CTRL, 0, INPUTEVENT_SPC_QUALIFIER_CONTROL, 0), + K2(UKEY_LGUI, INPUTEVENT_KEY_AMIGA_LEFT, 0, INPUTEVENT_SPC_QUALIFIER_WIN, 0), + K2(UKEY_LALT, INPUTEVENT_KEY_ALT_LEFT, 0, INPUTEVENT_SPC_QUALIFIER_ALT, 0), + K2(UKEY_RALT, INPUTEVENT_KEY_ALT_RIGHT, 0, INPUTEVENT_SPC_QUALIFIER_ALT, 0), + K2(UKEY_RGUI, INPUTEVENT_KEY_AMIGA_RIGHT, 0, INPUTEVENT_SPC_QUALIFIER_WIN, 0), + K2(UKEY_APPLICATION, INPUTEVENT_KEY_AMIGA_RIGHT, 0, INPUTEVENT_SPC_QUALIFIER_WIN, 0), + K2(UKEY_RCTRL, INPUTEVENT_KEY_CTRL, 0, INPUTEVENT_SPC_QUALIFIER_CONTROL, 0), + K2(UKEY_RSHIFT, INPUTEVENT_KEY_SHIFT_RIGHT, 0, INPUTEVENT_SPC_QUALIFIER_SHIFT, 0), + + K(UKEY_UP, INPUTEVENT_KEY_CURSOR_UP), + K(UKEY_DOWN, INPUTEVENT_KEY_CURSOR_DOWN), + K2(UKEY_LEFT, INPUTEVENT_KEY_CURSOR_LEFT, 0, INPUTEVENT_SPC_PAUSE, ID_FLAG_QUALIFIER_SPECIAL), + K2(UKEY_RIGHT, INPUTEVENT_KEY_CURSOR_RIGHT, 0, INPUTEVENT_SPC_WARP, ID_FLAG_QUALIFIER_SPECIAL), + + K2(UKEY_INSERT, INPUTEVENT_KEY_AMIGA_LEFT, 0, INPUTEVENT_SPC_PASTE, ID_FLAG_QUALIFIER_SPECIAL), + K(UKEY_DELETE, INPUTEVENT_KEY_DEL), + K(UKEY_HOME, INPUTEVENT_KEY_AMIGA_RIGHT), + K(UKEY_PAGEDOWN, INPUTEVENT_KEY_HELP), + K(UKEY_PAGEUP, INPUTEVENT_SPC_FREEZEBUTTON), + + K(UKEY_LEFTBRACKET, INPUTEVENT_KEY_LEFTBRACKET), + K(UKEY_RIGHTBRACKET, INPUTEVENT_KEY_RIGHTBRACKET), + K(UKEY_SEMICOLON, INPUTEVENT_KEY_SEMICOLON), + K(UKEY_APOSTROPHE, INPUTEVENT_KEY_SINGLEQUOTE), + K(UKEY_GRAVE, INPUTEVENT_KEY_BACKQUOTE), + K(UKEY_BACKSLASH, INPUTEVENT_KEY_NUMBERSIGN), + K(UKEY_NONUSHASH, INPUTEVENT_KEY_BACKSLASH), + K(UKEY_NONUSBACKSLASH, INPUTEVENT_KEY_30), + K(UKEY_COMMA, INPUTEVENT_KEY_COMMA), + K(UKEY_PERIOD, INPUTEVENT_KEY_PERIOD), + K(UKEY_SLASH, INPUTEVENT_KEY_DIV), + + K(UKEY_F11, INPUTEVENT_KEY_BACKSLASH), + K(UKEY_F13, INPUTEVENT_KEY_BACKSLASH), + K(UKEY_F14, INPUTEVENT_KEY_NP_LPAREN), + K(UKEY_F15, INPUTEVENT_KEY_NP_RPAREN), + K2(UKEY_PRINTSCREEN, INPUTEVENT_SPC_SCREENSHOT_CLIPBOARD, 0, INPUTEVENT_SPC_SCREENSHOT, ID_FLAG_QUALIFIER_SPECIAL), + K(UKEY_END, INPUTEVENT_SPC_QUALIFIER_SPECIAL), + K4(UKEY_PAUSE, INPUTEVENT_SPC_PAUSE, 0, INPUTEVENT_SPC_SINGLESTEP, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_CONTROL, INPUTEVENT_SPC_IRQ7, ID_FLAG_QUALIFIER_SPECIAL | ID_FLAG_QUALIFIER_SHIFT, INPUTEVENT_SPC_WARP, ID_FLAG_QUALIFIER_SPECIAL), + K4(UKEY_F12, INPUTEVENT_SPC_ENTERGUI, 0, INPUTEVENT_SPC_ENTERDEBUGGER, ID_FLAG_QUALIFIER_SPECIAL, INPUTEVENT_SPC_ENTERDEBUGGER, ID_FLAG_QUALIFIER_SHIFT, INPUTEVENT_SPC_TOGGLEDEFAULTSCREEN, ID_FLAG_QUALIFIER_CONTROL), + + K(UKEY_AUDIOSTOP, INPUTEVENT_KEY_CDTV_STOP), + K(UKEY_AUDIOPLAY, INPUTEVENT_KEY_CDTV_PLAYPAUSE), + K(UKEY_AUDIOPREV, INPUTEVENT_KEY_CDTV_PREV), + K(UKEY_AUDIONEXT, INPUTEVENT_KEY_CDTV_NEXT), + + { -1, { { 0, 0 } } } +}; + +static uae_input_device_kbr_default keytrans_pc[] = { + K(UKEY_ESCAPE, INPUTEVENT_KEY_ESC), + + K(UKEY_F1, INPUTEVENT_KEY_F1), + K(UKEY_F2, INPUTEVENT_KEY_F2), + K(UKEY_F3, INPUTEVENT_KEY_F3), + K(UKEY_F4, INPUTEVENT_KEY_F4), + K(UKEY_F5, INPUTEVENT_KEY_F5), + K(UKEY_F6, INPUTEVENT_KEY_F6), + K(UKEY_F7, INPUTEVENT_KEY_F7), + K(UKEY_F8, INPUTEVENT_KEY_F8), + K(UKEY_F9, INPUTEVENT_KEY_F9), + K(UKEY_F10, INPUTEVENT_KEY_F10), + K(UKEY_F11, INPUTEVENT_KEY_F11), + K(UKEY_F12, INPUTEVENT_KEY_F12), + + K(UKEY_1, INPUTEVENT_KEY_1), + K(UKEY_2, INPUTEVENT_KEY_2), + K(UKEY_3, INPUTEVENT_KEY_3), + K(UKEY_4, INPUTEVENT_KEY_4), + K(UKEY_5, INPUTEVENT_KEY_5), + K(UKEY_6, INPUTEVENT_KEY_6), + K(UKEY_7, INPUTEVENT_KEY_7), + K(UKEY_8, INPUTEVENT_KEY_8), + K(UKEY_9, INPUTEVENT_KEY_9), + K(UKEY_0, INPUTEVENT_KEY_0), + + K(UKEY_TAB, INPUTEVENT_KEY_TAB), + K(UKEY_A, INPUTEVENT_KEY_A), + K(UKEY_B, INPUTEVENT_KEY_B), + K(UKEY_C, INPUTEVENT_KEY_C), + K(UKEY_D, INPUTEVENT_KEY_D), + K(UKEY_E, INPUTEVENT_KEY_E), + K(UKEY_F, INPUTEVENT_KEY_F), + K(UKEY_G, INPUTEVENT_KEY_G), + K(UKEY_H, INPUTEVENT_KEY_H), + K(UKEY_I, INPUTEVENT_KEY_I), + K(UKEY_J, INPUTEVENT_KEY_J), + K(UKEY_K, INPUTEVENT_KEY_K), + K(UKEY_L, INPUTEVENT_KEY_L), + K(UKEY_M, INPUTEVENT_KEY_M), + K(UKEY_N, INPUTEVENT_KEY_N), + K(UKEY_O, INPUTEVENT_KEY_O), + K(UKEY_P, INPUTEVENT_KEY_P), + K(UKEY_Q, INPUTEVENT_KEY_Q), + K(UKEY_R, INPUTEVENT_KEY_R), + K(UKEY_S, INPUTEVENT_KEY_S), + K(UKEY_T, INPUTEVENT_KEY_T), + K(UKEY_U, INPUTEVENT_KEY_U), + K(UKEY_V, INPUTEVENT_KEY_V), + K(UKEY_W, INPUTEVENT_KEY_W), + K(UKEY_X, INPUTEVENT_KEY_X), + K(UKEY_Y, INPUTEVENT_KEY_Y), + K(UKEY_Z, INPUTEVENT_KEY_Z), + + KF(UKEY_CAPSLOCK, INPUTEVENT_KEY_CAPS_LOCK, ID_FLAG_TOGGLE), + + K(UKEY_KP_1, INPUTEVENT_KEY_NP_1), + K(UKEY_KP_2, INPUTEVENT_KEY_NP_2), + K(UKEY_KP_3, INPUTEVENT_KEY_NP_3), + K(UKEY_KP_4, INPUTEVENT_KEY_NP_4), + K(UKEY_KP_5, INPUTEVENT_KEY_NP_5), + K(UKEY_KP_6, INPUTEVENT_KEY_NP_6), + K(UKEY_KP_7, INPUTEVENT_KEY_NP_7), + K(UKEY_KP_8, INPUTEVENT_KEY_NP_8), + K(UKEY_KP_9, INPUTEVENT_KEY_NP_9), + K(UKEY_KP_0, INPUTEVENT_KEY_NP_0), + K(UKEY_KP_PERIOD, INPUTEVENT_KEY_NP_PERIOD), + K(UKEY_KP_PLUS, INPUTEVENT_KEY_NP_ADD), + K(UKEY_KP_MINUS, INPUTEVENT_KEY_NP_SUB), + K(UKEY_KP_MULTIPLY, INPUTEVENT_KEY_NP_MUL), + K(UKEY_KP_DIVIDE, INPUTEVENT_KEY_NP_DIV), + K(UKEY_KP_ENTER, INPUTEVENT_KEY_ENTER), + + K(UKEY_MINUS, INPUTEVENT_KEY_SUB), + K(UKEY_EQUALS, INPUTEVENT_KEY_EQUALS), + K(UKEY_BACKSPACE, INPUTEVENT_KEY_BACKSPACE), + K(UKEY_RETURN, INPUTEVENT_KEY_RETURN), + K(UKEY_SPACE, INPUTEVENT_KEY_SPACE), + + K(UKEY_LSHIFT, INPUTEVENT_KEY_SHIFT_LEFT), + K(UKEY_LCTRL, INPUTEVENT_KEY_CTRL), + K(UKEY_LGUI, INPUTEVENT_KEY_AMIGA_LEFT), + K(UKEY_LALT, INPUTEVENT_KEY_ALT_LEFT), + K(UKEY_RALT, INPUTEVENT_KEY_ALT_RIGHT), + K(UKEY_RGUI, INPUTEVENT_KEY_AMIGA_RIGHT), + K(UKEY_APPLICATION, INPUTEVENT_KEY_APPS), + K(UKEY_RCTRL, INPUTEVENT_KEY_CTRL), + K(UKEY_RSHIFT, INPUTEVENT_KEY_SHIFT_RIGHT), + + K(UKEY_UP, INPUTEVENT_KEY_CURSOR_UP), + K(UKEY_DOWN, INPUTEVENT_KEY_CURSOR_DOWN), + K(UKEY_LEFT, INPUTEVENT_KEY_CURSOR_LEFT), + K(UKEY_RIGHT, INPUTEVENT_KEY_CURSOR_RIGHT), + + K(UKEY_LEFTBRACKET, INPUTEVENT_KEY_LEFTBRACKET), + K(UKEY_RIGHTBRACKET, INPUTEVENT_KEY_RIGHTBRACKET), + K(UKEY_SEMICOLON, INPUTEVENT_KEY_SEMICOLON), + K(UKEY_APOSTROPHE, INPUTEVENT_KEY_SINGLEQUOTE), + K(UKEY_GRAVE, INPUTEVENT_KEY_BACKQUOTE), + K(UKEY_BACKSLASH, INPUTEVENT_KEY_2B), + K(UKEY_NONUSHASH, INPUTEVENT_KEY_BACKSLASH), + K(UKEY_NONUSBACKSLASH, INPUTEVENT_KEY_30), + K(UKEY_COMMA, INPUTEVENT_KEY_COMMA), + K(UKEY_PERIOD, INPUTEVENT_KEY_PERIOD), + K(UKEY_SLASH, INPUTEVENT_KEY_DIV), + + K(UKEY_INSERT, INPUTEVENT_KEY_INSERT), + K(UKEY_DELETE, INPUTEVENT_KEY_DEL), + K(UKEY_HOME, INPUTEVENT_KEY_HOME), + K(UKEY_END, INPUTEVENT_KEY_END), + K(UKEY_PAGEUP, INPUTEVENT_KEY_PAGEUP), + K(UKEY_PAGEDOWN, INPUTEVENT_KEY_PAGEDOWN), + K(UKEY_SCROLLLOCK, INPUTEVENT_KEY_HELP), + K(UKEY_PRINTSCREEN, INPUTEVENT_KEY_SYSRQ), + K(UKEY_PAUSE, INPUTEVENT_KEY_PAUSE), + + K(UKEY_AUDIOSTOP, INPUTEVENT_KEY_CDTV_STOP), + K(UKEY_AUDIOPLAY, INPUTEVENT_KEY_CDTV_PLAYPAUSE), + K(UKEY_AUDIOPREV, INPUTEVENT_KEY_CDTV_PREV), + K(UKEY_AUDIONEXT, INPUTEVENT_KEY_CDTV_NEXT), + + { -1, { { 0, 0 } } } +}; + +#undef K4 +#undef K3 +#undef K2 +#undef KF +#undef K + +static uae_input_device_kbr_default *keytrans[] = { + keytrans_amiga, + keytrans_pc, + keytrans_pc +}; +static int kb_np[] = { UKEY_KP_4, -1, UKEY_KP_6, -1, UKEY_KP_8, -1, UKEY_KP_2, -1, UKEY_KP_0, UKEY_KP_5, -1, UKEY_KP_PERIOD, -1, UKEY_KP_ENTER, -1, -1 }; +static int kb_ck[] = { UKEY_LEFT, -1, UKEY_RIGHT, -1, UKEY_UP, -1, UKEY_DOWN, -1, UKEY_RCTRL, UKEY_RALT, -1, UKEY_RSHIFT, -1, -1 }; +static int kb_se[] = { UKEY_A, -1, UKEY_D, -1, UKEY_W, -1, UKEY_S, -1, UKEY_LALT, -1, UKEY_LSHIFT, -1, -1 }; +static int kb_np3[] = { UKEY_KP_4, -1, UKEY_KP_6, -1, UKEY_KP_8, -1, UKEY_KP_2, -1, UKEY_KP_0, UKEY_KP_5, -1, UKEY_KP_PERIOD, -1, UKEY_KP_ENTER, -1, -1 }; +static int kb_ck3[] = { UKEY_LEFT, -1, UKEY_RIGHT, -1, UKEY_UP, -1, UKEY_DOWN, -1, UKEY_RCTRL, -1, UKEY_RSHIFT, -1, UKEY_RALT, -1, -1 }; +static int kb_se3[] = { UKEY_A, -1, UKEY_D, -1, UKEY_W, -1, UKEY_S, -1, UKEY_LALT, -1, UKEY_LSHIFT, -1, UKEY_LCTRL, -1, -1 }; +static int kb_cd32_np[] = { UKEY_KP_4, -1, UKEY_KP_6, -1, UKEY_KP_8, -1, UKEY_KP_2, -1, UKEY_KP_0, UKEY_KP_5, UKEY_KP_1, -1, UKEY_KP_PERIOD, UKEY_KP_3, -1, UKEY_KP_7, -1, UKEY_KP_9, -1, UKEY_KP_DIVIDE, -1, UKEY_KP_MINUS, -1, UKEY_KP_MULTIPLY, -1, -1 }; +static int kb_cd32_ck[] = { UKEY_LEFT, -1, UKEY_RIGHT, -1, UKEY_UP, -1, UKEY_DOWN, -1, UKEY_RCTRL, UKEY_RALT, UKEY_RSHIFT, -1, UKEY_KP_7, -1, UKEY_KP_9, -1, UKEY_KP_DIVIDE, -1, UKEY_KP_MINUS, -1, UKEY_KP_MULTIPLY, -1, -1 }; +static int kb_cd32_se[] = { UKEY_A, -1, UKEY_D, -1, UKEY_W, -1, UKEY_S, -1, -1, UKEY_LALT, -1, UKEY_LSHIFT, -1, UKEY_KP_7, -1, UKEY_KP_9, -1, UKEY_KP_DIVIDE, -1, UKEY_KP_MINUS, -1, UKEY_KP_MULTIPLY, -1, -1 }; +static int kb_arcadia[] = { UKEY_F2, -1, UKEY_1, -1, UKEY_2, -1, UKEY_5, -1, UKEY_6, -1, -1 }; +static int kb_cdtv[] = { UKEY_KP_1, -1, UKEY_KP_3, -1, UKEY_KP_7, -1, UKEY_KP_9, -1, -1 }; +static int *keymaps[] = { + kb_np, kb_ck, kb_se, kb_np3, kb_ck3, kb_se3, + kb_cd32_np, kb_cd32_ck, kb_cd32_se, + kb_arcadia, kb_cdtv +}; + +static const int keyboard_keycodes[] = { + UKEY_ESCAPE, + UKEY_F1, UKEY_F2, UKEY_F3, UKEY_F4, UKEY_F5, UKEY_F6, UKEY_F7, UKEY_F8, UKEY_F9, UKEY_F10, UKEY_F11, UKEY_F12, + UKEY_1, UKEY_2, UKEY_3, UKEY_4, UKEY_5, UKEY_6, UKEY_7, UKEY_8, UKEY_9, UKEY_0, + UKEY_TAB, + UKEY_A, UKEY_B, UKEY_C, UKEY_D, UKEY_E, UKEY_F, UKEY_G, UKEY_H, UKEY_I, UKEY_J, UKEY_K, UKEY_L, UKEY_M, + UKEY_N, UKEY_O, UKEY_P, UKEY_Q, UKEY_R, UKEY_S, UKEY_T, UKEY_U, UKEY_V, UKEY_W, UKEY_X, UKEY_Y, UKEY_Z, + UKEY_CAPSLOCK, + UKEY_KP_1, UKEY_KP_2, UKEY_KP_3, UKEY_KP_4, UKEY_KP_5, UKEY_KP_6, UKEY_KP_7, UKEY_KP_8, UKEY_KP_9, UKEY_KP_0, + UKEY_KP_PERIOD, UKEY_KP_PLUS, UKEY_KP_MINUS, UKEY_KP_MULTIPLY, UKEY_KP_DIVIDE, UKEY_KP_ENTER, + UKEY_MINUS, UKEY_EQUALS, UKEY_BACKSPACE, UKEY_RETURN, UKEY_SPACE, + UKEY_LSHIFT, UKEY_LCTRL, UKEY_LGUI, UKEY_LALT, UKEY_RALT, UKEY_RGUI, UKEY_APPLICATION, UKEY_RCTRL, UKEY_RSHIFT, + UKEY_UP, UKEY_DOWN, UKEY_LEFT, UKEY_RIGHT, + UKEY_INSERT, UKEY_DELETE, UKEY_HOME, UKEY_END, UKEY_PAGEUP, UKEY_PAGEDOWN, UKEY_SCROLLLOCK, UKEY_PRINTSCREEN, UKEY_PAUSE, + UKEY_LEFTBRACKET, UKEY_RIGHTBRACKET, UKEY_SEMICOLON, UKEY_APOSTROPHE, UKEY_GRAVE, UKEY_BACKSLASH, UKEY_NONUSHASH, UKEY_NONUSBACKSLASH, + UKEY_COMMA, UKEY_PERIOD, UKEY_SLASH, + UKEY_F13, UKEY_F14, UKEY_F15, + UKEY_AUDIOSTOP, UKEY_AUDIOPLAY, UKEY_AUDIOPREV, UKEY_AUDIONEXT +}; + +static int input_init(void) +{ + inputdevice_setkeytranslation(keytrans, keymaps); + return 1; +} +static void input_close(void) {} +static int input_acquire(int, int) { return 1; } +static void input_unacquire(int) {} +static void input_read(void) {} +static int empty_get_num(void) { return 0; } +static TCHAR *empty_get_friendlyname(int) { return empty_friendly; } +static TCHAR *empty_get_uniquename(int) { return empty_unique_name; } +static int empty_get_widget_num(int) { return 0; } +static int empty_get_widget_type(int, int, TCHAR *, uae_u32 *) { return IDEV_WIDGET_NONE; } +static int empty_get_widget_first(int, int) { return -1; } +static int empty_get_flags(int) { return 0; } + +static int mouse_get_num(void) { return 1; } +static TCHAR *mouse_get_friendlyname(int) { return mouse_friendly; } +static TCHAR *mouse_get_uniquename(int) { return mouse_unique_name; } +static int mouse_get_widget_num(int) { return 6; } +static int mouse_get_widget_type(int, int widget, TCHAR *name, uae_u32 *code) +{ + if (code) { + *code = widget; + } + if (widget >= 0 && widget < 3) { + if (name) { + _tcscpy(name, mouse_axis_names[widget]); + } + return IDEV_WIDGET_AXIS; + } + if (widget >= 3 && widget < 6) { + if (name) { + _tcscpy(name, mouse_button_names[widget - 3]); + } + return IDEV_WIDGET_BUTTON; + } + return IDEV_WIDGET_NONE; +} +static int mouse_get_widget_first(int, int type) +{ + if (type == IDEV_WIDGET_AXIS) { + return 0; + } + if (type == IDEV_WIDGET_BUTTON) { + return 3; + } + return -1; +} +static int mouse_get_flags(int) { return 0; } + +static int keyboard_get_num(void) { return 1; } +static TCHAR *keyboard_get_friendlyname(int) { return keyboard_friendly; } +static TCHAR *keyboard_get_uniquename(int) { return keyboard_unique_name; } +static int keyboard_get_widget_num(int) { return sizeof keyboard_keycodes / sizeof keyboard_keycodes[0]; } +static int keyboard_get_widget_type(int, int widget, TCHAR *name, uae_u32 *code) +{ + if (widget < 0 || widget >= keyboard_get_widget_num(0)) { + return IDEV_WIDGET_NONE; + } + int scancode = keyboard_keycodes[widget]; + if (name) { + _sntprintf(name, 64, _T("Key %d"), scancode); + name[63] = 0; + } + if (code) { + *code = scancode; + } + return IDEV_WIDGET_KEY; +} +static int keyboard_get_widget_first(int, int type) +{ + return type == IDEV_WIDGET_KEY ? 0 : -1; +} +static int keyboard_get_flags(int) { return 0; } + +#ifdef UAE_UNIX_WITH_SDL3 +static void joystick_release_device(int index) +{ + if (index < 0 || index >= unix_joystick_count) { + return; + } + unix_joystick_device *dev = &unix_joysticks[index]; + for (int i = 0; i < dev->axis_count && i < ID_AXIS_TOTAL; i++) { + if (dev->axis_state[i]) { + setjoystickstate(index, i, 0, 32767); + dev->axis_state[i] = 0; + } + } + for (int i = 0; i < dev->button_count && i < ID_BUTTON_TOTAL; i++) { + if (dev->button_state[i]) { + setjoybuttonstate(index, i, 0); + dev->button_state[i] = false; + } + } +} + +static void joystick_close_devices(void) +{ + for (int i = 0; i < unix_joystick_count; i++) { + joystick_release_device(i); + if (unix_joysticks[i].gamepad) { + SDL_CloseGamepad(unix_joysticks[i].gamepad); + } else if (unix_joysticks[i].joystick) { + SDL_CloseJoystick(unix_joysticks[i].joystick); + } + } + memset(unix_joysticks, 0, sizeof unix_joysticks); + unix_joystick_count = 0; +} + +static void joystick_copy_text(TCHAR *dst, int dstlen, const char *src, const TCHAR *fallback) +{ + if (!dst || dstlen <= 0) { + return; + } + if (src && src[0]) { + _sntprintf(dst, dstlen, _T("%s"), src); + } else { + _sntprintf(dst, dstlen, _T("%s"), fallback); + } + dst[dstlen - 1] = 0; +} + +static void joystick_make_unique(TCHAR *dst, int dstlen, const TCHAR *kind, SDL_JoystickID instance_id, int ordinal) +{ + char guid[64]; + SDL_GUIDToString(SDL_GetJoystickGUIDForID(instance_id), guid, sizeof guid); + _sntprintf(dst, dstlen, _T("unix.%s.%s.%d"), kind, guid, ordinal); + dst[dstlen - 1] = 0; +} + +static void joystick_add_axis(unix_joystick_device *dev, int kind, int code) +{ + if (!dev || dev->axis_count >= ID_AXIS_TOTAL) { + return; + } + int axis = dev->axis_count++; + dev->axis_kind[axis] = kind; + dev->axis_code[axis] = code; +} + +static void joystick_add_button(unix_joystick_device *dev, int code) +{ + if (!dev || dev->button_count >= ID_BUTTON_TOTAL) { + return; + } + dev->button_code[dev->button_count++] = code; +} + +static void joystick_register_gamepad(SDL_JoystickID instance_id) +{ + if (unix_joystick_count >= MAX_INPUT_DEVICES) { + return; + } + + SDL_Gamepad *gamepad = SDL_OpenGamepad(instance_id); + if (!gamepad) { + write_log(_T("SDL3: failed to open gamepad %d: %s\n"), (int)instance_id, SDL_GetError()); + return; + } + + unix_joystick_device *dev = &unix_joysticks[unix_joystick_count]; + memset(dev, 0, sizeof *dev); + dev->instance_id = instance_id; + dev->gamepad = gamepad; + dev->is_gamepad = true; + joystick_copy_text(dev->friendly, sizeof dev->friendly / sizeof dev->friendly[0], + SDL_GetGamepadName(gamepad), _T("SDL Gamepad")); + joystick_make_unique(dev->unique, sizeof dev->unique / sizeof dev->unique[0], + _T("gamepad"), instance_id, unix_joystick_count); + + for (int i = 0; i < (int)(sizeof unix_gamepad_axes / sizeof unix_gamepad_axes[0]); i++) { + if (SDL_GamepadHasAxis(gamepad, unix_gamepad_axes[i])) { + joystick_add_axis(dev, UNIX_AXIS_SDL, unix_gamepad_axes[i]); + } + } + joystick_add_axis(dev, UNIX_AXIS_GAMEPAD_DPAD_X, 0); + joystick_add_axis(dev, UNIX_AXIS_GAMEPAD_DPAD_Y, 0); + + for (int i = 0; i < (int)(sizeof unix_gamepad_buttons / sizeof unix_gamepad_buttons[0]); i++) { + if (SDL_GamepadHasButton(gamepad, unix_gamepad_buttons[i])) { + joystick_add_button(dev, unix_gamepad_buttons[i]); + } + } + + write_log(_T("SDL3: gamepad %d: '%s' (%s), %d axes, %d buttons\n"), + unix_joystick_count, dev->friendly, dev->unique, dev->axis_count, dev->button_count); + unix_joystick_count++; +} + +static void joystick_register_joystick(SDL_JoystickID instance_id) +{ + if (unix_joystick_count >= MAX_INPUT_DEVICES || SDL_IsGamepad(instance_id)) { + return; + } + + SDL_Joystick *joystick = SDL_OpenJoystick(instance_id); + if (!joystick) { + write_log(_T("SDL3: failed to open joystick %d: %s\n"), (int)instance_id, SDL_GetError()); + return; + } + + unix_joystick_device *dev = &unix_joysticks[unix_joystick_count]; + memset(dev, 0, sizeof *dev); + dev->instance_id = instance_id; + dev->joystick = joystick; + joystick_copy_text(dev->friendly, sizeof dev->friendly / sizeof dev->friendly[0], + SDL_GetJoystickName(joystick), _T("SDL Joystick")); + joystick_make_unique(dev->unique, sizeof dev->unique / sizeof dev->unique[0], + _T("joystick"), instance_id, unix_joystick_count); + + int axes = SDL_GetNumJoystickAxes(joystick); + int hats = SDL_GetNumJoystickHats(joystick); + int buttons = SDL_GetNumJoystickButtons(joystick); + if (axes < 0) { + axes = 0; + } + if (hats < 0) { + hats = 0; + } + if (buttons < 0) { + buttons = 0; + } + for (int i = 0; i < axes && dev->axis_count < ID_AXIS_TOTAL; i++) { + joystick_add_axis(dev, UNIX_AXIS_SDL, i); + } + for (int i = 0; i < hats && dev->axis_count + 1 < ID_AXIS_TOTAL; i++) { + joystick_add_axis(dev, UNIX_AXIS_HAT_X, i); + joystick_add_axis(dev, UNIX_AXIS_HAT_Y, i); + } + for (int i = 0; i < buttons && dev->button_count < ID_BUTTON_TOTAL; i++) { + joystick_add_button(dev, i); + } + + write_log(_T("SDL3: joystick %d: '%s' (%s), %d axes, %d buttons\n"), + unix_joystick_count, dev->friendly, dev->unique, dev->axis_count, dev->button_count); + unix_joystick_count++; +} + +static void joystick_open_devices(void) +{ + joystick_close_devices(); + + int count = 0; + SDL_JoystickID *ids = SDL_GetGamepads(&count); + if (ids) { + for (int i = 0; i < count; i++) { + joystick_register_gamepad(ids[i]); + } + SDL_free(ids); + } + + count = 0; + ids = SDL_GetJoysticks(&count); + if (ids) { + for (int i = 0; i < count; i++) { + joystick_register_joystick(ids[i]); + } + SDL_free(ids); + } +} + +static int joystick_init(void) +{ + input_init(); + if (unix_joystick_sdl_initialized) { + return 1; + } + + SDL_SetMainReady(); + if (!SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { + write_log(_T("SDL3: joystick/gamepad unavailable: %s\n"), SDL_GetError()); + return 0; + } + SDL_SetJoystickEventsEnabled(true); + SDL_SetGamepadEventsEnabled(true); + unix_joystick_sdl_initialized = true; + joystick_open_devices(); + return 1; +} + +static void joystick_close(void) +{ + if (!unix_joystick_sdl_initialized) { + return; + } + joystick_close_devices(); + SDL_QuitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_JOYSTICK); + unix_joystick_sdl_initialized = false; +} + +static int joystick_acquire(int num, int) +{ + return num < 0 || num < unix_joystick_count; +} + +static void joystick_unacquire(int num) +{ + if (num >= 0 && num < unix_joystick_count) { + joystick_release_device(num); + } +} + +static void joystick_set_axis(unix_joystick_device *dev, int joy, int axis, int value, int max) +{ + if (!dev || axis < 0 || axis >= dev->axis_count || axis >= ID_AXIS_TOTAL) { + return; + } + if (dev->axis_state[axis] == value) { + return; + } + dev->axis_state[axis] = value; + setjoystickstate(joy, axis, value, max); +} + +static void joystick_set_button(unix_joystick_device *dev, int joy, int button, bool down) +{ + if (!dev || button < 0 || button >= dev->button_count || button >= ID_BUTTON_TOTAL) { + return; + } + if (dev->button_state[button] == down) { + return; + } + dev->button_state[button] = down; + setjoybuttonstate(joy, button, down ? 1 : 0); +} + +static void joystick_read_gamepad(unix_joystick_device *dev, int joy) +{ + int dpad_x = 0; + int dpad_y = 0; + + for (int axis = 0; axis < dev->axis_count; axis++) { + switch (dev->axis_kind[axis]) { + case UNIX_AXIS_SDL: + joystick_set_axis(dev, joy, axis, SDL_GetGamepadAxis(dev->gamepad, (SDL_GamepadAxis)dev->axis_code[axis]), 32767); + break; + case UNIX_AXIS_GAMEPAD_DPAD_X: + dpad_x = SDL_GetGamepadButton(dev->gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT) ? 1 : 0; + dpad_x -= SDL_GetGamepadButton(dev->gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT) ? 1 : 0; + joystick_set_axis(dev, joy, axis, dpad_x, 1); + break; + case UNIX_AXIS_GAMEPAD_DPAD_Y: + dpad_y = SDL_GetGamepadButton(dev->gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN) ? 1 : 0; + dpad_y -= SDL_GetGamepadButton(dev->gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP) ? 1 : 0; + joystick_set_axis(dev, joy, axis, dpad_y, 1); + break; + } + } + + for (int button = 0; button < dev->button_count; button++) { + joystick_set_button(dev, joy, button, + SDL_GetGamepadButton(dev->gamepad, (SDL_GamepadButton)dev->button_code[button])); + } +} + +static void joystick_read_joystick(unix_joystick_device *dev, int joy) +{ + for (int axis = 0; axis < dev->axis_count; axis++) { + switch (dev->axis_kind[axis]) { + case UNIX_AXIS_SDL: + joystick_set_axis(dev, joy, axis, SDL_GetJoystickAxis(dev->joystick, dev->axis_code[axis]), 32767); + break; + case UNIX_AXIS_HAT_X: + { + Uint8 hat = SDL_GetJoystickHat(dev->joystick, dev->axis_code[axis]); + int value = (hat & SDL_HAT_RIGHT) ? 1 : 0; + value -= (hat & SDL_HAT_LEFT) ? 1 : 0; + joystick_set_axis(dev, joy, axis, value, 1); + break; + } + case UNIX_AXIS_HAT_Y: + { + Uint8 hat = SDL_GetJoystickHat(dev->joystick, dev->axis_code[axis]); + int value = (hat & SDL_HAT_DOWN) ? 1 : 0; + value -= (hat & SDL_HAT_UP) ? 1 : 0; + joystick_set_axis(dev, joy, axis, value, 1); + break; + } + } + } + + for (int button = 0; button < dev->button_count; button++) { + joystick_set_button(dev, joy, button, SDL_GetJoystickButton(dev->joystick, dev->button_code[button])); + } +} + +static void joystick_read(void) +{ + if (!unix_joystick_sdl_initialized) { + return; + } + SDL_UpdateGamepads(); + SDL_UpdateJoysticks(); + for (int i = 0; i < unix_joystick_count; i++) { + unix_joystick_device *dev = &unix_joysticks[i]; + if (dev->gamepad) { + joystick_read_gamepad(dev, i); + } else if (dev->joystick) { + joystick_read_joystick(dev, i); + } + } +} + +static int joystick_get_num(void) +{ + return unix_joystick_count; +} + +static TCHAR *joystick_get_friendlyname(int joy) +{ + return joy >= 0 && joy < unix_joystick_count ? unix_joysticks[joy].friendly : empty_friendly; +} + +static TCHAR *joystick_get_uniquename(int joy) +{ + return joy >= 0 && joy < unix_joystick_count ? unix_joysticks[joy].unique : empty_unique_name; +} + +static int joystick_get_widget_num(int joy) +{ + if (joy < 0 || joy >= unix_joystick_count) { + return 0; + } + return unix_joysticks[joy].axis_count + unix_joysticks[joy].button_count; +} + +static void joystick_axis_name(unix_joystick_device *dev, int axis, TCHAR *name) +{ + if (!name) { + return; + } + switch (dev->axis_kind[axis]) { + case UNIX_AXIS_SDL: + if (dev->is_gamepad) { + for (int i = 0; i < (int)(sizeof unix_gamepad_axes / sizeof unix_gamepad_axes[0]); i++) { + if (dev->axis_code[axis] == unix_gamepad_axes[i]) { + _tcscpy(name, unix_gamepad_axis_names[i]); + return; + } + } + } + _sntprintf(name, 64, _T("Axis %d"), dev->axis_code[axis] + 1); + name[63] = 0; + return; + case UNIX_AXIS_GAMEPAD_DPAD_X: + _tcscpy(name, _T("DPad X Axis")); + return; + case UNIX_AXIS_GAMEPAD_DPAD_Y: + _tcscpy(name, _T("DPad Y Axis")); + return; + case UNIX_AXIS_HAT_X: + _sntprintf(name, 64, _T("Hat %d X Axis"), dev->axis_code[axis] + 1); + name[63] = 0; + return; + case UNIX_AXIS_HAT_Y: + _sntprintf(name, 64, _T("Hat %d Y Axis"), dev->axis_code[axis] + 1); + name[63] = 0; + return; + } + _tcscpy(name, _T("Axis")); +} + +static void joystick_button_name(unix_joystick_device *dev, int button, TCHAR *name) +{ + if (!name) { + return; + } + if (dev->is_gamepad) { + for (int i = 0; i < (int)(sizeof unix_gamepad_buttons / sizeof unix_gamepad_buttons[0]); i++) { + if (dev->button_code[button] == unix_gamepad_buttons[i]) { + _tcscpy(name, unix_gamepad_button_names[i]); + return; + } + } + } + _sntprintf(name, 64, _T("Button %d"), dev->button_code[button] + 1); + name[63] = 0; +} + +static int joystick_get_widget_type(int joy, int widget, TCHAR *name, uae_u32 *code) +{ + if (joy < 0 || joy >= unix_joystick_count) { + return IDEV_WIDGET_NONE; + } + unix_joystick_device *dev = &unix_joysticks[joy]; + if (code) { + *code = widget; + } + if (widget >= 0 && widget < dev->axis_count) { + joystick_axis_name(dev, widget, name); + return IDEV_WIDGET_AXIS; + } + int button = widget - dev->axis_count; + if (button >= 0 && button < dev->button_count) { + joystick_button_name(dev, button, name); + return IDEV_WIDGET_BUTTON; + } + return IDEV_WIDGET_NONE; +} + +static int joystick_get_widget_first(int joy, int type) +{ + if (joy < 0 || joy >= unix_joystick_count) { + return -1; + } + switch (type) { + case IDEV_WIDGET_AXIS: + return unix_joysticks[joy].axis_count > 0 ? 0 : -1; + case IDEV_WIDGET_BUTTON: + return unix_joysticks[joy].button_count > 0 ? unix_joysticks[joy].axis_count : -1; + } + return -1; +} + +static int joystick_get_flags(int) { return 0; } + +static bool joystick_has_button(int joy, int button) +{ + return joy >= 0 && joy < unix_joystick_count && button >= 0 && button < unix_joysticks[joy].button_count; +} + +static bool joystick_axis_is_dpad_or_hat(int joy, int axis) +{ + if (joy < 0 || joy >= unix_joystick_count || axis < 0 || axis >= unix_joysticks[joy].axis_count) { + return false; + } + int kind = unix_joysticks[joy].axis_kind[axis]; + return kind == UNIX_AXIS_GAMEPAD_DPAD_X || kind == UNIX_AXIS_GAMEPAD_DPAD_Y || + kind == UNIX_AXIS_HAT_X || kind == UNIX_AXIS_HAT_Y; +} + +void unix_input_joystick_device_changed(void) +{ + if (unix_joystick_sdl_initialized) { + joystick_open_devices(); + } +} +#else +static int joystick_init(void) { return input_init(); } +static void joystick_close(void) {} +static int joystick_acquire(int, int) { return 1; } +static void joystick_unacquire(int) {} +static void joystick_read(void) {} +static int joystick_get_num(void) { return 0; } +static TCHAR *joystick_get_friendlyname(int) { return empty_friendly; } +static TCHAR *joystick_get_uniquename(int) { return empty_unique_name; } +static int joystick_get_widget_num(int) { return 0; } +static int joystick_get_widget_type(int, int, TCHAR *, uae_u32 *) { return IDEV_WIDGET_NONE; } +static int joystick_get_widget_first(int, int) { return -1; } +static int joystick_get_flags(int) { return 0; } +static bool joystick_has_button(int, int) { return false; } +static bool joystick_axis_is_dpad_or_hat(int, int) { return false; } +void unix_input_joystick_device_changed(void) {} +#endif + +inputdevice_functions inputdevicefunc_joystick = { + joystick_init, joystick_close, joystick_acquire, joystick_unacquire, joystick_read, + joystick_get_num, joystick_get_friendlyname, joystick_get_uniquename, + joystick_get_widget_num, joystick_get_widget_type, joystick_get_widget_first, + joystick_get_flags +}; + +inputdevice_functions inputdevicefunc_mouse = { + input_init, input_close, input_acquire, input_unacquire, input_read, + mouse_get_num, mouse_get_friendlyname, mouse_get_uniquename, + mouse_get_widget_num, mouse_get_widget_type, mouse_get_widget_first, + mouse_get_flags +}; + +inputdevice_functions inputdevicefunc_keyboard = { + input_init, input_close, input_acquire, input_unacquire, input_read, + keyboard_get_num, keyboard_get_friendlyname, keyboard_get_uniquename, + keyboard_get_widget_num, keyboard_get_widget_type, keyboard_get_widget_first, + keyboard_get_flags +}; + +static int nextsub(struct uae_input_device *uid, int dev, int slot, int sub) +{ + if (currprefs.input_advancedmultiinput) { + while (uid[dev].eventid[slot][sub] > 0) { + sub++; + if (sub >= MAX_INPUT_SUB_EVENT) { + return -1; + } + } + } + return sub; +} + +static void setid(struct uae_input_device *uid, int dev, int slot, int sub, int port, int evt, bool gp) +{ + sub = nextsub(uid, dev, slot, sub); + if (sub < 0 || evt <= 0) { + return; + } + if (gp && sub == 0) { + inputdevice_sparecopy(&uid[dev], slot, sub); + } + uid[dev].eventid[slot][sub] = evt; + uid[dev].port[slot][sub] = port + 1; +} + +static void setid(struct uae_input_device *uid, int dev, int slot, int sub, int port, int evt, int af, bool gp) +{ + sub = nextsub(uid, dev, slot, sub); + if (sub < 0) { + return; + } + setid(uid, dev, slot, sub, port, evt, gp); + uid[dev].flags[slot][sub] &= ~ID_FLAG_AUTOFIRE_MASK; + if (af >= JPORT_AF_NORMAL) { + uid[dev].flags[slot][sub] |= ID_FLAG_AUTOFIRE; + } + if (af == JPORT_AF_TOGGLE) { + uid[dev].flags[slot][sub] |= ID_FLAG_TOGGLE; + } + if (af == JPORT_AF_ALWAYS) { + uid[dev].flags[slot][sub] |= ID_FLAG_INVERTTOGGLE; + } + if (af == JPORT_AF_TOGGLENOAF) { + uid[dev].flags[slot][sub] |= ID_FLAG_INVERT; + } +} + +void unix_input_mouse_motion(int dx, int dy) +{ + if (dx) { + setmousestate(0, 0, dx, 0); + } + if (dy) { + setmousestate(0, 1, dy, 0); + } +} + +void unix_input_mouse_button(int button, bool pressed) +{ + if (button >= 0 && button < 3) { + setmousebuttonstate(0, button, pressed ? 1 : 0); + } +} + +void unix_input_mouse_wheel(int, int y) +{ + if (y) { + setmousestate(0, 2, y * 120, 0); + } +} + +void unix_input_set_mouse_active(bool active) +{ + mouse_active = active; +} + +bool unix_input_get_mouse_active(void) +{ + return mouse_active; +} + +static void unix_input_update_lock_state(int lockstate) +{ + host_capslockstate = (lockstate & UNIX_INPUT_LOCK_CAPS) != 0; + host_numlockstate = (lockstate & UNIX_INPUT_LOCK_NUM) != 0; + host_scrolllockstate = (lockstate & UNIX_INPUT_LOCK_SCROLL) != 0; +} + +void unix_input_keyboard_key(int scancode, bool pressed, int lockstate) +{ + if (scancode <= 0 || scancode >= (int)(sizeof keyboard_state / sizeof keyboard_state[0])) { + return; + } + unix_input_update_lock_state(lockstate); + if (keyboard_state[scancode] == pressed) { + return; + } + + keyboard_state[scancode] = pressed; + inputdevice_translatekeycode(0, scancode, pressed ? 1 : 0, false); +} + +void unix_input_release_keys(void) +{ + for (int scancode = 0; scancode < (int)(sizeof keyboard_state / sizeof keyboard_state[0]); scancode++) { + if (keyboard_state[scancode]) { + keyboard_state[scancode] = false; + inputdevice_translatekeycode(0, scancode, 0, true); + } + } + setmousebuttonstateall(0, 0, 7); +} + +void release_keys(void) { unix_input_release_keys(); } +int input_get_default_keyboard(int num) +{ + if (num < 0) { + return 0; + } + return num == 0 ? 1 : 0; +} +int input_get_default_mouse(uae_input_device *uid, int dev, int port, int af, bool gp, bool wheel, bool joymouseswap) +{ + if (joymouseswap || dev != 0) { + return 0; + } + + setid(uid, dev, ID_AXIS_OFFSET + 0, 0, port, port ? INPUTEVENT_MOUSE2_HORIZ : INPUTEVENT_MOUSE1_HORIZ, gp); + setid(uid, dev, ID_AXIS_OFFSET + 1, 0, port, port ? INPUTEVENT_MOUSE2_VERT : INPUTEVENT_MOUSE1_VERT, gp); + if (wheel && port == 0) { + setid(uid, dev, ID_AXIS_OFFSET + 2, 0, port, INPUTEVENT_MOUSE1_WHEEL, gp); + } + setid(uid, dev, ID_BUTTON_OFFSET + 0, 0, port, port ? INPUTEVENT_JOY2_FIRE_BUTTON : INPUTEVENT_JOY1_FIRE_BUTTON, af, gp); + setid(uid, dev, ID_BUTTON_OFFSET + 1, 0, port, port ? INPUTEVENT_JOY2_2ND_BUTTON : INPUTEVENT_JOY1_2ND_BUTTON, gp); + setid(uid, dev, ID_BUTTON_OFFSET + 2, 0, port, port ? INPUTEVENT_JOY2_3RD_BUTTON : INPUTEVENT_JOY1_3RD_BUTTON, gp); + + return 1; +} +int input_get_default_lightpen(uae_input_device *, int, int, int, bool, bool, int) { return 0; } +int input_get_default_joystick(uae_input_device *uid, int dev, int port, int af, int mode, bool gp, bool joymouseswap, bool default_osk) +{ + if (joymouseswap || dev < 0 || dev >= joystick_get_num()) { + return 0; + } + + int h; + int v; + if (mode == JSEM_MODE_MOUSE_CDTV) { + h = INPUTEVENT_MOUSE_CDTV_HORIZ; + v = INPUTEVENT_MOUSE_CDTV_VERT; + } else if (port >= 2) { + h = port == 3 ? INPUTEVENT_PAR_JOY2_HORIZ : INPUTEVENT_PAR_JOY1_HORIZ; + v = port == 3 ? INPUTEVENT_PAR_JOY2_VERT : INPUTEVENT_PAR_JOY1_VERT; + } else { + h = port ? INPUTEVENT_JOY2_HORIZ : INPUTEVENT_JOY1_HORIZ; + v = port ? INPUTEVENT_JOY2_VERT : INPUTEVENT_JOY1_VERT; + } + + setid(uid, dev, ID_AXIS_OFFSET + 0, 0, port, h, gp); + setid(uid, dev, ID_AXIS_OFFSET + 1, 0, port, v, gp); + int first_button = joystick_get_widget_first(dev, IDEV_WIDGET_BUTTON); + if (first_button < 0) { + first_button = joystick_get_widget_num(dev); + } + for (int axis = 2; axis < first_button; axis++) { + if (!joystick_axis_is_dpad_or_hat(dev, axis) || axis + 1 >= first_button) { + continue; + } + if (joystick_axis_is_dpad_or_hat(dev, axis + 1)) { + setid(uid, dev, ID_AXIS_OFFSET + axis, 0, port, h, gp); + setid(uid, dev, ID_AXIS_OFFSET + axis + 1, 0, port, v, gp); + axis++; + } + } + + if (port >= 2) { + setid(uid, dev, ID_BUTTON_OFFSET + 0, 0, port, + port == 3 ? INPUTEVENT_PAR_JOY2_FIRE_BUTTON : INPUTEVENT_PAR_JOY1_FIRE_BUTTON, af, gp); + } else { + setid(uid, dev, ID_BUTTON_OFFSET + 0, 0, port, + port ? INPUTEVENT_JOY2_FIRE_BUTTON : INPUTEVENT_JOY1_FIRE_BUTTON, af, gp); + if (joystick_has_button(dev, 1)) { + setid(uid, dev, ID_BUTTON_OFFSET + 1, 0, port, + port ? INPUTEVENT_JOY2_2ND_BUTTON : INPUTEVENT_JOY1_2ND_BUTTON, gp); + } + if (mode != JSEM_MODE_JOYSTICK && joystick_has_button(dev, 2)) { + setid(uid, dev, ID_BUTTON_OFFSET + 2, 0, port, + port ? INPUTEVENT_JOY2_3RD_BUTTON : INPUTEVENT_JOY1_3RD_BUTTON, gp); + } + if (default_osk && joystick_has_button(dev, 3)) { + setid(uid, dev, ID_BUTTON_OFFSET + 3, 0, port, INPUTEVENT_SPC_OSK, gp); + } + } + + if (mode == JSEM_MODE_JOYSTICK_CD32) { + setid(uid, dev, ID_BUTTON_OFFSET + 0, 0, port, + port ? INPUTEVENT_JOY2_CD32_RED : INPUTEVENT_JOY1_CD32_RED, af, gp); + if (joystick_has_button(dev, 1)) { + setid(uid, dev, ID_BUTTON_OFFSET + 1, 0, port, + port ? INPUTEVENT_JOY2_CD32_BLUE : INPUTEVENT_JOY1_CD32_BLUE, gp); + } + if (joystick_has_button(dev, 2)) { + setid(uid, dev, ID_BUTTON_OFFSET + 2, 0, port, + port ? INPUTEVENT_JOY2_CD32_GREEN : INPUTEVENT_JOY1_CD32_GREEN, gp); + } + if (joystick_has_button(dev, 3)) { + setid(uid, dev, ID_BUTTON_OFFSET + 3, 0, port, + port ? INPUTEVENT_JOY2_CD32_YELLOW : INPUTEVENT_JOY1_CD32_YELLOW, gp); + } + if (joystick_has_button(dev, 4)) { + setid(uid, dev, ID_BUTTON_OFFSET + 4, 0, port, + port ? INPUTEVENT_JOY2_CD32_RWD : INPUTEVENT_JOY1_CD32_RWD, gp); + } + if (joystick_has_button(dev, 5)) { + setid(uid, dev, ID_BUTTON_OFFSET + 5, 0, port, + port ? INPUTEVENT_JOY2_CD32_FFW : INPUTEVENT_JOY1_CD32_FFW, gp); + } + if (joystick_has_button(dev, 6)) { + setid(uid, dev, ID_BUTTON_OFFSET + 6, 0, port, + port ? INPUTEVENT_JOY2_CD32_PLAY : INPUTEVENT_JOY1_CD32_PLAY, gp); + } + } + + return dev == 0 ? 1 : 0; +} + +int input_get_default_joystick_analog(uae_input_device *uid, int dev, int port, int af, bool gp, bool joymouseswap, bool default_osk) +{ + if (joymouseswap || dev < 0 || dev >= joystick_get_num()) { + return 0; + } + + setid(uid, dev, ID_AXIS_OFFSET + 0, 0, port, port ? INPUTEVENT_JOY2_HORIZ_POT : INPUTEVENT_JOY1_HORIZ_POT, gp); + setid(uid, dev, ID_AXIS_OFFSET + 1, 0, port, port ? INPUTEVENT_JOY2_VERT_POT : INPUTEVENT_JOY1_VERT_POT, gp); + setid(uid, dev, ID_BUTTON_OFFSET + 0, 0, port, port ? INPUTEVENT_JOY2_LEFT : INPUTEVENT_JOY1_LEFT, af, gp); + if (joystick_has_button(dev, 1)) { + setid(uid, dev, ID_BUTTON_OFFSET + 1, 0, port, port ? INPUTEVENT_JOY2_RIGHT : INPUTEVENT_JOY1_RIGHT, gp); + } + if (joystick_has_button(dev, 2)) { + setid(uid, dev, ID_BUTTON_OFFSET + 2, 0, port, port ? INPUTEVENT_JOY2_UP : INPUTEVENT_JOY1_UP, gp); + } + if (joystick_has_button(dev, 3)) { + setid(uid, dev, ID_BUTTON_OFFSET + 3, 0, port, port ? INPUTEVENT_JOY2_DOWN : INPUTEVENT_JOY1_DOWN, gp); + } + if (default_osk && joystick_has_button(dev, 4)) { + setid(uid, dev, ID_BUTTON_OFFSET + 4, 0, port, INPUTEVENT_SPC_OSK, gp); + } + + return dev == 0 ? 1 : 0; +} +int is_tablet(void) { return 0; } +bool ismouseactive(void) { return unix_input_get_mouse_active(); } +void setmouseactive(int, int active) { unix_input_set_mouse_active(active != 0); } +bool target_can_autoswitchdevice(void) { return false; } +void target_inputdevice_acquire(void) {} +void target_inputdevice_unacquire(bool) {} +int getcapslockstate(void) { return capslockstate; } +void setcapslockstate(int state) { capslockstate = state; } +int target_checkcapslock(int scancode, int *state) +{ + if (scancode != UKEY_CAPSLOCK && scancode != UKEY_NUMLOCKCLEAR && scancode != UKEY_SCROLLLOCK) { + return 0; + } + if (currprefs.keyboard_mode > 0) { + return 1; + } + if (*state == 0) { + return -1; + } + if (scancode == UKEY_CAPSLOCK) { + *state = host_capslockstate; + if (gui_data.capslock != (host_capslockstate != 0)) { + gui_data.capslock = host_capslockstate != 0; + gui_led(LED_CAPS, gui_data.capslock, -1); + } + } else if (scancode == UKEY_NUMLOCKCLEAR) { + *state = host_numlockstate; + } else if (scancode == UKEY_SCROLLLOCK) { + *state = host_scrolllockstate; + } + return 1; +} diff --git a/od-unix/input.h b/od-unix/input.h new file mode 100644 index 00000000..89704c0e --- /dev/null +++ b/od-unix/input.h @@ -0,0 +1,18 @@ +#ifndef WINUAE_OD_UNIX_INPUT_H +#define WINUAE_OD_UNIX_INPUT_H + +void unix_input_mouse_motion(int dx, int dy); +void unix_input_mouse_button(int button, bool pressed); +void unix_input_mouse_wheel(int x, int y); +void unix_input_set_mouse_active(bool active); +bool unix_input_get_mouse_active(void); +enum { + UNIX_INPUT_LOCK_CAPS = 1 << 0, + UNIX_INPUT_LOCK_NUM = 1 << 1, + UNIX_INPUT_LOCK_SCROLL = 1 << 2 +}; +void unix_input_keyboard_key(int scancode, bool pressed, int lockstate); +void unix_input_release_keys(void); +void unix_input_joystick_device_changed(void); + +#endif /* WINUAE_OD_UNIX_INPUT_H */ diff --git a/od-unix/sampler_sdl.cpp b/od-unix/sampler_sdl.cpp new file mode 100644 index 00000000..9b531000 --- /dev/null +++ b/od-unix/sampler_sdl.cpp @@ -0,0 +1,244 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#ifdef WINUAE_UNIX_WITH_SAMPLER + +#define SDL_MAIN_HANDLED +#include +#include + +#include "options.h" +#include "events.h" +#include "sampler.h" +#include "sound_unix.h" +#include "uae.h" + +#define UNIX_SAMPLER_MAX_DEVICES 100 +#define UNIX_SAMPLER_FRAME_BYTES 4 + +float sampler_evtime; + +struct unix_sampler_device { + SDL_AudioDeviceID id; + TCHAR name[256]; + TCHAR config_name[320]; +}; + +static unix_sampler_device sampler_devices[UNIX_SAMPLER_MAX_DEVICES]; +static int sampler_device_count; +static bool sampler_devices_enumerated; +static bool sampler_audio_initialized; +static SDL_AudioStream *sampler_stream; +static SDL_AudioSpec sampler_spec; +static int sampler_inited; +static uae_s16 sampler_last[2]; + +static bool ensure_sampler_audio(void) +{ + if (sampler_audio_initialized) { + return true; + } + SDL_SetMainReady(); + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { + write_log(_T("SDL3: sampler audio unavailable: %s\n"), SDL_GetError()); + return false; + } + sampler_audio_initialized = true; + return true; +} + +static void reset_sampler_devices(void) +{ + memset(sampler_devices, 0, sizeof sampler_devices); + sampler_device_count = 0; + sampler_devices_enumerated = false; +} + +static void add_sampler_device(SDL_AudioDeviceID id, const char *name) +{ + if (sampler_device_count >= UNIX_SAMPLER_MAX_DEVICES) { + return; + } + unix_sampler_device *device = &sampler_devices[sampler_device_count++]; + device->id = id; + const char *display_name = name && name[0] ? name : "Default Recording Device"; + _tcsncpy(device->name, display_name, sizeof device->name / sizeof(TCHAR) - 1); + device->name[sizeof device->name / sizeof(TCHAR) - 1] = 0; + _sntprintf(device->config_name, sizeof device->config_name / sizeof(TCHAR), + _T("SDL:%s"), device->name); +} + +static void enumerate_sdl_sampler_devices(void) +{ + int count = 0; + SDL_AudioDeviceID *devices; + + if (sampler_devices_enumerated) { + return; + } + reset_sampler_devices(); + add_sampler_device(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, "Default Recording Device"); + devices = SDL_GetAudioRecordingDevices(&count); + if (devices) { + for (int i = 0; i < count; i++) { + const char *name = SDL_GetAudioDeviceName(devices[i]); + add_sampler_device(devices[i], name); + } + SDL_free(devices); + } + sampler_devices_enumerated = true; +} + +static int enumerate_sampler_devices(void) +{ + if (!ensure_sampler_audio()) { + return 0; + } + enumerate_sdl_sampler_devices(); + return sampler_device_count; +} + +int unix_sampler_device_count(void) +{ + return enumerate_sampler_devices(); +} + +const TCHAR *unix_sampler_device_name(int index) +{ + if (index < 0 || index >= enumerate_sampler_devices()) { + return _T(""); + } + return sampler_devices[index].name; +} + +const TCHAR *unix_sampler_device_config_name(int index) +{ + if (index < 0 || index >= enumerate_sampler_devices()) { + return _T(""); + } + return sampler_devices[index].config_name; +} + +int unix_sampler_device_index_from_config_name(const TCHAR *name) +{ + TCHAR prefixed[320]; + + if (!name || !name[0]) { + return -1; + } + enumerate_sampler_devices(); + for (int i = 0; i < sampler_device_count; i++) { + if (!_tcsicmp(sampler_devices[i].config_name, name) || !_tcsicmp(sampler_devices[i].name, name)) { + return i; + } + } + if (_tcsncmp(name, _T("SDL:"), 4) != 0) { + _sntprintf(prefixed, sizeof prefixed / sizeof(TCHAR), _T("SDL:%s"), name); + for (int i = 0; i < sampler_device_count; i++) { + if (!_tcsicmp(sampler_devices[i].config_name, prefixed)) { + return i; + } + } + } + return -1; +} + +static bool open_sampler_stream(void) +{ + int device_index; + SDL_AudioDeviceID device_id; + + if (!ensure_sampler_audio()) { + return false; + } + if (enumerate_sampler_devices() <= 0) { + write_log(_T("SDL3: no recording audio devices available\n")); + return false; + } + device_index = currprefs.win32_samplersoundcard; + if (device_index < 0 || device_index >= sampler_device_count) { + write_log(_T("SDL3: sampler input device is not selected\n")); + return false; + } + device_id = sampler_devices[device_index].id; + + memset(&sampler_spec, 0, sizeof sampler_spec); + sampler_spec.freq = currprefs.sampler_freq > 0 ? currprefs.sampler_freq : 44100; + sampler_spec.format = SDL_AUDIO_S16; + sampler_spec.channels = 2; + sampler_stream = SDL_OpenAudioDeviceStream(device_id, &sampler_spec, NULL, NULL); + if (!sampler_stream) { + write_log(_T("SDL3: failed to open sampler device '%s': %s\n"), + sampler_devices[device_index].config_name, SDL_GetError()); + return false; + } + SDL_ResumeAudioStreamDevice(sampler_stream); + write_log(_T("SDL3: sampler input initialized, '%s'\n"), sampler_devices[device_index].config_name); + return true; +} + +int sampler_init(void) +{ + return currprefs.win32_samplersoundcard >= 0 && enumerate_sampler_devices() > 0; +} + +void sampler_free(void) +{ + if (sampler_stream) { + SDL_DestroyAudioStream(sampler_stream); + sampler_stream = NULL; + } + sampler_inited = 0; + sampler_last[0] = 0; + sampler_last[1] = 0; +} + +static void update_sampler_frame(void) +{ + uae_s16 frame[2]; + int available; + + if (!sampler_inited) { + if (!open_sampler_stream()) { + sampler_free(); + return; + } + sampler_inited = 1; + } + if (!sampler_stream) { + return; + } + available = SDL_GetAudioStreamAvailable(sampler_stream); + while (available >= UNIX_SAMPLER_FRAME_BYTES) { + if (SDL_GetAudioStreamData(sampler_stream, frame, sizeof frame) != sizeof frame) { + break; + } + sampler_last[0] = frame[0]; + sampler_last[1] = frame[1]; + available -= UNIX_SAMPLER_FRAME_BYTES; + } +} + +uae_u8 sampler_getsample(int channel) +{ + int sample; + + if (!currprefs.sampler_stereo) { + channel = 0; + } + channel = channel ? 1 : 0; + update_sampler_frame(); + sample = sampler_last[channel] / 128; + if (sample < -128) { + sample = 0; + } else if (sample > 127) { + sample = 127; + } + return (uae_u8)(sample - 128); +} + +void sampler_vsync(void) +{ +} + +#endif diff --git a/od-unix/sound.cpp b/od-unix/sound.cpp new file mode 100644 index 00000000..ce820903 --- /dev/null +++ b/od-unix/sound.cpp @@ -0,0 +1,570 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include "options.h" +#include "audio.h" +#ifdef AVIOUTPUT +#include "avioutput.h" +#endif +#include "custom.h" +#include "events.h" +#include "gui.h" +#include "sounddep/sound.h" +#include "gensound.h" +#include "sound_unix.h" +#ifdef DRIVESOUND +#include "driveclick.h" +#endif + +#ifdef UAE_UNIX_WITH_SDL3 +#define SDL_MAIN_HANDLED +#include +#include +#endif + +#define UNIX_SOUND_MAX_BUFFER_BYTES (65536 * 2 + 1024) +#define UNIX_SOUND_MIN_FRAMES 128 +#define UNIX_SOUND_QUEUE_BUFFERS 4 +#define UNIX_SOUND_MAX_DEVICES 100 + +int active_sound_stereo; +uae_u16 paula_sndbuffer[UNIX_SOUND_MAX_BUFFER_BYTES / sizeof(uae_u16)]; +uae_u16 *paula_sndbufpt = paula_sndbuffer; +int paula_sndbufsize = DEFAULT_SOUND_MAXB; + +static int have_sound; +static int sound_muted; +static int sound_softvolume = -1; +static float sound_sync_multiplier = 1.0f; +static float scaled_sample_evtime_orig; + +extern float sampler_evtime; + +#ifdef UAE_UNIX_WITH_SDL3 +static SDL_AudioStream *audio_stream; +static SDL_AudioSpec audio_spec; +static bool audio_subsystem_initialized; +#endif + +struct unix_sound_device { +#ifdef UAE_UNIX_WITH_SDL3 + SDL_AudioDeviceID id; +#endif + TCHAR name[256]; + TCHAR config_name[320]; +}; + +static unix_sound_device sound_devices[UNIX_SOUND_MAX_DEVICES]; +static int sound_device_count; +static bool sound_devices_enumerated; + +static void reset_sound_devices(void) +{ + memset(sound_devices, 0, sizeof sound_devices); + sound_device_count = 0; + sound_devices_enumerated = false; +} + +#ifdef UAE_UNIX_WITH_SDL3 +static void add_sound_device(SDL_AudioDeviceID id, const char *name, const TCHAR *config_prefix) +{ + if (sound_device_count >= UNIX_SOUND_MAX_DEVICES) { + return; + } + + unix_sound_device *device = &sound_devices[sound_device_count++]; + device->id = id; + const char *display_name = name && name[0] ? name : "Default Audio Device"; + _tcsncpy(device->name, display_name, sizeof device->name / sizeof(TCHAR) - 1); + device->name[sizeof device->name / sizeof(TCHAR) - 1] = 0; + _sntprintf(device->config_name, sizeof device->config_name / sizeof(TCHAR), + _T("%s%s"), config_prefix, device->name); +} + +static void enumerate_sdl_sound_devices(void) +{ + int count = 0; + SDL_AudioDeviceID *devices; + + if (sound_devices_enumerated) { + return; + } + reset_sound_devices(); + add_sound_device(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, "Default Audio Device", _T("SDL:")); + + devices = SDL_GetAudioPlaybackDevices(&count); + if (devices) { + for (int i = 0; i < count; i++) { + const char *name = SDL_GetAudioDeviceName(devices[i]); + add_sound_device(devices[i], name, _T("SDL:")); + } + SDL_free(devices); + } + sound_devices_enumerated = true; +} +#endif + +static void clearbuffer(void) +{ + memset(paula_sndbuffer, 0, sizeof paula_sndbuffer); + paula_sndbufpt = paula_sndbuffer; +#ifdef UAE_UNIX_WITH_SDL3 + if (audio_stream) { + SDL_ClearAudioStream(audio_stream); + } +#endif +} + +static void channelswap(uae_s16 *sndbuffer, int len) +{ + for (int i = 0; i < len; i += 2) { + uae_s16 t = sndbuffer[i]; + sndbuffer[i] = sndbuffer[i + 1]; + sndbuffer[i + 1] = t; + } +} + +static void channelswap6(uae_s16 *sndbuffer, int len) +{ + for (int i = 0; i < len; i += 6) { + uae_s16 t = sndbuffer[i + 0]; + sndbuffer[i + 0] = sndbuffer[i + 1]; + sndbuffer[i + 1] = t; + t = sndbuffer[i + 4]; + sndbuffer[i + 4] = sndbuffer[i + 5]; + sndbuffer[i + 5] = t; + } +} + +static int get_sound_channels(void) +{ + int channels = get_audio_nativechannels(active_sound_stereo); + if (channels < 1) { + channels = 2; + } + return channels; +} + +static int get_sound_buffer_frames(int channels) +{ + int frames = currprefs.sound_maxbsiz > 0 ? currprefs.sound_maxbsiz : DEFAULT_SOUND_MAXB; + + frames >>= 2; + frames &= ~63; + if (frames < UNIX_SOUND_MIN_FRAMES) { + frames = UNIX_SOUND_MIN_FRAMES; + } + while (frames * channels * (int)sizeof(uae_s16) > UNIX_SOUND_MAX_BUFFER_BYTES) { + frames >>= 1; + } + if (frames < UNIX_SOUND_MIN_FRAMES) { + frames = UNIX_SOUND_MIN_FRAMES; + } + return frames; +} + +static void update_softvolume(void) +{ + int volume = currprefs.sound_volume_master; + + if (volume < 0) { + volume = 0; + } else if (volume > 100) { + volume = 100; + } + + if (sound_muted || volume >= 100) { + sound_softvolume = 0; + } else { + sound_softvolume = (100 - volume) * 32768 / 100; + if (sound_softvolume >= 32768) { + sound_softvolume = -1; + } + } +} + +int setup_sound(void) +{ +#ifdef UAE_UNIX_WITH_SDL3 + sound_available = 1; + return 1; +#else + sound_available = 0; + return 0; +#endif +} + +void update_sound(float clk) +{ +#ifdef UAE_UNIX_WITH_SDL3 + if (!have_sound || audio_spec.freq <= 0) { + return; + } + scaled_sample_evtime_orig = clk * (float)CYCLE_UNIT * sound_sync_multiplier / audio_spec.freq; + scaled_sample_evtime = scaled_sample_evtime_orig; + sampler_evtime = clk * CYCLE_UNIT * sound_sync_multiplier; +#endif +} + +#ifdef UAE_UNIX_WITH_SDL3 +static bool ensure_audio_subsystem(void) +{ + if (audio_subsystem_initialized) { + return true; + } + + SDL_SetMainReady(); + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { + write_log(_T("SDL3: audio unavailable: %s\n"), SDL_GetError()); + return false; + } + audio_subsystem_initialized = true; + return true; +} + +static int selected_sound_device(void) +{ + int devices = enumerate_sound_devices(); + if (devices <= 0) { + return -1; + } + if (currprefs.win32_soundcard < 0 || currprefs.win32_soundcard >= devices) { + currprefs.win32_soundcard = changed_prefs.win32_soundcard = 0; + } + return currprefs.win32_soundcard; +} + +static bool open_sound_device(void) +{ + SDL_AudioSpec desired; + int channels = get_sound_channels(); + int freq = currprefs.sound_freq > 0 ? currprefs.sound_freq : DEFAULT_SOUND_FREQ; + int device_index; + SDL_AudioDeviceID device_id; + + if (!ensure_audio_subsystem()) { + return false; + } + device_index = selected_sound_device(); + if (device_index < 0) { + write_log(_T("SDL3: no playback audio devices available\n")); + return false; + } + device_id = sound_devices[device_index].id; + + memset(&desired, 0, sizeof desired); + desired.freq = freq; + desired.format = SDL_AUDIO_S16; + desired.channels = channels; + + audio_stream = SDL_OpenAudioDeviceStream(device_id, &desired, NULL, NULL); + if (!audio_stream) { + write_log(_T("SDL3: failed to open audio device '%s': %s\n"), + sound_devices[device_index].config_name, SDL_GetError()); + return false; + } + audio_spec = desired; + SDL_GetAudioStreamFormat(audio_stream, &audio_spec, NULL); + + if (audio_spec.channels != channels) { + active_sound_stereo = get_audio_stereomode(audio_spec.channels); + channels = audio_spec.channels; + } + + currprefs.sound_freq = changed_prefs.sound_freq = audio_spec.freq; + paula_sndbufsize = get_sound_buffer_frames(channels) * channels * (int)sizeof(uae_s16); + if (paula_sndbufsize > UNIX_SOUND_MAX_BUFFER_BYTES) { + paula_sndbufsize = UNIX_SOUND_MAX_BUFFER_BYTES; + } + paula_sndbufpt = paula_sndbuffer; + + if (get_audio_amigachannels(active_sound_stereo) == 4) { + sample_handler = sample16ss_handler; + } else { + sample_handler = get_audio_ismono(active_sound_stereo) ? sample16_handler : sample16s_handler; + } + + update_softvolume(); + clearbuffer(); + SDL_ResumeAudioStreamDevice(audio_stream); + gui_data.sndbuf_avail = true; + write_log(_T("SDL3: audio initialized: %s, %d Hz, %d channels, %d byte buffer\n"), + sound_devices[device_index].config_name, audio_spec.freq, audio_spec.channels, paula_sndbufsize); + return true; +} +#endif + +int init_sound(void) +{ +#ifdef UAE_UNIX_WITH_SDL3 + gui_data.sndbuf = 0; + gui_data.sndbuf_status = 3; + gui_data.sndbuf_avail = false; + + if (!sound_available || currprefs.produce_sound <= 1) { + return 0; + } + if (have_sound) { + return 1; + } + if (!open_sound_device()) { + return 0; + } + + have_sound = 1; +#ifdef DRIVESOUND + driveclick_init(); +#endif + return 1; +#else + return 0; +#endif +} + +void close_sound(void) +{ + gui_data.sndbuf = 0; + gui_data.sndbuf_status = 3; + gui_data.sndbuf_avail = false; + if (!have_sound) { + return; + } + +#ifdef UAE_UNIX_WITH_SDL3 + if (audio_stream) { + SDL_PauseAudioStreamDevice(audio_stream); + SDL_ClearAudioStream(audio_stream); + SDL_DestroyAudioStream(audio_stream); + audio_stream = NULL; + } +#endif + have_sound = 0; +#ifdef DRIVESOUND + driveclick_reset(); +#endif + clearbuffer(); +} + +void finish_sound_buffer(void) +{ + int bufsize = (int)((uae_u8 *)paula_sndbufpt - (uae_u8 *)paula_sndbuffer); + + if (currprefs.turbo_emulation) { + paula_sndbufpt = paula_sndbuffer; + return; + } + + if (bufsize <= 0) { + paula_sndbufpt = paula_sndbuffer; + return; + } + + if (currprefs.sound_stereo_swap_paula) { + int channels = get_audio_nativechannels(active_sound_stereo); + if (channels == 2 || channels == 4) { + channelswap((uae_s16 *)paula_sndbuffer, bufsize / (int)sizeof(uae_s16)); + } else if (channels >= 6) { + channelswap6((uae_s16 *)paula_sndbuffer, bufsize / (int)sizeof(uae_s16)); + } + } + + paula_sndbufpt = paula_sndbuffer; + +#ifdef AVIOUTPUT + if (avioutput_audio) { + if (AVIOutput_WriteAudio((uae_u8 *)paula_sndbuffer, bufsize)) { + if (avioutput_nosoundsync) { + sound_setadjust(0); + } + } + } + if (avioutput_enabled && (!avioutput_framelimiter || avioutput_nosoundoutput)) { + return; + } +#endif + +#ifdef UAE_UNIX_WITH_SDL3 + if (!have_sound || !audio_stream) { + return; + } + + if (sound_softvolume >= 0) { + uae_s16 *p = (uae_s16 *)paula_sndbuffer; + for (int i = 0; i < bufsize / (int)sizeof(uae_s16); i++) { + p[i] = (uae_s16)(p[i] * sound_softvolume / 32768); + } + } + + if (!SDL_PutAudioStreamData(audio_stream, paula_sndbuffer, bufsize)) { + write_log(_T("SDL3: SDL_PutAudioStreamData failed: %s\n"), SDL_GetError()); + gui_data.sndbuf_status = -1; + return; + } + + int queued = SDL_GetAudioStreamQueued(audio_stream); + if (queued < 0) { + gui_data.sndbuf_status = -1; + return; + } + int target = paula_sndbufsize * UNIX_SOUND_QUEUE_BUFFERS; + if (queued > target * 3) { + SDL_ClearAudioStream(audio_stream); + gui_data.sndbuf_status = 2; + } else { + gui_data.sndbuf_status = 0; + gui_data.sndbuf = target ? (int)(queued * 1000 / target) : 0; + } +#endif +} + +void restart_sound_buffer(void) +{ + clearbuffer(); +} + +void pause_sound_buffer(void) +{ + reset_sound(); +} + +void resume_sound(void) +{ +#ifdef UAE_UNIX_WITH_SDL3 + if (have_sound && audio_stream) { + SDL_ResumeAudioStreamDevice(audio_stream); + } +#endif +} + +void pause_sound(void) +{ +#ifdef UAE_UNIX_WITH_SDL3 + if (have_sound && audio_stream) { + SDL_PauseAudioStreamDevice(audio_stream); + } +#endif +} + +void reset_sound(void) +{ + clearbuffer(); +} + +bool sound_paused(void) +{ +#ifdef UAE_UNIX_WITH_SDL3 + return !have_sound || !audio_stream || SDL_AudioStreamDevicePaused(audio_stream); +#else + return true; +#endif +} + +void sound_setadjust(float v) +{ + if (v < -6.0f) { + v = -6.0f; + } else if (v > 6.0f) { + v = 6.0f; + } + if (scaled_sample_evtime_orig > 0) { + scaled_sample_evtime = scaled_sample_evtime_orig * (1000.0f + v) / 1000.0f; + } +} + +int enumerate_sound_devices(void) +{ +#ifdef UAE_UNIX_WITH_SDL3 + if (!ensure_audio_subsystem()) { + return 0; + } + enumerate_sdl_sound_devices(); + return sound_device_count; +#else + return 0; +#endif +} + +int unix_sound_device_count(void) +{ + return enumerate_sound_devices(); +} + +const TCHAR *unix_sound_device_name(int index) +{ + if (index < 0 || index >= enumerate_sound_devices()) { + return _T(""); + } + return sound_devices[index].name; +} + +const TCHAR *unix_sound_device_config_name(int index) +{ + if (index < 0 || index >= enumerate_sound_devices()) { + return _T(""); + } + return sound_devices[index].config_name; +} + +int unix_sound_device_index_from_config_name(const TCHAR *name) +{ + TCHAR prefixed[320]; + + if (!name || !name[0]) { + return -1; + } + enumerate_sound_devices(); + for (int i = 0; i < sound_device_count; i++) { + if (!_tcsicmp(sound_devices[i].config_name, name) || !_tcsicmp(sound_devices[i].name, name)) { + return i; + } + } + if (_tcsncmp(name, _T("SDL:"), 4) != 0) { + _sntprintf(prefixed, sizeof prefixed / sizeof(TCHAR), _T("SDL:%s"), name); + for (int i = 0; i < sound_device_count; i++) { + if (!_tcsicmp(sound_devices[i].config_name, prefixed)) { + return i; + } + } + } + return -1; +} + +void sound_mute(int newmute) +{ + if (newmute < 0) { + sound_muted = !sound_muted; + } else { + sound_muted = newmute != 0; + } + update_softvolume(); +} + +void sound_volume(int dir) +{ + currprefs.sound_volume_master -= dir * 10; + if (currprefs.sound_volume_master < 0) { + currprefs.sound_volume_master = 0; + } else if (currprefs.sound_volume_master > 100) { + currprefs.sound_volume_master = 100; + } + changed_prefs.sound_volume_master = currprefs.sound_volume_master; + update_softvolume(); + config_changed = 1; +} + +void set_volume(int volume, int mute) +{ + currprefs.sound_volume_master = volume; + changed_prefs.sound_volume_master = volume; + sound_muted = mute != 0; + update_softvolume(); +} + +void master_sound_volume(int dir) +{ + if (dir == 0) { + sound_mute(-1); + } else { + sound_volume(dir); + } +} diff --git a/od-unix/sound_unix.h b/od-unix/sound_unix.h new file mode 100644 index 00000000..5c63d99d --- /dev/null +++ b/od-unix/sound_unix.h @@ -0,0 +1,15 @@ +#ifndef WINUAE_OD_UNIX_SOUND_UNIX_H +#define WINUAE_OD_UNIX_SOUND_UNIX_H + +#include "sysdeps.h" + +int unix_sound_device_count(void); +const TCHAR *unix_sound_device_name(int index); +const TCHAR *unix_sound_device_config_name(int index); +int unix_sound_device_index_from_config_name(const TCHAR *name); +int unix_sampler_device_count(void); +const TCHAR *unix_sampler_device_name(int index); +const TCHAR *unix_sampler_device_config_name(int index); +int unix_sampler_device_index_from_config_name(const TCHAR *name); + +#endif /* WINUAE_OD_UNIX_SOUND_UNIX_H */ diff --git a/od-unix/sounddep/sound.h b/od-unix/sounddep/sound.h new file mode 100644 index 00000000..4257fe80 --- /dev/null +++ b/od-unix/sounddep/sound.h @@ -0,0 +1,48 @@ +#ifndef WINUAE_OD_UNIX_SOUNDDEP_SOUND_H +#define WINUAE_OD_UNIX_SOUNDDEP_SOUND_H + +#include "uae/types.h" + +#define SOUNDSTUFF 1 +#define SOUND_MODE_NG 0 +#define DEFAULT_SOUND_MAXB 16384 +#define DEFAULT_SOUND_MINB 16384 +#define DEFAULT_SOUND_BITS 16 +#define DEFAULT_SOUND_FREQ 44100 +#define HAVE_STEREO_SUPPORT 1 + +#define FILTER_SOUND_OFF 0 +#define FILTER_SOUND_EMUL 1 +#define FILTER_SOUND_ON 2 +#define FILTER_SOUND_TYPE_A500 0 +#define FILTER_SOUND_TYPE_A1200 1 +#define FILTER_SOUND_TYPE_A500_FIXEDONLY 2 + +extern uae_u16 paula_sndbuffer[]; +extern uae_u16 *paula_sndbufpt; +extern int paula_sndbufsize; +extern int active_sound_stereo; + +void finish_sound_buffer(void); +void restart_sound_buffer(void); +void pause_sound_buffer(void); +int init_sound(void); +void close_sound(void); +int setup_sound(void); +void resume_sound(void); +void pause_sound(void); +void reset_sound(void); +bool sound_paused(void); +void sound_setadjust(float); +int enumerate_sound_devices(void); +void sound_mute(int); +void sound_volume(int); +void set_volume(int, int); +void master_sound_volume(int); + +#define PUT_SOUND_WORD(b) do { *(uae_u16 *)paula_sndbufpt = (b); paula_sndbufpt = (uae_u16 *)(((uae_u8 *)paula_sndbufpt) + 2); } while (0) +#define PUT_SOUND_WORD_MONO(b) PUT_SOUND_WORD(b) +#define SOUND16_BASE_VAL 0 +#define SOUND8_BASE_VAL 128 + +#endif /* WINUAE_OD_UNIX_SOUNDDEP_SOUND_H */ diff --git a/od-unix/video.h b/od-unix/video.h new file mode 100644 index 00000000..bd1b4a47 --- /dev/null +++ b/od-unix/video.h @@ -0,0 +1,48 @@ +#ifndef WINUAE_OD_UNIX_VIDEO_H +#define WINUAE_OD_UNIX_VIDEO_H + +#include "sysdeps.h" + +struct unix_video_frame +{ + const uae_u8 *pixels; + int width; + int height; + int rowbytes; + int pixbytes; + int filter_index; + int monitor_id; + int backbuffers; +}; + +struct unix_video_display_mode +{ + int width; + int height; + int refresh_rate; +}; + +enum unix_video_window_mode +{ + UNIX_VIDEO_WINDOWED = 0, + UNIX_VIDEO_FULLSCREEN = 1, + UNIX_VIDEO_FULLWINDOW = 2 +}; + +bool unix_video_setup(void); +bool unix_video_init(int width, int height, int pixbytes); +void unix_video_shutdown(void); +int unix_video_poll(bool *quit_requested); +int unix_video_poll_window_events(bool *quit_requested); +void unix_video_present(const struct unix_video_frame *frame); +void unix_video_set_title(const TCHAR *title); +bool unix_video_set_window_mode(enum unix_video_window_mode mode, int display_index, int width, int height, int refresh_rate); +enum unix_video_window_mode unix_video_get_window_mode(void); +float unix_video_get_display_refresh_rate(int display_index); +int unix_video_get_display_modes(int display_index, struct unix_video_display_mode *modes, int max_modes); +void unix_video_get_desktop(int *dw, int *dh, int *x, int *y, int *w, int *h); +void unix_video_set_mouse_grab(bool grab); +bool unix_video_get_mouse_grab(void); +void unix_video_toggle_mouse_grab(void); + +#endif /* WINUAE_OD_UNIX_VIDEO_H */ diff --git a/od-unix/video_null.cpp b/od-unix/video_null.cpp new file mode 100644 index 00000000..267b4e11 --- /dev/null +++ b/od-unix/video_null.cpp @@ -0,0 +1,107 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include "video.h" + +bool unix_video_setup(void) +{ + return true; +} + +bool unix_video_init(int, int, int) +{ + return false; +} + +void unix_video_shutdown(void) +{ +} + +int unix_video_poll(bool *quit_requested) +{ + if (quit_requested) { + *quit_requested = false; + } + return 0; +} + +int unix_video_poll_window_events(bool *quit_requested) +{ + if (quit_requested) { + *quit_requested = false; + } + return 0; +} + +void unix_video_present(const struct unix_video_frame *) +{ +} + +void unix_video_set_title(const TCHAR *) +{ +} + +bool unix_video_set_window_mode(enum unix_video_window_mode, int, int, int, int) +{ + return false; +} + +enum unix_video_window_mode unix_video_get_window_mode(void) +{ + return UNIX_VIDEO_WINDOWED; +} + +float unix_video_get_display_refresh_rate(int) +{ + return 0.0f; +} + +int unix_video_get_display_modes(int, struct unix_video_display_mode *, int) +{ + return 0; +} + +void unix_video_set_mouse_grab(bool) +{ +} + +bool unix_video_get_mouse_grab(void) +{ + return false; +} + +void unix_video_toggle_mouse_grab(void) +{ +} + +void unix_video_get_desktop(int *dw, int *dh, int *x, int *y, int *w, int *h) +{ + if (dw) { + *dw = 640; + } + if (dh) { + *dh = 480; + } + if (x) { + *x = 0; + } + if (y) { + *y = 0; + } + if (w) { + *w = 640; + } + if (h) { + *h = 480; + } +} + +int target_get_display(const TCHAR *) +{ + return -1; +} + +const TCHAR *target_get_display_name(int, bool) +{ + return NULL; +} diff --git a/od-unix/video_sdl.cpp b/od-unix/video_sdl.cpp new file mode 100644 index 00000000..c8a0ee4d --- /dev/null +++ b/od-unix/video_sdl.cpp @@ -0,0 +1,2136 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#define SDL_MAIN_HANDLED +#include +#include + +#ifdef WINUAE_UNIX_WITH_OPENGL_SHADER_PIPELINE +#if defined(__APPLE__) +#include +#else +#include +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "statusline.h" +#include "traps.h" +#include "clipboard.h" +#include "disk.h" +#include "gui.h" +#include "input.h" +#include "options.h" +#include "threaddep/thread.h" +#include "uae.h" +#include "video.h" + +extern int pause_emulation; +extern void pausemode(int mode); + +static SDL_Window *s_window; +static SDL_Renderer *s_renderer; +static SDL_Texture *s_texture; +static std::vector s_textures; +static SDL_Texture *s_status_texture; +#ifdef WINUAE_UNIX_WITH_OPENGL_SHADER_PIPELINE +static SDL_GLContext s_gl_context; +static std::vector s_gl_textures; +static GLuint s_gl_texture; +static GLuint s_gl_status_texture; +static GLuint s_gl_program; +static bool s_gl_active; +static bool s_gl_failed; +static bool s_gl_functions_loaded; +static int s_gl_uniform_texture = -1; +static int s_gl_uniform_source_size = -1; +static int s_gl_uniform_adjust = -1; +static int s_gl_uniform_scanline = -1; +static int s_gl_uniform_blur_noise = -1; +static int s_gl_uniform_frame = -1; +static unsigned int s_gl_frame_counter; +#endif +static bool s_setup_done; +static bool s_available; +static std::atomic s_event_thread_valid; +static std::atomic s_wrong_event_thread_logged; +static std::atomic s_queued_present_logged; +static uae_thread_id s_event_thread; +static bool s_mouse_grabbed; +static enum unix_video_window_mode s_requested_window_mode = UNIX_VIDEO_WINDOWED; +static enum unix_video_window_mode s_active_window_mode = UNIX_VIDEO_WINDOWED; +static int s_requested_display_index; +static int s_active_display_index = -1; +static int s_requested_fullscreen_width; +static int s_requested_fullscreen_height; +static int s_requested_fullscreen_refresh; +static int s_active_fullscreen_width = -1; +static int s_active_fullscreen_height = -1; +static int s_active_fullscreen_refresh = -1; +static int s_auto_window_width; +static int s_auto_window_height; +static int s_texture_width; +static int s_texture_height; +static int s_texture_pixbytes; +static int s_texture_backbuffers; +static int s_texture_index; +static int s_status_width; +static int s_status_height; +static std::vector s_status_pixels; +static uae_u32 s_status_rc[256]; +static uae_u32 s_status_gc[256]; +static uae_u32 s_status_bc[256]; +static bool s_status_colors_ready; +static Uint8 s_status_click_button; +static SDL_MouseButtonFlags s_suppressed_mouse_buttons; + +struct unix_pending_video_frame { + std::vector pixels; + int width; + int height; + int rowbytes; + int pixbytes; + int filter_index; + int monitor_id; + int backbuffers; + bool valid; +}; + +struct unix_video_layout { + float pixel_scale_x; + float pixel_scale_y; + SDL_FRect frame_dst; + SDL_FRect status_dst; + SDL_Rect frame_clip; +}; + +static std::mutex s_pending_frame_mutex; +static unix_pending_video_frame s_pending_frame; + +static constexpr int UnixStatusScale = 2; +static TCHAR s_display_name[MAX_DPATH]; + +static void unix_video_present_on_event_thread(const struct unix_video_frame *frame); + +#ifdef WINUAE_UNIX_WITH_OPENGL_SHADER_PIPELINE +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GL_FRAGMENT_SHADER +#define GL_FRAGMENT_SHADER 0x8B30 +#endif +#ifndef GL_VERTEX_SHADER +#define GL_VERTEX_SHADER 0x8B31 +#endif +#ifndef GL_COMPILE_STATUS +#define GL_COMPILE_STATUS 0x8B81 +#endif +#ifndef GL_LINK_STATUS +#define GL_LINK_STATUS 0x8B82 +#endif +#ifndef GL_INFO_LOG_LENGTH +#define GL_INFO_LOG_LENGTH 0x8B84 +#endif +#ifndef GL_CLAMP_TO_EDGE +#define GL_CLAMP_TO_EDGE 0x812F +#endif +#ifndef GL_BGRA +#define GL_BGRA 0x80E1 +#endif +#ifndef GL_UNSIGNED_SHORT_5_6_5 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#endif + +typedef GLuint(APIENTRYP UnixGlCreateShaderProc)(GLenum); +typedef void(APIENTRYP UnixGlShaderSourceProc)(GLuint, GLsizei, const GLchar **, const GLint *); +typedef void(APIENTRYP UnixGlCompileShaderProc)(GLuint); +typedef void(APIENTRYP UnixGlGetShaderivProc)(GLuint, GLenum, GLint *); +typedef void(APIENTRYP UnixGlGetShaderInfoLogProc)(GLuint, GLsizei, GLsizei *, GLchar *); +typedef GLuint(APIENTRYP UnixGlCreateProgramProc)(void); +typedef void(APIENTRYP UnixGlAttachShaderProc)(GLuint, GLuint); +typedef void(APIENTRYP UnixGlLinkProgramProc)(GLuint); +typedef void(APIENTRYP UnixGlGetProgramivProc)(GLuint, GLenum, GLint *); +typedef void(APIENTRYP UnixGlGetProgramInfoLogProc)(GLuint, GLsizei, GLsizei *, GLchar *); +typedef void(APIENTRYP UnixGlDeleteShaderProc)(GLuint); +typedef void(APIENTRYP UnixGlDeleteProgramProc)(GLuint); +typedef void(APIENTRYP UnixGlUseProgramProc)(GLuint); +typedef GLint(APIENTRYP UnixGlGetUniformLocationProc)(GLuint, const GLchar *); +typedef void(APIENTRYP UnixGlUniform1iProc)(GLint, GLint); +typedef void(APIENTRYP UnixGlUniform1fProc)(GLint, GLfloat); +typedef void(APIENTRYP UnixGlUniform2fProc)(GLint, GLfloat, GLfloat); +typedef void(APIENTRYP UnixGlUniform4fProc)(GLint, GLfloat, GLfloat, GLfloat, GLfloat); + +static UnixGlCreateShaderProc p_glCreateShader; +static UnixGlShaderSourceProc p_glShaderSource; +static UnixGlCompileShaderProc p_glCompileShader; +static UnixGlGetShaderivProc p_glGetShaderiv; +static UnixGlGetShaderInfoLogProc p_glGetShaderInfoLog; +static UnixGlCreateProgramProc p_glCreateProgram; +static UnixGlAttachShaderProc p_glAttachShader; +static UnixGlLinkProgramProc p_glLinkProgram; +static UnixGlGetProgramivProc p_glGetProgramiv; +static UnixGlGetProgramInfoLogProc p_glGetProgramInfoLog; +static UnixGlDeleteShaderProc p_glDeleteShader; +static UnixGlDeleteProgramProc p_glDeleteProgram; +static UnixGlUseProgramProc p_glUseProgram; +static UnixGlGetUniformLocationProc p_glGetUniformLocation; +static UnixGlUniform1iProc p_glUniform1i; +static UnixGlUniform1fProc p_glUniform1f; +static UnixGlUniform2fProc p_glUniform2f; +static UnixGlUniform4fProc p_glUniform4f; + +static SDL_FunctionPointer unix_gl_get_proc(const char *name) +{ + SDL_FunctionPointer proc = SDL_GL_GetProcAddress(name); + if (!proc) { + write_log(_T("OpenGL shader pipeline: missing %s\n"), name); + } + return proc; +} +#endif + +static int clamp_window_dimension(int value, int fallback, int maxvalue) +{ + if (value <= 0) { + value = fallback; + } + if (value > maxvalue) { + value = maxvalue; + } + return value; +} + +static SDL_PixelFormat texture_format_for_pixbytes(int pixbytes) +{ + if (pixbytes == 2) { + return SDL_PIXELFORMAT_RGB565; + } + return SDL_PIXELFORMAT_ARGB8888; +} + +static int clamp_backbuffer_count(int backbuffers) +{ + if (backbuffers < 1) { + return 1; + } + if (backbuffers > 3) { + return 3; + } + return backbuffers; +} + +static bool unix_video_on_event_thread(void) +{ + return !s_event_thread_valid.load() || + pthread_equal(uae_thread_get_id(), s_event_thread); +} + +static void queue_video_frame_for_event_thread(const struct unix_video_frame *frame) +{ + if (!frame || !frame->pixels || frame->width <= 0 || frame->height <= 0 || + frame->rowbytes <= 0 || frame->pixbytes <= 0) { + return; + } + + if (!s_queued_present_logged.exchange(true)) { + write_log(_T("SDL3: queueing video present from non-video thread\n")); + } + + const int rowbytes = frame->width * frame->pixbytes; + std::vector pixels((size_t)rowbytes * (size_t)frame->height); + for (int y = 0; y < frame->height; y++) { + memcpy(pixels.data() + (size_t)y * (size_t)rowbytes, + frame->pixels + (size_t)y * (size_t)frame->rowbytes, + (size_t)rowbytes); + } + + std::lock_guard lock(s_pending_frame_mutex); + s_pending_frame.pixels = std::move(pixels); + s_pending_frame.width = frame->width; + s_pending_frame.height = frame->height; + s_pending_frame.rowbytes = rowbytes; + s_pending_frame.pixbytes = frame->pixbytes; + s_pending_frame.filter_index = frame->filter_index; + s_pending_frame.monitor_id = frame->monitor_id; + s_pending_frame.backbuffers = frame->backbuffers; + s_pending_frame.valid = true; +} + +static bool pop_queued_video_frame(unix_pending_video_frame *frame) +{ + std::lock_guard lock(s_pending_frame_mutex); + if (!s_pending_frame.valid) { + return false; + } + *frame = std::move(s_pending_frame); + s_pending_frame = unix_pending_video_frame(); + return true; +} + +static void destroy_frame_textures(void) +{ + for (SDL_Texture *texture : s_textures) { + SDL_DestroyTexture(texture); + } + s_textures.clear(); +#ifdef WINUAE_UNIX_WITH_OPENGL_SHADER_PIPELINE + if (!s_gl_textures.empty()) { + glDeleteTextures((GLsizei)s_gl_textures.size(), s_gl_textures.data()); + s_gl_textures.clear(); + } + s_gl_texture = 0; +#endif + s_texture = NULL; + s_texture_width = 0; + s_texture_height = 0; + s_texture_pixbytes = 0; + s_texture_backbuffers = 0; + s_texture_index = 0; +} + +static SDL_DisplayID *get_sdl_displays(int *count) +{ + if (count) { + *count = 0; + } + if (!unix_video_setup()) { + return NULL; + } + return SDL_GetDisplays(count); +} + +static SDL_DisplayID get_sdl_display_for_index(int display_index) +{ + int count = 0; + SDL_DisplayID *displays = get_sdl_displays(&count); + SDL_DisplayID display = 0; + + if (displays && count > 0) { + if (display_index > 0 && display_index <= count) { + display = displays[display_index - 1]; + } else { + display = displays[0]; + } + } + if (displays) { + SDL_free(displays); + } + if (!display) { + display = SDL_GetPrimaryDisplay(); + } + return display; +} + +static void center_window_on_display(SDL_DisplayID display) +{ + if (!s_window || !display) { + return; + } + SDL_SetWindowPosition(s_window, SDL_WINDOWPOS_CENTERED_DISPLAY(display), SDL_WINDOWPOS_CENTERED_DISPLAY(display)); +} + +static float refresh_rate_from_mode(const SDL_DisplayMode *mode) +{ + if (!mode) { + return 0.0f; + } + if (mode->refresh_rate_numerator > 0 && mode->refresh_rate_denominator > 0) { + return (float)mode->refresh_rate_numerator / (float)mode->refresh_rate_denominator; + } + return mode->refresh_rate; +} + +static int rounded_refresh_rate_from_mode(const SDL_DisplayMode *mode) +{ + float refresh = refresh_rate_from_mode(mode); + return refresh > 0.0f ? (int)(refresh + 0.5f) : 0; +} + +static void add_display_mode(struct unix_video_display_mode *modes, int max_modes, int *count, + int width, int height, int refresh_rate) +{ + if (!modes || !count || width <= 0 || height <= 0) { + return; + } + + for (int i = 0; i < *count; i++) { + if (modes[i].width == width && modes[i].height == height) { + if (!modes[i].refresh_rate && refresh_rate) { + modes[i].refresh_rate = refresh_rate; + } + return; + } + } + if (*count >= max_modes) { + return; + } + + modes[*count].width = width; + modes[*count].height = height; + modes[*count].refresh_rate = refresh_rate; + (*count)++; +} + +static void add_sdl_display_modes(SDL_DisplayID display, struct unix_video_display_mode *modes, int max_modes, int *count) +{ + if (!display) { + return; + } + + int mode_count = 0; + SDL_DisplayMode **fullscreen_modes = SDL_GetFullscreenDisplayModes(display, &mode_count); + if (fullscreen_modes) { + for (int i = 0; i < mode_count; i++) { + const SDL_DisplayMode *mode = fullscreen_modes[i]; + if (mode) { + add_display_mode(modes, max_modes, count, mode->w, mode->h, rounded_refresh_rate_from_mode(mode)); + } + } + SDL_free(fullscreen_modes); + } + + const SDL_DisplayMode *desktop = SDL_GetDesktopDisplayMode(display); + if (desktop) { + add_display_mode(modes, max_modes, count, desktop->w, desktop->h, rounded_refresh_rate_from_mode(desktop)); + } + + const SDL_DisplayMode *current = SDL_GetCurrentDisplayMode(display); + if (current) { + add_display_mode(modes, max_modes, count, current->w, current->h, rounded_refresh_rate_from_mode(current)); + } +} + +static bool copy_sdl_display_name(int index, bool friendlyname, TCHAR *dst, size_t dstsize) +{ + int count = 0; + SDL_DisplayID *displays = get_sdl_displays(&count); + if (!displays) { + return false; + } + + bool found = false; + if (index >= 0 && index < count) { + if (friendlyname) { + const char *name = SDL_GetDisplayName(displays[index]); + if (name && name[0]) { + snprintf(dst, dstsize, "%s", name); + found = true; + } + } else { + snprintf(dst, dstsize, "SDL:%u", (unsigned int)displays[index]); + found = true; + } + } + + SDL_free(displays); + return found; +} + +int target_get_display(const TCHAR *name) +{ + if (!name || !name[0]) { + return -1; + } + + int count = 0; + SDL_DisplayID *displays = get_sdl_displays(&count); + if (!displays) { + return -1; + } + + int found = -1; + unsigned int displayid = 0; + if (sscanf(name, "SDL:%u", &displayid) == 1) { + for (int i = 0; i < count; i++) { + if (displays[i] == (SDL_DisplayID)displayid) { + found = i + 1; + break; + } + } + } else { + for (int i = 0; i < count; i++) { + const char *displayname = SDL_GetDisplayName(displays[i]); + if (displayname && !_tcsicmp(displayname, name)) { + found = i + 1; + break; + } + } + } + + SDL_free(displays); + return found; +} + +const TCHAR *target_get_display_name(int num, bool friendlyname) +{ + if (num <= 0) { + return NULL; + } + if (!copy_sdl_display_name(num - 1, friendlyname, s_display_name, sizeof s_display_name / sizeof s_display_name[0])) { + return NULL; + } + return s_display_name; +} + +static int unix_input_lock_state_from_sdl(SDL_Keymod mod) +{ + int lockstate = 0; + if (mod & SDL_KMOD_CAPS) { + lockstate |= UNIX_INPUT_LOCK_CAPS; + } + if (mod & SDL_KMOD_NUM) { + lockstate |= UNIX_INPUT_LOCK_NUM; + } + if (mod & SDL_KMOD_SCROLL) { + lockstate |= UNIX_INPUT_LOCK_SCROLL; + } + return lockstate; +} + +static int statusbar_source_height(void) +{ + return TD_TOTAL_HEIGHT; +} + +static int statusbar_display_height(void) +{ + return statusbar_source_height() * UnixStatusScale; +} + +static void init_status_colors(void) +{ + if (s_status_colors_ready) { + return; + } + for (int i = 0; i < 256; i++) { + s_status_rc[i] = 0xff000000u | (uae_u32(i) << 16); + s_status_gc[i] = uae_u32(i) << 8; + s_status_bc[i] = uae_u32(i); + } + s_status_colors_ready = true; +} + +static bool ensure_texture(int width, int height, int pixbytes, int backbuffers) +{ + if (!s_renderer || width <= 0 || height <= 0) { + return false; + } + backbuffers = clamp_backbuffer_count(backbuffers); + if (!s_textures.empty() && s_texture_width == width && s_texture_height == height && + s_texture_pixbytes == pixbytes && s_texture_backbuffers == backbuffers) { + return true; + } + + destroy_frame_textures(); + + for (int i = 0; i < backbuffers; i++) { + SDL_Texture *texture = SDL_CreateTexture(s_renderer, texture_format_for_pixbytes(pixbytes), + SDL_TEXTUREACCESS_STREAMING, width, height); + if (!texture) { + write_log(_T("SDL3: failed to create %dx%d texture: %s\n"), width, height, SDL_GetError()); + destroy_frame_textures(); + return false; + } + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE); + s_textures.push_back(texture); + } + s_texture = s_textures[0]; + s_texture_width = width; + s_texture_height = height; + s_texture_pixbytes = pixbytes; + s_texture_backbuffers = backbuffers; + s_texture_index = 0; + return true; +} + +static const struct gfx_filterdata *filterdata_for_frame(const struct unix_video_frame *frame) +{ + int index = frame ? frame->filter_index : GF_NORMAL; + if (index < 0 || index >= MAX_FILTERDATA) { + index = GF_NORMAL; + } + return &currprefs.gf[index]; +} + +static float bounded_scale(float value) +{ + if (!std::isfinite(value) || value < 0.01f) { + return 1.0f; + } + if (value > 64.0f) { + return 64.0f; + } + return value; +} + +static SDL_FRect filtered_frame_rect(const struct unix_video_frame *frame, const struct gfx_filterdata *filter) +{ + float scale_x = 1.0f; + float scale_y = 1.0f; + float offset_x = 0.0f; + float offset_y = 0.0f; + + if (filter) { + if (filter->gfx_filter_horiz_zoom_mult > 0.0f) { + scale_x = filter->gfx_filter_horiz_zoom_mult; + } + if (filter->gfx_filter_vert_zoom_mult > 0.0f) { + scale_y = filter->gfx_filter_vert_zoom_mult; + } + scale_x += scale_x * (filter->gfx_filter_horiz_zoom / 1000.0f) / 2.0f; + scale_y += scale_y * (filter->gfx_filter_vert_zoom / 1000.0f) / 2.0f; + offset_x = -(filter->gfx_filter_horiz_offset / 10000.0f) * frame->width; + offset_y = -(filter->gfx_filter_vert_offset / 10000.0f) * frame->height; + } + + scale_x = bounded_scale(scale_x); + scale_y = bounded_scale(scale_y); + + SDL_FRect rect; + rect.w = frame->width * scale_x; + rect.h = frame->height * scale_y; + rect.x = (frame->width - rect.w) / 2.0f + offset_x; + rect.y = (frame->height - rect.h) / 2.0f + offset_y; + return rect; +} + +static void get_window_pixel_scale(int output_width, int output_height, float *scale_x, float *scale_y) +{ + int window_width = 0; + int window_height = 0; + + if (scale_x) { + *scale_x = 1.0f; + } + if (scale_y) { + *scale_y = 1.0f; + } + if (!s_window || output_width <= 0 || output_height <= 0) { + return; + } + + SDL_GetWindowSize(s_window, &window_width, &window_height); + if (window_width > 0 && scale_x) { + *scale_x = std::max(0.01f, (float)output_width / (float)window_width); + } + if (window_height > 0 && scale_y) { + *scale_y = std::max(0.01f, (float)output_height / (float)window_height); + } +} + +static bool get_window_pixel_size(int *width, int *height) +{ + int window_width = 0; + int window_height = 0; + + if (!s_window) { + return false; + } + SDL_GetWindowSizeInPixels(s_window, &window_width, &window_height); + if (window_width <= 0 || window_height <= 0) { + SDL_GetWindowSize(s_window, &window_width, &window_height); + } + if (window_width <= 0 || window_height <= 0) { + return false; + } + + if (width) { + *width = window_width; + } + if (height) { + *height = window_height; + } + return true; +} + +static bool get_renderer_output_size(int *width, int *height) +{ + int output_width = 0; + int output_height = 0; + + if (s_renderer && SDL_GetRenderOutputSize(s_renderer, &output_width, &output_height) && + output_width > 0 && output_height > 0) { + if (width) { + *width = output_width; + } + if (height) { + *height = output_height; + } + return true; + } + return get_window_pixel_size(width, height); +} + +static bool make_video_layout(const struct unix_video_frame *frame, const struct gfx_filterdata *filter, + int output_width, int output_height, struct unix_video_layout *layout) +{ + if (!frame || !layout || frame->width <= 0 || frame->height <= 0 || + output_width <= 0 || output_height <= 0) { + return false; + } + + memset(layout, 0, sizeof(*layout)); + get_window_pixel_scale(output_width, output_height, &layout->pixel_scale_x, &layout->pixel_scale_y); + + int status_height = std::max(1, (int)(statusbar_display_height() * layout->pixel_scale_y + 0.5f)); + if (status_height >= output_height) { + status_height = std::max(0, output_height - 1); + } + const int frame_area_height = std::max(1, output_height - status_height); + + SDL_FRect source_rect = filtered_frame_rect(frame, filter); + SDL_FRect dst = {}; + int mode = frame->filter_index == GF_RTG && filter ? filter->gfx_filter_autoscale : 0; + + if (frame->filter_index == GF_RTG && mode == 2) { /* center */ + dst.w = source_rect.w * layout->pixel_scale_x; + dst.h = source_rect.h * layout->pixel_scale_y; + dst.x = ((float)output_width - dst.w) / 2.0f + source_rect.x * layout->pixel_scale_x; + dst.y = ((float)frame_area_height - dst.h) / 2.0f + source_rect.y * layout->pixel_scale_y; + } else if (frame->filter_index == GF_RTG && mode == 3) { /* integer */ + int ix = std::max(1, output_width / frame->width); + int iy = std::max(1, frame_area_height / frame->height); + int scale = std::max(1, std::min(ix, iy)); + dst.w = source_rect.w * scale; + dst.h = source_rect.h * scale; + dst.x = ((float)output_width - (float)frame->width * scale) / 2.0f + source_rect.x * scale; + dst.y = ((float)frame_area_height - (float)frame->height * scale) / 2.0f + source_rect.y * scale; + } else { + const float sx = (float)output_width / (float)frame->width; + const float sy = (float)frame_area_height / (float)frame->height; + float draw_sx = sx; + float draw_sy = sy; + + if (frame->filter_index == GF_RTG && mode == 1 && currprefs.win32_rtgscaleaspectratio) { + draw_sx = draw_sy = std::min(sx, sy); + } + dst.w = source_rect.w * draw_sx; + dst.h = source_rect.h * draw_sy; + dst.x = ((float)output_width - (float)frame->width * draw_sx) / 2.0f + source_rect.x * draw_sx; + dst.y = ((float)frame_area_height - (float)frame->height * draw_sy) / 2.0f + source_rect.y * draw_sy; + } + + layout->frame_dst = dst; + layout->frame_clip = { 0, 0, output_width, frame_area_height }; + layout->status_dst = { + 0.0f, + (float)frame_area_height, + (float)output_width, + (float)status_height + }; + return true; +} + +static int valid_monitor_id(int monitor_id) +{ + if (monitor_id < 0 || monitor_id >= MAX_AMIGADISPLAYS) { + return 0; + } + return monitor_id; +} + +static void configured_window_size(int monitor_id, int *width, int *height) +{ + monitor_id = valid_monitor_id(monitor_id); + int w = currprefs.gfx_monitor[monitor_id].gfx_size.width; + int h = currprefs.gfx_monitor[monitor_id].gfx_size.height; + if (w <= 0) { + w = currprefs.gfx_monitor[monitor_id].gfx_size_win.width; + } + if (h <= 0) { + h = currprefs.gfx_monitor[monitor_id].gfx_size_win.height; + } + if (width) { + *width = w; + } + if (height) { + *height = h; + } +} + +static void auto_resize_window_for_rtg(const struct unix_video_frame *frame, + const struct gfx_filterdata *filter) +{ + if (!s_window || s_active_window_mode != UNIX_VIDEO_WINDOWED || !frame || + frame->filter_index != GF_RTG || frame->width <= 0 || frame->height <= 0) { + s_auto_window_width = 0; + s_auto_window_height = 0; + return; + } + + SDL_WindowFlags flags = SDL_GetWindowFlags(s_window); + if (flags & SDL_WINDOW_MAXIMIZED) { + return; + } + + SDL_FRect rect = filtered_frame_rect(frame, filter); + int source_width = std::max(1, (int)std::ceil(rect.w)); + int source_height = std::max(1, (int)std::ceil(rect.h)); + int configured_width = 0; + int configured_height = 0; + int desired_width = source_width; + int desired_height = source_height; + + configured_window_size(frame->monitor_id, &configured_width, &configured_height); + bool have_configured = configured_width > 0 && configured_height > 0; + int mode = filter ? filter->gfx_filter_autoscale : 0; + + switch (mode) { + case 1: /* scale */ + if (have_configured && currprefs.win32_rtgallowscaling) { + desired_width = configured_width; + desired_height = configured_height; + } else if (have_configured) { + desired_width = std::max(configured_width, source_width); + desired_height = std::max(configured_height, source_height); + } + break; + case 2: /* center */ + if (have_configured && + (currprefs.win32_rtgallowscaling || + (configured_width >= source_width && configured_height >= source_height))) { + desired_width = configured_width; + desired_height = configured_height; + } + break; + case 3: /* integer */ + if (have_configured) { + desired_width = configured_width; + desired_height = configured_height; + } + break; + default: /* resize */ + break; + } + + desired_height += statusbar_display_height(); + if (desired_width == s_auto_window_width && desired_height == s_auto_window_height) { + return; + } + + if (SDL_SetWindowSize(s_window, desired_width, desired_height)) { + s_auto_window_width = desired_width; + s_auto_window_height = desired_height; + write_log(_T("SDL3: RTG window resize %dx%d\n"), desired_width, desired_height); + } else { + write_log(_T("SDL3: failed to resize RTG window to %dx%d: %s\n"), + desired_width, desired_height, SDL_GetError()); + } +} + +static int clamp_filter_percent(int value) +{ + if (value < 0) { + return 0; + } + if (value > 100) { + return 100; + } + return value; +} + +static void render_scanline_overlay(const struct gfx_filterdata *filter, const SDL_FRect *frame_dst) +{ + if (!filter || !frame_dst) { + return; + } + + int opacity = clamp_filter_percent(filter->gfx_filter_scanlines); + int level = clamp_filter_percent(filter->gfx_filter_scanlinelevel); + if (!opacity && !level) { + return; + } + + int lit_lines = filter->gfx_filter_scanlineratio & 15; + int shaded_lines = (filter->gfx_filter_scanlineratio >> 4) & 15; + int period = lit_lines + shaded_lines; + if (period <= 0 || shaded_lines <= 0) { + return; + } + + int offset = filter->gfx_filter_scanlineoffset % (lit_lines + 1); + if (offset < 0) { + offset += lit_lines + 1; + } + + Uint8 alpha = (Uint8)(opacity * 255 / 100); + Uint8 color = (Uint8)(level * 255 / 100); + int height = (int)(frame_dst->h + 0.5f); + + SDL_SetRenderDrawBlendMode(s_renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(s_renderer, color, color, color, alpha); + for (int y = 0; y < height; y += period) { + int y2 = y + offset; + for (int yy = 0; yy < shaded_lines && y2 + yy < height; yy++) { + SDL_FRect line = { + frame_dst->x, + frame_dst->y + (float)(y2 + yy), + frame_dst->w, + 1.0f + }; + SDL_RenderFillRect(s_renderer, &line); + } + } + SDL_SetRenderDrawBlendMode(s_renderer, SDL_BLENDMODE_NONE); +} + +static bool ensure_status_pixels(int width) +{ + const int height = statusbar_source_height(); + if (width <= 0 || height <= 0) { + return false; + } + if (s_status_width == width && s_status_height == height) { + return true; + } + + if (s_status_texture) { + SDL_DestroyTexture(s_status_texture); + s_status_texture = NULL; + } +#ifdef WINUAE_UNIX_WITH_OPENGL_SHADER_PIPELINE + if (s_gl_status_texture) { + glDeleteTextures(1, &s_gl_status_texture); + s_gl_status_texture = 0; + } +#endif + s_status_width = width; + s_status_height = height; + s_status_pixels.resize(size_t(width) * size_t(height)); + return true; +} + +static bool ensure_status_texture(int width) +{ + if (!s_renderer || !ensure_status_pixels(width)) { + return false; + } + if (s_status_texture) { + return true; + } + + const int height = statusbar_source_height(); + s_status_texture = SDL_CreateTexture(s_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); + if (!s_status_texture) { + write_log(_T("SDL3: failed to create %dx%d status texture: %s\n"), width, height, SDL_GetError()); + return false; + } + SDL_SetTextureBlendMode(s_status_texture, SDL_BLENDMODE_NONE); + return true; +} + +static bool update_status_pixels(int width) +{ + if (!ensure_status_pixels(width)) { + return false; + } + + init_status_colors(); + std::fill(s_status_pixels.begin(), s_status_pixels.end(), 0xffd4d0c8u); + statusline_set_multiplier(0, width, statusbar_source_height()); + for (int y = 0; y < statusbar_source_height(); y++) { + draw_status_line_single( + 0, + reinterpret_cast(s_status_pixels.data() + size_t(y) * size_t(width)), + y, + width, + s_status_rc, + s_status_gc, + s_status_bc, + NULL); + } + return true; +} + +static bool update_status_texture(int width) +{ + if (!ensure_status_texture(width) || !update_status_pixels(width)) { + return false; + } + + SDL_UpdateTexture(s_status_texture, NULL, s_status_pixels.data(), width * int(sizeof(uae_u32))); + return true; +} + +#ifdef WINUAE_UNIX_WITH_OPENGL_SHADER_PIPELINE +static bool unix_gl_load_functions(void) +{ + if (s_gl_functions_loaded) { + return true; + } + + p_glCreateShader = reinterpret_cast(unix_gl_get_proc("glCreateShader")); + p_glShaderSource = reinterpret_cast(unix_gl_get_proc("glShaderSource")); + p_glCompileShader = reinterpret_cast(unix_gl_get_proc("glCompileShader")); + p_glGetShaderiv = reinterpret_cast(unix_gl_get_proc("glGetShaderiv")); + p_glGetShaderInfoLog = reinterpret_cast(unix_gl_get_proc("glGetShaderInfoLog")); + p_glCreateProgram = reinterpret_cast(unix_gl_get_proc("glCreateProgram")); + p_glAttachShader = reinterpret_cast(unix_gl_get_proc("glAttachShader")); + p_glLinkProgram = reinterpret_cast(unix_gl_get_proc("glLinkProgram")); + p_glGetProgramiv = reinterpret_cast(unix_gl_get_proc("glGetProgramiv")); + p_glGetProgramInfoLog = reinterpret_cast(unix_gl_get_proc("glGetProgramInfoLog")); + p_glDeleteShader = reinterpret_cast(unix_gl_get_proc("glDeleteShader")); + p_glDeleteProgram = reinterpret_cast(unix_gl_get_proc("glDeleteProgram")); + p_glUseProgram = reinterpret_cast(unix_gl_get_proc("glUseProgram")); + p_glGetUniformLocation = reinterpret_cast(unix_gl_get_proc("glGetUniformLocation")); + p_glUniform1i = reinterpret_cast(unix_gl_get_proc("glUniform1i")); + p_glUniform1f = reinterpret_cast(unix_gl_get_proc("glUniform1f")); + p_glUniform2f = reinterpret_cast(unix_gl_get_proc("glUniform2f")); + p_glUniform4f = reinterpret_cast(unix_gl_get_proc("glUniform4f")); + + s_gl_functions_loaded = p_glCreateShader && p_glShaderSource && p_glCompileShader && + p_glGetShaderiv && p_glGetShaderInfoLog && p_glCreateProgram && p_glAttachShader && + p_glLinkProgram && p_glGetProgramiv && p_glGetProgramInfoLog && p_glDeleteShader && + p_glDeleteProgram && p_glUseProgram && p_glGetUniformLocation && p_glUniform1i && + p_glUniform1f && p_glUniform2f && p_glUniform4f; + return s_gl_functions_loaded; +} + +static bool unix_gl_shader_ok(GLuint shader, const char *label) +{ + GLint ok = 0; + p_glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + if (ok) { + return true; + } + + GLint length = 0; + p_glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); + std::vector log(length > 1 ? length : 1, 0); + if (length > 1) { + p_glGetShaderInfoLog(shader, length, NULL, log.data()); + } + write_log(_T("OpenGL shader pipeline: %s compile failed: %s\n"), label, log.data()); + return false; +} + +static bool unix_gl_program_ok(GLuint program) +{ + GLint ok = 0; + p_glGetProgramiv(program, GL_LINK_STATUS, &ok); + if (ok) { + return true; + } + + GLint length = 0; + p_glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length); + std::vector log(length > 1 ? length : 1, 0); + if (length > 1) { + p_glGetProgramInfoLog(program, length, NULL, log.data()); + } + write_log(_T("OpenGL shader pipeline: program link failed: %s\n"), log.data()); + return false; +} + +static GLuint unix_gl_compile_shader(GLenum type, const char *source, const char *label) +{ + GLuint shader = p_glCreateShader(type); + if (!shader) { + return 0; + } + const GLchar *sources[] = { reinterpret_cast(source) }; + p_glShaderSource(shader, 1, sources, NULL); + p_glCompileShader(shader); + if (!unix_gl_shader_ok(shader, label)) { + p_glDeleteShader(shader); + return 0; + } + return shader; +} + +static bool unix_gl_build_program(void) +{ + if (s_gl_program) { + return true; + } + if (!unix_gl_load_functions()) { + return false; + } + + static const char *vertex_shader = + "#version 120\n" + "varying vec2 v_tex;\n" + "void main(void) {\n" + " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n" + " v_tex = gl_MultiTexCoord0.xy;\n" + "}\n"; + static const char *fragment_shader = + "#version 120\n" + "uniform sampler2D u_tex;\n" + "uniform vec2 u_source_size;\n" + "uniform vec4 u_adjust;\n" + "uniform vec4 u_scanline;\n" + "uniform vec4 u_blur_noise;\n" + "uniform float u_frame;\n" + "varying vec2 v_tex;\n" + "float rand(vec2 co) {\n" + " return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);\n" + "}\n" + "void main(void) {\n" + " vec2 texel = 1.0 / max(u_source_size, vec2(1.0));\n" + " vec4 color = texture2D(u_tex, v_tex);\n" + " float blur = clamp(u_blur_noise.x, 0.0, 1.0);\n" + " if (blur > 0.001) {\n" + " vec4 sum = color * 4.0;\n" + " sum += texture2D(u_tex, v_tex + vec2(texel.x, 0.0));\n" + " sum += texture2D(u_tex, v_tex - vec2(texel.x, 0.0));\n" + " sum += texture2D(u_tex, v_tex + vec2(0.0, texel.y));\n" + " sum += texture2D(u_tex, v_tex - vec2(0.0, texel.y));\n" + " color = mix(color, sum / 8.0, blur);\n" + " }\n" + " float luma = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n" + " color.rgb = mix(vec3(luma), color.rgb, u_adjust.z);\n" + " color.rgb = (color.rgb - vec3(0.5)) * u_adjust.y + vec3(0.5 + u_adjust.x);\n" + " color.rgb = pow(max(color.rgb, vec3(0.0)), vec3(u_adjust.w));\n" + " if (u_scanline.x > 0.001 && u_scanline.z > 0.5) {\n" + " float y = mod(gl_FragCoord.y + u_scanline.w, u_scanline.z);\n" + " if (y < u_scanline.y) {\n" + " color.rgb *= 1.0 - u_scanline.x;\n" + " }\n" + " }\n" + " float noise = clamp(u_blur_noise.y, 0.0, 1.0);\n" + " if (noise > 0.001) {\n" + " color.rgb += (rand(v_tex * u_source_size + vec2(u_frame, u_frame * 0.37)) - 0.5) * noise;\n" + " }\n" + " gl_FragColor = clamp(color, 0.0, 1.0);\n" + "}\n"; + + GLuint vs = unix_gl_compile_shader(GL_VERTEX_SHADER, vertex_shader, "vertex"); + GLuint fs = unix_gl_compile_shader(GL_FRAGMENT_SHADER, fragment_shader, "fragment"); + if (!vs || !fs) { + if (vs) { + p_glDeleteShader(vs); + } + if (fs) { + p_glDeleteShader(fs); + } + return false; + } + + GLuint program = p_glCreateProgram(); + p_glAttachShader(program, vs); + p_glAttachShader(program, fs); + p_glLinkProgram(program); + p_glDeleteShader(vs); + p_glDeleteShader(fs); + if (!unix_gl_program_ok(program)) { + p_glDeleteProgram(program); + return false; + } + + s_gl_program = program; + s_gl_uniform_texture = p_glGetUniformLocation(program, reinterpret_cast("u_tex")); + s_gl_uniform_source_size = p_glGetUniformLocation(program, reinterpret_cast("u_source_size")); + s_gl_uniform_adjust = p_glGetUniformLocation(program, reinterpret_cast("u_adjust")); + s_gl_uniform_scanline = p_glGetUniformLocation(program, reinterpret_cast("u_scanline")); + s_gl_uniform_blur_noise = p_glGetUniformLocation(program, reinterpret_cast("u_blur_noise")); + s_gl_uniform_frame = p_glGetUniformLocation(program, reinterpret_cast("u_frame")); + return true; +} + +static bool unix_gl_ensure_context(int width, int height) +{ + if (s_gl_active) { + return true; + } + if (!s_window || s_gl_failed) { + return false; + } + + s_gl_context = SDL_GL_CreateContext(s_window); + if (!s_gl_context) { + write_log(_T("OpenGL shader pipeline: context creation failed: %s\n"), SDL_GetError()); + s_gl_failed = true; + return false; + } + SDL_GL_MakeCurrent(s_window, s_gl_context); + SDL_GL_SetSwapInterval(1); + if (!unix_gl_build_program()) { + SDL_GL_DestroyContext(s_gl_context); + s_gl_context = NULL; + s_gl_failed = true; + return false; + } + + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glEnable(GL_TEXTURE_2D); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glViewport(0, 0, width, height); + s_gl_active = true; + write_log(_T("OpenGL shader pipeline enabled\n")); + return true; +} + +static void unix_gl_destroy_status_texture(void) +{ + if (s_gl_status_texture) { + glDeleteTextures(1, &s_gl_status_texture); + s_gl_status_texture = 0; + } +} + +static bool unix_gl_ensure_textures(int width, int height, int pixbytes, int backbuffers) +{ + if (!s_gl_active || width <= 0 || height <= 0) { + return false; + } + backbuffers = clamp_backbuffer_count(backbuffers); + if (!s_gl_textures.empty() && s_texture_width == width && s_texture_height == height && + s_texture_pixbytes == pixbytes && s_texture_backbuffers == backbuffers) { + return true; + } + + destroy_frame_textures(); + s_gl_textures.resize(backbuffers); + glGenTextures(backbuffers, s_gl_textures.data()); + for (GLuint texture : s_gl_textures) { + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if (pixbytes == 2) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, + GL_BGRA, GL_UNSIGNED_BYTE, NULL); + } + } + s_gl_texture = s_gl_textures[0]; + s_texture_width = width; + s_texture_height = height; + s_texture_pixbytes = pixbytes; + s_texture_backbuffers = backbuffers; + s_texture_index = 0; + return true; +} + +static bool unix_gl_ensure_status_texture(int width) +{ + if (!s_gl_active || !update_status_pixels(width)) { + return false; + } + if (!s_gl_status_texture) { + glGenTextures(1, &s_gl_status_texture); + glBindTexture(GL_TEXTURE_2D, s_gl_status_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, statusbar_source_height(), 0, + GL_BGRA, GL_UNSIGNED_BYTE, s_status_pixels.data()); + } else { + glBindTexture(GL_TEXTURE_2D, s_gl_status_texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, statusbar_source_height(), + GL_BGRA, GL_UNSIGNED_BYTE, s_status_pixels.data()); + } + return true; +} + +static void unix_gl_draw_texture(GLuint texture, const SDL_FRect &dst, bool shader, + const struct gfx_filterdata *filter, int source_width, int source_height) +{ + glBindTexture(GL_TEXTURE_2D, texture); + if (shader) { + p_glUseProgram(s_gl_program); + p_glUniform1i(s_gl_uniform_texture, 0); + p_glUniform2f(s_gl_uniform_source_size, (GLfloat)source_width, (GLfloat)source_height); + + float luminance = filter ? filter->gfx_filter_luminance / 1000.0f : 0.0f; + float contrast = filter ? 1.0f + filter->gfx_filter_contrast / 1000.0f : 1.0f; + float saturation = filter ? 1.0f + filter->gfx_filter_saturation / 1000.0f : 1.0f; + float gamma = filter ? 1.0f + filter->gfx_filter_gamma / 1000.0f : 1.0f; + if (contrast < 0.0f) { + contrast = 0.0f; + } + if (saturation < 0.0f) { + saturation = 0.0f; + } + if (gamma <= 0.01f) { + gamma = 1.0f; + } + p_glUniform4f(s_gl_uniform_adjust, luminance, contrast, saturation, 1.0f / gamma); + + int lit_lines = filter ? (filter->gfx_filter_scanlineratio & 15) : 0; + int shaded_lines = filter ? ((filter->gfx_filter_scanlineratio >> 4) & 15) : 0; + int period = lit_lines + shaded_lines; + float opacity = filter ? clamp_filter_percent(filter->gfx_filter_scanlines) / 100.0f : 0.0f; + if (period <= 0 || shaded_lines <= 0) { + opacity = 0.0f; + period = 0; + shaded_lines = 0; + } + float scan_offset = filter ? (float)filter->gfx_filter_scanlineoffset : 0.0f; + p_glUniform4f(s_gl_uniform_scanline, opacity, (GLfloat)shaded_lines, + (GLfloat)period, scan_offset); + float blur = filter ? std::min(1.0f, std::max(0.0f, filter->gfx_filter_blur / 1000.0f)) : 0.0f; + float noise = filter ? std::min(1.0f, std::max(0.0f, filter->gfx_filter_noise / 1000.0f)) : 0.0f; + p_glUniform4f(s_gl_uniform_blur_noise, blur, noise, 0.0f, 0.0f); + p_glUniform1f(s_gl_uniform_frame, (GLfloat)s_gl_frame_counter); + } else { + p_glUseProgram(0); + } + + glBegin(GL_QUADS); + glTexCoord2f(0.0f, 0.0f); + glVertex2f(dst.x, dst.y); + glTexCoord2f(1.0f, 0.0f); + glVertex2f(dst.x + dst.w, dst.y); + glTexCoord2f(1.0f, 1.0f); + glVertex2f(dst.x + dst.w, dst.y + dst.h); + glTexCoord2f(0.0f, 1.0f); + glVertex2f(dst.x, dst.y + dst.h); + glEnd(); + if (shader) { + p_glUseProgram(0); + } +} + +static bool unix_gl_upload_frame(const struct unix_video_frame *frame) +{ + if (!unix_gl_ensure_textures(frame->width, frame->height, frame->pixbytes, frame->backbuffers)) { + return false; + } + if (!s_gl_textures.empty()) { + s_texture_index = (s_texture_index + 1) % (int)s_gl_textures.size(); + s_gl_texture = s_gl_textures[s_texture_index]; + } + + glBindTexture(GL_TEXTURE_2D, s_gl_texture); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + if (frame->rowbytes == frame->width * frame->pixbytes) { + if (frame->pixbytes == 2) { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->width, frame->height, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame->pixels); + } else { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->width, frame->height, + GL_BGRA, GL_UNSIGNED_BYTE, frame->pixels); + } + } else { + std::vector packed((size_t)frame->width * (size_t)frame->height * (size_t)frame->pixbytes); + for (int y = 0; y < frame->height; y++) { + memcpy(packed.data() + (size_t)y * (size_t)frame->width * (size_t)frame->pixbytes, + frame->pixels + (size_t)y * (size_t)frame->rowbytes, + (size_t)frame->width * (size_t)frame->pixbytes); + } + if (frame->pixbytes == 2) { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->width, frame->height, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, packed.data()); + } else { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->width, frame->height, + GL_BGRA, GL_UNSIGNED_BYTE, packed.data()); + } + } + return true; +} + +static void unix_gl_present(const struct unix_video_frame *frame, const struct gfx_filterdata *filter) +{ + if (!unix_gl_upload_frame(frame)) { + return; + } + int output_width = 0; + int output_height = 0; + struct unix_video_layout layout; + + if (!get_window_pixel_size(&output_width, &output_height) || + !make_video_layout(frame, filter, output_width, output_height, &layout)) { + return; + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + filter && filter->gfx_filter_bilinear ? GL_LINEAR : GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + filter && filter->gfx_filter_bilinear ? GL_LINEAR : GL_NEAREST); + + glViewport(0, 0, output_width, output_height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, output_width, output_height, 0.0, -1.0, 1.0); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glEnable(GL_SCISSOR_TEST); + glScissor(layout.frame_clip.x, + output_height - layout.frame_clip.y - layout.frame_clip.h, + layout.frame_clip.w, layout.frame_clip.h); + unix_gl_draw_texture(s_gl_texture, layout.frame_dst, true, filter, frame->width, frame->height); + glDisable(GL_SCISSOR_TEST); + + if (unix_gl_ensure_status_texture(frame->width)) { + unix_gl_draw_texture(s_gl_status_texture, layout.status_dst, true, NULL, + frame->width, statusbar_source_height()); + } + + SDL_GL_SwapWindow(s_window); + s_gl_frame_counter++; +} +#endif + +static int unix_mouse_button_from_sdl(Uint8 button) +{ + switch (button) { + case SDL_BUTTON_LEFT: + return 0; + case SDL_BUTTON_RIGHT: + return 1; + case SDL_BUTTON_MIDDLE: + return 2; + default: + return -1; + } +} + +static SDL_MouseButtonFlags unix_mouse_button_mask(Uint8 button) +{ + if (button == 0 || button > 32) { + return 0; + } + return SDL_BUTTON_MASK(button); +} + +static bool unix_event_for_window(SDL_WindowID window_id) +{ + return s_window && window_id != 0 && window_id == SDL_GetWindowID(s_window); +} + +static bool unix_mouse_point_in_client(float x, float y) +{ + int width = 0; + int height = 0; + + if (!s_window) { + return false; + } + SDL_GetWindowSize(s_window, &width, &height); + return width > 0 && height > 0 && + x >= 0.0f && y >= 0.0f && x < (float)width && y < (float)height; +} + +static bool unix_mouse_event_in_client(SDL_WindowID window_id, float x, float y) +{ + return unix_event_for_window(window_id) && unix_mouse_point_in_client(x, y); +} + +static bool statusbar_logical_position(int window_x, int window_y, int *logical_x, int *logical_y) +{ + if (!s_window || s_texture_width <= 0 || s_texture_height <= 0) { + return false; + } + + int window_width = 0; + int window_height = 0; + SDL_GetWindowSize(s_window, &window_width, &window_height); + if (window_width <= 0 || window_height <= 0) { + return false; + } + + const int logical_width = s_texture_width; + const int logical_height = s_texture_height + statusbar_display_height(); + const int lx = window_x * logical_width / window_width; + const int ly = window_y * logical_height / window_height; + if (ly < s_texture_height || ly >= logical_height) { + return false; + } + if (logical_x) { + *logical_x = lx; + } + if (logical_y) { + *logical_y = ly; + } + return true; +} + +static int statusbar_hit_slot(int logical_x) +{ + if (s_status_width <= 0) { + return -1; + } + + int mult = statusline_get_multiplier(0) / 100; + if (mult < 1) { + mult = 1; + } + const int x_start = (td_numbers_pos & TD_RIGHT) + ? s_status_width - (td_numbers_padx + VISIBLE_LEDS * td_width) * mult + : td_numbers_padx * mult; + const int slot_width = td_width * mult; + if (slot_width <= 0 || logical_x < x_start) { + return -1; + } + const int slot = (logical_x - x_start) / slot_width; + return slot >= 0 && slot < VISIBLE_LEDS ? slot : -1; +} + +static bool handle_statusbar_click(int window_x, int window_y, Uint8 button) +{ + int logical_x = 0; + if (!statusbar_logical_position(window_x, window_y, &logical_x, NULL)) { + return false; + } + + const int slot = statusbar_hit_slot(logical_x); + if (slot < 0) { + return true; + } + + const bool right_click = button == SDL_BUTTON_RIGHT; + if (slot >= 8 && slot <= 11) { + const int drive = slot - 8; + if (right_click) { + disk_eject(drive); + } else if (changed_prefs.floppyslots[drive].dfxtype >= 0) { + gui_display(drive); + } + return true; + } + if (slot == 6) { + if (right_click) { + changed_prefs.cdslots[0].name[0] = 0; + changed_prefs.cdslots[0].inuse = false; + set_config_changed(); + } else { + gui_display(6); + } + return true; + } + if (slot == 3) { + if (right_click) { + uae_reset(0, 1); + } else { + gui_display(-1); + } + return true; + } + if (slot == 2 && !right_click && pause_emulation) { + pausemode(0); + return true; + } + + return true; +} + +bool unix_video_setup(void) +{ + if (s_setup_done) { + return s_available; + } + + SDL_SetMainReady(); + if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { + write_log(_T("SDL3: video unavailable: %s\n"), SDL_GetError()); + s_setup_done = true; + s_available = false; + return false; + } + + s_event_thread = uae_thread_get_id(); + s_event_thread_valid = true; + s_wrong_event_thread_logged = false; + s_queued_present_logged = false; + s_setup_done = true; + s_available = true; + return true; +} + +static bool apply_window_mode(void) +{ + if (!s_window || !s_setup_done || !s_available) { + return false; + } + + if (s_requested_window_mode == s_active_window_mode && + s_requested_display_index == s_active_display_index && + s_requested_fullscreen_width == s_active_fullscreen_width && + s_requested_fullscreen_height == s_active_fullscreen_height && + s_requested_fullscreen_refresh == s_active_fullscreen_refresh) { + return true; + } + + SDL_DisplayID display = get_sdl_display_for_index(s_requested_display_index); + center_window_on_display(display); + + if (s_requested_window_mode == UNIX_VIDEO_WINDOWED) { + if (!SDL_SetWindowFullscreen(s_window, false)) { + write_log(_T("SDL3: failed to leave fullscreen: %s\n"), SDL_GetError()); + return false; + } + s_active_window_mode = UNIX_VIDEO_WINDOWED; + s_active_display_index = s_requested_display_index; + s_active_fullscreen_width = s_requested_fullscreen_width; + s_active_fullscreen_height = s_requested_fullscreen_height; + s_active_fullscreen_refresh = s_requested_fullscreen_refresh; + return true; + } + + SDL_DisplayMode fullscreen_mode; + const SDL_DisplayMode *mode = NULL; + if (s_requested_window_mode == UNIX_VIDEO_FULLSCREEN) { + if (s_requested_fullscreen_width > 0 && s_requested_fullscreen_height > 0) { + float refresh = s_requested_fullscreen_refresh > 0 ? (float)s_requested_fullscreen_refresh : 0.0f; + if (SDL_GetClosestFullscreenDisplayMode(display, s_requested_fullscreen_width, + s_requested_fullscreen_height, refresh, true, &fullscreen_mode)) { + mode = &fullscreen_mode; + } else { + write_log(_T("SDL3: no exclusive fullscreen mode %dx%d@%d on display %d, using desktop fullscreen\n"), + s_requested_fullscreen_width, s_requested_fullscreen_height, + s_requested_fullscreen_refresh, s_requested_display_index); + } + } else { + const SDL_DisplayMode *desktop = SDL_GetDesktopDisplayMode(display); + if (desktop) { + fullscreen_mode = *desktop; + mode = &fullscreen_mode; + } + } + } + + if (!SDL_SetWindowFullscreenMode(s_window, mode)) { + write_log(_T("SDL3: failed to set fullscreen mode: %s\n"), SDL_GetError()); + return false; + } + if (!SDL_SetWindowFullscreen(s_window, true)) { + write_log(_T("SDL3: failed to enter fullscreen: %s\n"), SDL_GetError()); + return false; + } + + s_active_window_mode = s_requested_window_mode; + s_active_display_index = s_requested_display_index; + s_active_fullscreen_width = s_requested_fullscreen_width; + s_active_fullscreen_height = s_requested_fullscreen_height; + s_active_fullscreen_refresh = s_requested_fullscreen_refresh; + return true; +} + +bool unix_video_init(int width, int height, int pixbytes) +{ + if (!unix_video_setup()) { + return false; + } + + width = width > 0 ? width : 768; + height = height > 0 ? height : 576; + pixbytes = pixbytes == 2 ? 2 : 4; + + if (!s_window) { + int window_width = clamp_window_dimension(width, 768, 960); + int window_height = clamp_window_dimension(height + statusbar_display_height(), 576 + statusbar_display_height(), 720 + statusbar_display_height()); + SDL_WindowFlags base_window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY; + SDL_WindowFlags window_flags = base_window_flags; +#ifdef WINUAE_UNIX_WITH_OPENGL_SHADER_PIPELINE + bool tried_gl_window = false; + if (!s_gl_failed) { + window_flags = (SDL_WindowFlags)(window_flags | SDL_WINDOW_OPENGL); + tried_gl_window = true; + } +#endif + s_window = SDL_CreateWindow(WINUAE_UNIX_WINDOW_TITLE, window_width, window_height, + window_flags); +#ifdef WINUAE_UNIX_WITH_OPENGL_SHADER_PIPELINE + if (!s_window && tried_gl_window) { + write_log(_T("OpenGL shader pipeline: window creation failed: %s\n"), SDL_GetError()); + s_gl_failed = true; + s_window = SDL_CreateWindow(WINUAE_UNIX_WINDOW_TITLE, window_width, window_height, + base_window_flags); + } +#endif + if (!s_window) { + write_log(_T("SDL3: failed to create window: %s\n"), SDL_GetError()); + s_available = false; + return false; + } + center_window_on_display(get_sdl_display_for_index(s_requested_display_index)); + } + +#ifdef WINUAE_UNIX_WITH_OPENGL_SHADER_PIPELINE + if (!s_gl_failed) { + int window_width = 0; + int window_height = 0; + SDL_GetWindowSizeInPixels(s_window, &window_width, &window_height); + if (unix_gl_ensure_context(window_width, window_height)) { + apply_window_mode(); + return unix_gl_ensure_textures(width, height, pixbytes, 1); + } + if (!s_renderer && s_window) { + SDL_DestroyWindow(s_window); + s_window = NULL; + return unix_video_init(width, height, pixbytes); + } + } + if (s_gl_active) { + apply_window_mode(); + return unix_gl_ensure_textures(width, height, pixbytes, 1); + } +#endif + + if (!s_renderer) { + s_renderer = SDL_CreateRenderer(s_window, NULL); + if (!s_renderer) { + s_renderer = SDL_CreateRenderer(s_window, "software"); + } + if (!s_renderer) { + write_log(_T("SDL3: failed to create renderer: %s\n"), SDL_GetError()); + s_available = false; + return false; + } + SDL_SetRenderVSync(s_renderer, 1); + SDL_SetRenderDrawColor(s_renderer, 0, 0, 0, 255); + SDL_RenderClear(s_renderer); + SDL_RenderPresent(s_renderer); + } + + apply_window_mode(); + + return ensure_texture(width, height, pixbytes, 1); +} + +void unix_video_shutdown(void) +{ + unix_input_release_keys(); + unix_video_set_mouse_grab(false); + + destroy_frame_textures(); + if (s_status_texture) { + SDL_DestroyTexture(s_status_texture); + s_status_texture = NULL; + } +#ifdef WINUAE_UNIX_WITH_OPENGL_SHADER_PIPELINE + unix_gl_destroy_status_texture(); + if (s_gl_program) { + p_glDeleteProgram(s_gl_program); + s_gl_program = 0; + } + if (s_gl_context) { + SDL_GL_DestroyContext(s_gl_context); + s_gl_context = NULL; + } + s_gl_active = false; +#endif + s_status_pixels.clear(); + if (s_renderer) { + SDL_DestroyRenderer(s_renderer); + s_renderer = NULL; + } + if (s_window) { + SDL_DestroyWindow(s_window); + s_window = NULL; + } + s_status_width = 0; + s_status_height = 0; + s_auto_window_width = 0; + s_auto_window_height = 0; + s_event_thread_valid = false; + s_wrong_event_thread_logged = false; + s_queued_present_logged = false; + s_suppressed_mouse_buttons = 0; + { + std::lock_guard lock(s_pending_frame_mutex); + s_pending_frame = unix_pending_video_frame(); + } + + if (s_setup_done && s_available) { + SDL_QuitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO); + } + s_setup_done = false; + s_available = false; +} + +static int unix_video_poll_internal(bool *quit_requested, bool input_events) +{ + SDL_Event event; + int got = 0; + + if (quit_requested) { + *quit_requested = false; + } + if (!s_setup_done || !s_available) { + return 0; + } + if (s_event_thread_valid.load() && !pthread_equal(uae_thread_get_id(), s_event_thread)) { + if (!s_wrong_event_thread_logged.exchange(true)) { + write_log(_T("SDL3: ignoring event poll from non-video thread\n")); + } + return 0; + } + + unix_pending_video_frame pending; + if (pop_queued_video_frame(&pending)) { + struct unix_video_frame frame; + frame.pixels = pending.pixels.data(); + frame.width = pending.width; + frame.height = pending.height; + frame.rowbytes = pending.rowbytes; + frame.pixbytes = pending.pixbytes; + frame.filter_index = pending.filter_index; + frame.monitor_id = pending.monitor_id; + frame.backbuffers = pending.backbuffers; + unix_video_present_on_event_thread(&frame); + got = 1; + } + + while (SDL_PollEvent(&event)) { + got = 1; + switch (event.type) { + case SDL_EVENT_QUIT: + if (quit_requested) { + *quit_requested = true; + } + break; + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + if (quit_requested) { + *quit_requested = true; + } + break; + case SDL_EVENT_WINDOW_MOVED: + if (s_mouse_grabbed && unix_event_for_window(event.window.windowID)) { + unix_input_release_keys(); + unix_video_set_mouse_grab(false); + s_suppressed_mouse_buttons = 0; + } + break; + case SDL_EVENT_WINDOW_FOCUS_LOST: + unix_input_release_keys(); + unix_video_set_mouse_grab(false); + s_suppressed_mouse_buttons = 0; + break; + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + if (!input_events) { + break; + } + if (event.key.repeat) { + break; + } + if (event.key.key == SDLK_Q && (event.key.mod & (SDL_KMOD_CTRL | SDL_KMOD_GUI))) { + if (quit_requested) { + *quit_requested = true; + } + break; + } + if (event.key.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_G && + (event.key.mod & (SDL_KMOD_CTRL | SDL_KMOD_GUI))) { + unix_video_set_mouse_grab(false); + break; + } + if (event.key.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE && s_mouse_grabbed) { + unix_video_set_mouse_grab(false); + } + unix_input_keyboard_key((int)event.key.scancode, event.key.type == SDL_EVENT_KEY_DOWN, + unix_input_lock_state_from_sdl(event.key.mod)); + break; + case SDL_EVENT_MOUSE_MOTION: + if (!input_events) { + break; + } + if (s_suppressed_mouse_buttons) { + s_suppressed_mouse_buttons &= event.motion.state; + break; + } + if (s_mouse_grabbed) { + unix_input_mouse_motion((int)event.motion.xrel, (int)event.motion.yrel); + } + break; + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + { + if (!input_events) { + break; + } + SDL_MouseButtonFlags button_mask = unix_mouse_button_mask(event.button.button); + if (button_mask && (s_suppressed_mouse_buttons & button_mask)) { + if (event.type == SDL_EVENT_MOUSE_BUTTON_UP) { + s_suppressed_mouse_buttons &= ~button_mask; + } + break; + } + if (!s_mouse_grabbed && + !unix_mouse_event_in_client(event.button.windowID, event.button.x, event.button.y)) { + if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && button_mask) { + s_suppressed_mouse_buttons |= button_mask; + } + break; + } + int button = unix_mouse_button_from_sdl(event.button.button); + if (button >= 0) { + if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && handle_statusbar_click((int)event.button.x, (int)event.button.y, event.button.button)) { + s_status_click_button = event.button.button; + break; + } + if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && s_status_click_button == event.button.button) { + s_status_click_button = 0; + break; + } + if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && !s_mouse_grabbed) { + unix_video_set_mouse_grab(true); + } + unix_input_mouse_button(button, event.type == SDL_EVENT_MOUSE_BUTTON_DOWN); + } + break; + } + case SDL_EVENT_MOUSE_WHEEL: + if (!input_events) { + break; + } + if (!s_mouse_grabbed && + !unix_mouse_event_in_client(event.wheel.windowID, event.wheel.mouse_x, event.wheel.mouse_y)) { + break; + } + if (s_mouse_grabbed) { + unix_input_mouse_wheel(event.wheel.integer_x, event.wheel.integer_y); + } + break; + case SDL_EVENT_JOYSTICK_ADDED: + case SDL_EVENT_JOYSTICK_REMOVED: + case SDL_EVENT_GAMEPAD_ADDED: + case SDL_EVENT_GAMEPAD_REMOVED: + case SDL_EVENT_GAMEPAD_REMAPPED: + unix_input_joystick_device_changed(); + break; + case SDL_EVENT_CLIPBOARD_UPDATE: + clipboard_host_changed(); + break; + default: + break; + } + } + + return got; +} + +int unix_video_poll(bool *quit_requested) +{ + return unix_video_poll_internal(quit_requested, true); +} + +int unix_video_poll_window_events(bool *quit_requested) +{ + return unix_video_poll_internal(quit_requested, false); +} + +static void unix_video_present_on_event_thread(const struct unix_video_frame *frame) +{ + if (!frame || !frame->pixels || frame->width <= 0 || frame->height <= 0 || frame->rowbytes <= 0) { + return; + } + if (!unix_video_init(frame->width, frame->height, frame->pixbytes)) { + return; + } + const struct gfx_filterdata *filter = filterdata_for_frame(frame); + auto_resize_window_for_rtg(frame, filter); +#ifdef WINUAE_UNIX_WITH_OPENGL_SHADER_PIPELINE + if (s_gl_active) { + unix_gl_present(frame, filter); + return; + } +#endif + if (!ensure_texture(frame->width, frame->height, frame->pixbytes, frame->backbuffers)) { + return; + } + if (!s_textures.empty()) { + s_texture_index = (s_texture_index + 1) % (int)s_textures.size(); + s_texture = s_textures[s_texture_index]; + } + + SDL_SetTextureScaleMode(s_texture, + filter && filter->gfx_filter_bilinear ? SDL_SCALEMODE_LINEAR : SDL_SCALEMODE_NEAREST); + + SDL_UpdateTexture(s_texture, NULL, frame->pixels, frame->rowbytes); + SDL_SetRenderLogicalPresentation(s_renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED); + int output_width = 0; + int output_height = 0; + struct unix_video_layout layout; + if (!get_renderer_output_size(&output_width, &output_height) || + !make_video_layout(frame, filter, output_width, output_height, &layout)) { + return; + } + SDL_SetRenderDrawColor(s_renderer, 0, 0, 0, 255); + SDL_RenderClear(s_renderer); + + SDL_SetRenderClipRect(s_renderer, &layout.frame_clip); + SDL_RenderTexture(s_renderer, s_texture, NULL, &layout.frame_dst); + render_scanline_overlay(filter, &layout.frame_dst); + SDL_SetRenderClipRect(s_renderer, NULL); + + if (update_status_texture(frame->width)) { + SDL_RenderTexture(s_renderer, s_status_texture, NULL, &layout.status_dst); + } + SDL_RenderPresent(s_renderer); +} + +void unix_video_present(const struct unix_video_frame *frame) +{ + if (!unix_video_on_event_thread()) { + queue_video_frame_for_event_thread(frame); + return; + } + unix_video_present_on_event_thread(frame); +} + +void unix_video_set_title(const TCHAR *title) +{ + if (s_window && title) { + SDL_SetWindowTitle(s_window, title); + } +} + +bool unix_video_set_window_mode(enum unix_video_window_mode mode, int display_index, int width, int height, int refresh_rate) +{ + s_requested_window_mode = mode; + s_requested_display_index = display_index; + s_requested_fullscreen_width = width; + s_requested_fullscreen_height = height; + s_requested_fullscreen_refresh = refresh_rate; + + if (!s_window) { + return true; + } + return apply_window_mode(); +} + +enum unix_video_window_mode unix_video_get_window_mode(void) +{ + return s_active_window_mode; +} + +float unix_video_get_display_refresh_rate(int display_index) +{ + if (!unix_video_setup()) { + return 0.0f; + } + + SDL_DisplayID display = 0; + if (display_index < 0 && s_window) { + display = SDL_GetDisplayForWindow(s_window); + } + if (!display) { + display = get_sdl_display_for_index(display_index); + } + + const SDL_DisplayMode *mode = display ? SDL_GetCurrentDisplayMode(display) : NULL; + float refresh = refresh_rate_from_mode(mode); + if (refresh <= 0.0f && display) { + refresh = refresh_rate_from_mode(SDL_GetDesktopDisplayMode(display)); + } + return refresh; +} + +int unix_video_get_display_modes(int display_index, struct unix_video_display_mode *modes, int max_modes) +{ + if (!modes || max_modes <= 0 || !unix_video_setup()) { + return 0; + } + + int count = 0; + int display_count = 0; + SDL_DisplayID *displays = get_sdl_displays(&display_count); + if (displays && display_count > 0) { + if (display_index > 0 && display_index <= display_count) { + add_sdl_display_modes(displays[display_index - 1], modes, max_modes, &count); + } else { + for (int i = 0; i < display_count; i++) { + add_sdl_display_modes(displays[i], modes, max_modes, &count); + } + } + } + if (displays) { + SDL_free(displays); + } + if (!count) { + add_sdl_display_modes(SDL_GetPrimaryDisplay(), modes, max_modes, &count); + } + return count; +} + +void unix_video_set_mouse_grab(bool grab) +{ + s_mouse_grabbed = grab; + unix_input_set_mouse_active(grab); + if (grab) { + s_suppressed_mouse_buttons = 0; + } + + if (!s_setup_done || !s_available) { + return; + } + + if (s_window) { + SDL_SetWindowRelativeMouseMode(s_window, grab); + SDL_SetWindowMouseGrab(s_window, grab); + SDL_CaptureMouse(grab); + } +} + +bool unix_video_get_mouse_grab(void) +{ + return s_mouse_grabbed; +} + +void unix_video_toggle_mouse_grab(void) +{ + unix_video_set_mouse_grab(!unix_video_get_mouse_grab()); +} + +void unix_video_get_desktop(int *dw, int *dh, int *x, int *y, int *w, int *h) +{ + SDL_Rect usable; + SDL_DisplayID display = SDL_GetPrimaryDisplay(); + + if (x) { + *x = 0; + } + if (y) { + *y = 0; + } + const SDL_DisplayMode *mode = display ? SDL_GetCurrentDisplayMode(display) : NULL; + if (mode) { + if (dw) { + *dw = mode->w; + } + if (dh) { + *dh = mode->h; + } + } else { + if (dw) { + *dw = 640; + } + if (dh) { + *dh = 480; + } + } + + if (display && SDL_GetDisplayUsableBounds(display, &usable)) { + if (x) { + *x = usable.x; + } + if (y) { + *y = usable.y; + } + if (w) { + *w = usable.w; + } + if (h) { + *h = usable.h; + } + } else { + if (w) { + *w = dw ? *dw : 640; + } + if (h) { + *h = dh ? *dh : 480; + } + } +}