Emulator core geared towards emulating ZZT and Super ZZT.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

frontend.c 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. /**
  2. * Copyright (c) 2018, 2019 Adrian Siekierka
  3. *
  4. * This file is part of Zeta.
  5. *
  6. * Zeta is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Zeta is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Zeta. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "../config.h"
  20. #ifdef USE_GETOPT
  21. #define _POSIX_C_SOURCE 2
  22. #include <unistd.h>
  23. #endif
  24. #include <ctype.h>
  25. #include <stdatomic.h>
  26. #include <stdio.h>
  27. #include <stdlib.h>
  28. #include <string.h>
  29. #include <time.h>
  30. #include <SDL2/SDL.h>
  31. #include <SDL2/SDL_main.h>
  32. #include "../zzt.h"
  33. #include "../audio_stream.h"
  34. #include "../posix_vfs.h"
  35. #include "../render_software.h"
  36. #ifdef ENABLE_SCREENSHOTS
  37. #include "../screenshot_writer.h"
  38. #endif
  39. #include "frontend_sdl.h"
  40. static const u8 sdl_to_pc_scancode[] = {
  41. /* 0*/ 0,
  42. /* 1*/ 0, 0, 0,
  43. /* 4*/ 0x1E, 0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, 0x23, 0x17, /* A-I */
  44. /* 13*/ 0x24, 0x25, 0x26, 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, /* J-R */
  45. /* 22*/ 0x1F, 0x14, 0x16, 0x2F, 0x11, 0x2D, 0x15, 0x2C, /* S-Z */
  46. /* 30*/ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, /* 1-0 */
  47. /* 40*/ 0x1C, 0x01, 0x0E, 0x0F, 0x39,
  48. /* 45*/ 0x0C, 0x0D, 0x1A, 0x1B, 0x2B,
  49. /* 50*/ 0x2B, 0x27, 0x28, 0x29,
  50. /* 54*/ 0x33, 0x34, 0x35, 0x3A,
  51. 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x57, 0x58,
  52. 0x37, 0x46, 0, 0x52, 0x47, 0x49, 0x53, 0x4F, 0x51,
  53. 0x4D, 0x4B, 0x50, 0x48, 0x45
  54. };
  55. static const int sdl_to_pc_scancode_max = sizeof(sdl_to_pc_scancode) - 1;
  56. long zeta_time_ms(void) {
  57. return SDL_GetTicks();
  58. }
  59. void cpu_ext_log(const char *s) {
  60. fprintf(stderr, "%s\n", s);
  61. }
  62. int zeta_has_feature(int feature) {
  63. return 1;
  64. }
  65. static SDL_AudioDeviceID audio_device;
  66. static SDL_AudioSpec audio_spec;
  67. static SDL_mutex *audio_mutex;
  68. static void audio_callback(void *userdata, Uint8 *stream, int len) {
  69. SDL_LockMutex(audio_mutex);
  70. audio_stream_generate_u8(zeta_time_ms(), stream, len);
  71. SDL_UnlockMutex(audio_mutex);
  72. }
  73. void speaker_on(double freq) {
  74. SDL_LockMutex(audio_mutex);
  75. audio_stream_append_on(zeta_time_ms(), freq);
  76. SDL_UnlockMutex(audio_mutex);
  77. }
  78. void speaker_off(void) {
  79. SDL_LockMutex(audio_mutex);
  80. audio_stream_append_off(zeta_time_ms());
  81. SDL_UnlockMutex(audio_mutex);
  82. }
  83. static SDL_mutex *zzt_thread_lock;
  84. static SDL_cond *zzt_thread_cond;
  85. static u8 zzt_vram_copy[80*25*2];
  86. static u8 zzt_thread_running;
  87. static atomic_int zzt_renderer_waiting = 0;
  88. static u8 video_blink = 1;
  89. static long first_timer_tick;
  90. static double timer_time;
  91. static Uint32 sdl_timer_thread(Uint32 interval, void *param) {
  92. if (!zzt_thread_running) return 0;
  93. long curr_timer_tick = zeta_time_ms();
  94. atomic_fetch_add(&zzt_renderer_waiting, 1);
  95. SDL_LockMutex(zzt_thread_lock);
  96. atomic_fetch_sub(&zzt_renderer_waiting, 1);
  97. zzt_mark_timer();
  98. timer_time += SYS_TIMER_TIME;
  99. long duration = curr_timer_tick - first_timer_tick;
  100. long tick_time = ((long) (timer_time + SYS_TIMER_TIME)) - duration;
  101. while (tick_time <= 0) {
  102. zzt_mark_timer();
  103. timer_time += SYS_TIMER_TIME;
  104. tick_time = ((long) (timer_time + SYS_TIMER_TIME)) - duration;
  105. }
  106. SDL_CondBroadcast(zzt_thread_cond);
  107. SDL_UnlockMutex(zzt_thread_lock);
  108. return tick_time;
  109. }
  110. static void sdl_timer_init(void) {
  111. first_timer_tick = zeta_time_ms();
  112. timer_time = 0;
  113. SDL_AddTimer((int) SYS_TIMER_TIME, sdl_timer_thread, (void*)NULL);
  114. }
  115. static int sdl_is_blink_phase(long curr_time) {
  116. return ((curr_time % (BLINK_TOGGLE_DURATION_MS*2)) >= BLINK_TOGGLE_DURATION_MS);
  117. }
  118. // try to keep a budget of ~5ms per call
  119. static int zzt_thread_func(void *ptr) {
  120. int opcodes = 1000;
  121. while (zzt_thread_running) {
  122. if (SDL_LockMutex(zzt_thread_lock) == 0) {
  123. while (zzt_renderer_waiting > 0) {
  124. SDL_CondWait(zzt_thread_cond, zzt_thread_lock);
  125. }
  126. long duration = zeta_time_ms();
  127. int rcode = zzt_execute(opcodes);
  128. duration = zeta_time_ms() - duration;
  129. if (rcode == STATE_CONTINUE) {
  130. if (duration < 2) {
  131. opcodes = (opcodes * 20 / 19);
  132. } else if (duration > 4) {
  133. opcodes = (opcodes * 19 / 20);
  134. }
  135. }
  136. SDL_CondBroadcast(zzt_thread_cond);
  137. if (rcode == STATE_WAIT) {
  138. SDL_CondWaitTimeout(zzt_thread_cond, zzt_thread_lock, 20);
  139. } else if (rcode == STATE_END) {
  140. zzt_thread_running = 0;
  141. }
  142. SDL_UnlockMutex(zzt_thread_lock);
  143. }
  144. }
  145. return 0;
  146. }
  147. #define KEYMOD_ALT(keymod) ((keymod) & (KMOD_LALT | KMOD_RALT))
  148. #define KEYMOD_CTRL(keymod) ((keymod) & (KMOD_LCTRL | KMOD_RCTRL))
  149. #define KEYMOD_SHIFT(keymod) ((keymod) & (KMOD_LSHIFT | KMOD_RSHIFT))
  150. static void update_keymod(SDL_Keymod keymod) {
  151. if (KEYMOD_SHIFT(keymod)) zzt_kmod_set(0x01); else zzt_kmod_clear(0x01);
  152. if (KEYMOD_CTRL(keymod)) zzt_kmod_set(0x04); else zzt_kmod_clear(0x04);
  153. if (KEYMOD_ALT(keymod)) zzt_kmod_set(0x08); else zzt_kmod_clear(0x08);
  154. }
  155. static char as_shifted(char kcode) {
  156. if (kcode >= 'a' && kcode <= 'z') {
  157. return kcode - 32;
  158. } else switch(kcode) {
  159. case '1': return '!';
  160. case '2': return '@';
  161. case '3': return '#';
  162. case '4': return '$';
  163. case '5': return '%';
  164. case '6': return '^';
  165. case '7': return '&';
  166. case '8': return '*';
  167. case '9': return '(';
  168. case '0': return ')';
  169. case '-': return '_';
  170. case '=': return '+';
  171. case '[': return '{';
  172. case ']': return '}';
  173. case ';': return ':';
  174. case '\'': return '"';
  175. case '\\': return '|';
  176. case ',': return '<';
  177. case '.': return '>';
  178. case '/': return '?';
  179. case '`': return '~';
  180. default: return kcode;
  181. }
  182. }
  183. static SDL_Window *window;
  184. static int charw, charh;
  185. // used for marking render data updates
  186. static SDL_mutex *render_data_update_mutex;
  187. static int charset_update_requested = 0;
  188. static u8* charset_update_data = NULL;
  189. static int palette_update_requested = 0;
  190. static u32* palette_update_data = NULL;
  191. void calc_render_area(SDL_Rect *rect, int w, int h, int *scale_out, int flags) {
  192. int iw = 80*charw;
  193. int ih = 25*charh;
  194. int scale = 1;
  195. while (((scale+1)*iw <= w) && ((scale+1)*ih <= h)) scale++;
  196. if (scale_out != NULL) *scale_out = scale;
  197. w /= scale;
  198. h /= scale;
  199. if (flags & AREA_WITHOUT_SCALE) scale = 1;
  200. rect->x = ((w - iw) * scale) / 2;
  201. rect->y = ((h - ih) * scale) / 2;
  202. rect->w = iw * scale;
  203. rect->h = ih * scale;
  204. }
  205. void zeta_update_charset(int width, int height, u8* data) {
  206. SDL_LockMutex(render_data_update_mutex);
  207. charw = width;
  208. charh = height;
  209. charset_update_data = data;
  210. charset_update_requested = 1;
  211. SDL_UnlockMutex(render_data_update_mutex);
  212. }
  213. void zeta_update_palette(u32* data) {
  214. SDL_LockMutex(render_data_update_mutex);
  215. palette_update_data = data;
  216. palette_update_requested = 1;
  217. SDL_UnlockMutex(render_data_update_mutex);
  218. }
  219. #include "../asset_loader.h"
  220. #include "../frontend_posix.c"
  221. #ifdef USE_OPENGL
  222. extern sdl_renderer sdl_renderer_opengl;
  223. #endif
  224. extern sdl_renderer sdl_renderer_software;
  225. int main(int argc, char **argv) {
  226. int scancodes_lifted[sdl_to_pc_scancode_max + 1];
  227. int slc = 0;
  228. SDL_AudioSpec requested_audio_spec;
  229. SDL_Event event;
  230. int scode, kcode;
  231. SDL_Thread* zzt_thread;
  232. u8 windowed = 1;
  233. init_posix_vfs("");
  234. if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) {
  235. SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_Init failed! %s", SDL_GetError());
  236. return 1;
  237. }
  238. render_data_update_mutex = SDL_CreateMutex();
  239. zzt_thread_lock = SDL_CreateMutex();
  240. zzt_thread_cond = SDL_CreateCond();
  241. audio_mutex = SDL_CreateMutex();
  242. if (posix_zzt_init(argc, argv) < 0) {
  243. fprintf(stderr, "Could not load ZZT!\n");
  244. SDL_Quit();
  245. return 1;
  246. }
  247. sdl_renderer *renderer = NULL;
  248. #ifdef USE_OPENGL
  249. renderer = &sdl_renderer_opengl;
  250. if (renderer->init() < 0) {
  251. fprintf(stderr, "Could not initialize OpenGL (%s), using software renderer...", SDL_GetError());
  252. renderer = NULL;
  253. }
  254. #endif
  255. if (renderer == NULL) {
  256. renderer = &sdl_renderer_software;
  257. if (renderer->init() < 0) {
  258. SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not open video device!");
  259. return 1;
  260. }
  261. }
  262. window = renderer->get_window();
  263. SDL_zero(requested_audio_spec);
  264. requested_audio_spec.freq = 48000;
  265. requested_audio_spec.format = AUDIO_U8;
  266. requested_audio_spec.channels = 1;
  267. requested_audio_spec.samples = 4096;
  268. requested_audio_spec.callback = audio_callback;
  269. audio_device = SDL_OpenAudioDevice(NULL, 0, &requested_audio_spec, &audio_spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
  270. if (audio_device == 0) {
  271. SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not open audio device! %s", SDL_GetError());
  272. }
  273. u8 cont_loop = 1;
  274. zzt_thread_running = 1;
  275. zzt_thread = SDL_CreateThread(zzt_thread_func, "ZZT Executor", (void*)NULL);
  276. if (zzt_thread == NULL) {
  277. SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not create ZZT thread! %s", SDL_GetError());
  278. return 1;
  279. }
  280. if (audio_device != 0) {
  281. audio_stream_init(zeta_time_ms(), audio_spec.freq);
  282. audio_stream_set_volume(audio_stream_get_max_volume() >> 1);
  283. SDL_PauseAudioDevice(audio_device, 0);
  284. }
  285. sdl_timer_init();
  286. int should_render = 1;
  287. while (cont_loop) {
  288. if (!zzt_thread_running) { cont_loop = 0; break; }
  289. atomic_fetch_add(&zzt_renderer_waiting, 1);
  290. SDL_LockMutex(zzt_thread_lock);
  291. atomic_fetch_sub(&zzt_renderer_waiting, 1);
  292. u8* ram = zzt_get_ram();
  293. should_render = memcmp(ram + 0xB8000, zzt_vram_copy, 80*25*2);
  294. if (should_render) {
  295. memcpy(zzt_vram_copy, ram + 0xB8000, 80*25*2);
  296. renderer->update_vram(zzt_vram_copy);
  297. }
  298. zzt_mark_frame();
  299. // do KEYUPs before KEYDOWNS - fixes key loss issues w/ Windows
  300. while (slc > 0) {
  301. zzt_keyup(scancodes_lifted[--slc]);
  302. }
  303. while (SDL_PollEvent(&event)) {
  304. switch (event.type) {
  305. case SDL_KEYDOWN:
  306. if (windowed && (event.key.keysym.sym == 'q' || event.key.keysym.scancode == SDL_SCANCODE_ESCAPE)) {
  307. if (SDL_GetRelativeMouseMode() != 0) {
  308. SDL_SetRelativeMouseMode(0);
  309. break;
  310. }
  311. }
  312. #ifdef ENABLE_SCREENSHOTS
  313. if (event.key.keysym.sym == SDLK_F12) {
  314. int i = -1;
  315. FILE *file;
  316. char filename[24];
  317. int sflags = 0;
  318. int swidth = (zzt_video_mode() & 2) ? 80 : 40;
  319. if (charset_update_data == NULL || palette_update_data == NULL) {
  320. break;
  321. }
  322. if (!video_blink) sflags |= RENDER_BLINK_OFF;
  323. else if (sdl_is_blink_phase(zeta_time_ms())) sflags |= RENDER_BLINK_PHASE;
  324. while ((++i) <= 9999) {
  325. #ifdef USE_LIBPNG
  326. int stype = SCREENSHOT_TYPE_PNG;
  327. snprintf(filename, 23, "screen%d.png", i);
  328. #else
  329. int stype = SCREENSHOT_TYPE_BMP;
  330. snprintf(filename, 23, "screen%d.bmp", i);
  331. #endif
  332. file = fopen(filename, "rb");
  333. if (!file) {
  334. file = fopen(filename, "wb");
  335. if (!file) {
  336. fprintf(stderr, "Could not open file '%s' for writing!\n", filename);
  337. break;
  338. }
  339. if (write_screenshot(
  340. file, stype,
  341. swidth, sflags,
  342. zzt_get_ram() + 0xB8000, charset_update_data,
  343. charw, charh,
  344. palette_update_data
  345. ) < 0) {
  346. fprintf(stderr, "Could not write screenshot!\n");
  347. }
  348. fclose(file);
  349. break;
  350. } else {
  351. fclose(file);
  352. }
  353. }
  354. if (i > 9999) {
  355. fprintf(stderr, "Could not take screenshot!\n");
  356. }
  357. break;
  358. }
  359. #endif
  360. if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && KEYMOD_ALT(event.key.keysym.mod)) {
  361. // Alt+ENTER
  362. if (windowed) {
  363. SDL_DisplayMode mode;
  364. SDL_GetDesktopDisplayMode(SDL_GetWindowDisplayIndex(window), &mode);
  365. SDL_SetWindowSize(window, mode.w, mode.h);
  366. SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
  367. // force focus
  368. SDL_SetRelativeMouseMode(1);
  369. } else {
  370. SDL_SetWindowFullscreen(window, 0);
  371. SDL_SetWindowSize(window, 80*charw, 25*charh);
  372. // drop focus
  373. SDL_SetRelativeMouseMode(0);
  374. }
  375. windowed = 1 - windowed;
  376. break;
  377. }
  378. update_keymod(event.key.keysym.mod);
  379. scode = event.key.keysym.scancode;
  380. kcode = event.key.keysym.sym;
  381. if (kcode < 0 || kcode >= 127) kcode = 0;
  382. if (scode >= 0 && scode <= sdl_to_pc_scancode_max) {
  383. if (KEYMOD_SHIFT(event.key.keysym.mod)) kcode = as_shifted(kcode);
  384. zzt_key(kcode, sdl_to_pc_scancode[scode]);
  385. }
  386. break;
  387. case SDL_KEYUP:
  388. update_keymod(event.key.keysym.mod);
  389. scode = event.key.keysym.scancode;
  390. if (scode >= 0 && scode <= sdl_to_pc_scancode_max) {
  391. scancodes_lifted[slc++] = sdl_to_pc_scancode[scode];
  392. }
  393. break;
  394. case SDL_MOUSEBUTTONDOWN:
  395. if (SDL_GetRelativeMouseMode() == 0) {
  396. if (SDL_GetWindowFlags(window) & SDL_WINDOW_INPUT_FOCUS) {
  397. SDL_SetRelativeMouseMode(1);
  398. }
  399. } else {
  400. zzt_mouse_set(event.button.button);
  401. }
  402. break;
  403. case SDL_MOUSEBUTTONUP:
  404. zzt_mouse_clear(event.button.button);
  405. break;
  406. case SDL_MOUSEMOTION:
  407. if (SDL_GetRelativeMouseMode() != 0) {
  408. zzt_mouse_axis(0, event.motion.xrel);
  409. zzt_mouse_axis(1, event.motion.yrel);
  410. }
  411. break;
  412. case SDL_QUIT:
  413. cont_loop = 0;
  414. break;
  415. }
  416. }
  417. SDL_CondBroadcast(zzt_thread_cond);
  418. SDL_UnlockMutex(zzt_thread_lock);
  419. SDL_LockMutex(render_data_update_mutex);
  420. if (charset_update_requested) {
  421. renderer->update_charset(charw, charh, charset_update_data);
  422. charset_update_requested = 0;
  423. }
  424. if (palette_update_requested) {
  425. renderer->update_palette(palette_update_data);
  426. palette_update_requested = 0;
  427. }
  428. SDL_UnlockMutex(render_data_update_mutex);
  429. long curr_time = zeta_time_ms();
  430. int blink_mode = video_blink ? (sdl_is_blink_phase(curr_time) ? BLINK_MODE_2 : BLINK_MODE_1) : BLINK_MODE_NONE;
  431. renderer->draw(zzt_vram_copy, blink_mode);
  432. }
  433. zzt_thread_running = 0;
  434. if (audio_device != 0) {
  435. SDL_CloseAudioDevice(audio_device);
  436. }
  437. renderer->deinit();
  438. SDL_Quit();
  439. return 0;
  440. }