diff --git a/Makefile b/Makefile index f153dbf..b737e13 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CFLAGS = -g -Os -Wall -Wextra -Wconversion -Wno-sign-conversion -RISC_CFLAGS = $(CFLAGS) -std=c99 `sdl2-config --cflags --libs` +RISC_CFLAGS = $(CFLAGS) -std=c99 `sdl2-config --cflags --libs` -lm RISC_SOURCE = \ sdl-main.c \ diff --git a/README.md b/README.md index a70e513..3efe9c4 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Current emulation status * Display * OK. You can adjust the colors by editing `sdl-main.c`. + * Use F11 to toggle full screne display. * SD-Card * Very inaccurate, but good enough for Oberon. If you're going to diff --git a/sdl-main.c b/sdl-main.c index f86c6bf..b71cd16 100644 --- a/sdl-main.c +++ b/sdl-main.c @@ -2,7 +2,8 @@ #include #include #include -#include +#include +#include #include #include "risc.h" #include "sdl-ps2.h" @@ -18,14 +19,32 @@ static uint32_t BLACK = 0x657b83, WHITE = 0xfdf6e3; //static uint32_t BLACK = 0x0000FF, WHITE = 0xFFFF00; //static uint32_t BLACK = 0x000000, WHITE = 0x00FF00; -void init_texture(SDL_Texture *texture); -void update_texture(uint32_t *framebuffer, SDL_Texture *texture); + +static void init_texture(SDL_Texture *texture); +static void update_texture(uint32_t *framebuffer, SDL_Texture *texture); + +static int clamp(int x, int min, int max); +static SDL_Rect scale_display(SDL_Window *window, double *scale); + +static void usage() { + fprintf(stderr, "Usage: risc [--fullscreen] disk-file-name\n"); + exit(1); +} int main (int argc, char *argv[]) { - if (argc != 2) { - fprintf(stderr, "Usage: risc disk-file-name\n"); - return 1; + bool fullscreen = false; + while (argc > 1 && argv[1][0] == '-') { + if (strcmp(argv[1], "--fullscreen") == 0) { + fullscreen = true; + } else { + usage(); + } + argc--; argv++; } + if (argc != 2) { + usage(); + } + struct RISC *risc = risc_new(argv[1]); if (SDL_Init(SDL_INIT_VIDEO) != 0) { @@ -36,12 +55,29 @@ int main (int argc, char *argv[]) { SDL_EnableScreenSaver(); SDL_ShowCursor(false); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); + + int window_pos = SDL_WINDOWPOS_UNDEFINED; + int window_flags = SDL_WINDOW_HIDDEN; + if (fullscreen) { + window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + // Search for a 1024x768 display in multi-monitor configurations + int display_cnt = SDL_GetNumVideoDisplays(); + for (int i = 0; i < display_cnt; i++) { + SDL_Rect bounds; + SDL_GetDisplayBounds(i, &bounds); + if (bounds.w >= SCREEN_WIDTH && bounds.h == SCREEN_HEIGHT) { + window_pos = SDL_WINDOWPOS_UNDEFINED_DISPLAY(i); + if (bounds.w == SCREEN_WIDTH) + break; + } + } + } SDL_Window *window = SDL_CreateWindow("Project Oberon", - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, + window_pos, window_pos, SCREEN_WIDTH, SCREEN_HEIGHT, - SDL_WINDOW_HIDDEN); + window_flags); if (window == NULL) { fprintf(stderr, "Could not create window: %s\n", SDL_GetError()); return 1; @@ -62,36 +98,73 @@ int main (int argc, char *argv[]) { return 1; } + double display_scale; + SDL_Rect display_rect = scale_display(window, &display_scale); init_texture(texture); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); SDL_ShowWindow(window); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, &display_rect); + SDL_RenderPresent(renderer); bool done = false; - SDL_Event event; + bool mouse_was_offscreen = false; while (!done) { uint32_t frame_start = SDL_GetTicks(); + SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { - case SDL_QUIT: + case SDL_QUIT: { done = true; + } + + case SDL_WINDOWEVENT: { + if (event.window.event == SDL_WINDOWEVENT_RESIZED) { + display_rect = scale_display(window, &display_scale); + } break; - case SDL_MOUSEMOTION: - risc_mouse_moved(risc, event.motion.x, SCREEN_HEIGHT - event.motion.y - 1); + } + + case SDL_MOUSEMOTION: { + int scaled_x = (int)round((event.motion.x - display_rect.x) * display_scale); + int scaled_y = (int)round((event.motion.y - display_rect.y) * display_scale); + int x = clamp(scaled_x, 0, SCREEN_WIDTH - 1); + int y = clamp(scaled_y, 0, SCREEN_HEIGHT - 1); + bool mouse_is_offscreen = x != scaled_x || y != scaled_y; + if (mouse_is_offscreen != mouse_was_offscreen) { + SDL_ShowCursor(mouse_is_offscreen); + mouse_was_offscreen = mouse_is_offscreen; + } + risc_mouse_moved(risc, x, SCREEN_HEIGHT - y - 1); break; + } + case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: + case SDL_MOUSEBUTTONUP: { risc_mouse_button(risc, event.button.button, event.button.state == SDL_PRESSED); break; + } + case SDL_KEYDOWN: case SDL_KEYUP: { if (event.key.keysym.sym == SDLK_F12) { + // F12 = Oberon reset if (event.key.state == SDL_PRESSED) { risc_reset(risc); } + } else if (event.key.keysym.sym == SDLK_F11) { + // F11 = Toggle fullscreen + if (event.key.state == SDL_PRESSED) { + fullscreen ^= true; + if (fullscreen) { + SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } else { + SDL_SetWindowFullscreen(window, 0); + } + } } else { + // Pass other keys to Oberon uint8_t scancode[MAX_PS2_CODE_LEN]; int len = ps2_encode(event.key.keysym.scancode, event.key.state == SDL_PRESSED, @@ -107,7 +180,8 @@ int main (int argc, char *argv[]) { risc_run(risc, CPU_HZ / FPS); update_texture(risc_get_framebuffer_ptr(risc), texture); - SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, &display_rect); SDL_RenderPresent(renderer); uint32_t frame_end = SDL_GetTicks(); @@ -116,15 +190,43 @@ int main (int argc, char *argv[]) { SDL_Delay(delay); } } - return 0; } +static int clamp(int x, int min, int max) { + if (x < min) return min; + if (x > max) return max; + return x; +} + +static SDL_Rect scale_display(SDL_Window *window, double *display_scale) { + int win_w, win_h; + SDL_GetWindowSize(window, &win_w, &win_h); + double oberon_aspect = (double)SCREEN_WIDTH / SCREEN_HEIGHT; + double window_aspect = (double)win_w / win_h; + if (fabs(oberon_aspect - window_aspect) < 0.0001) { + // Aspect ratios are equal + *display_scale = (double)SCREEN_WIDTH / win_w; + return (SDL_Rect){ .x = 0, .y = 0, .w = win_w, .h = win_h }; + } + else if (oberon_aspect > window_aspect) { + // Oberon display is wider than our window -- letterbox it + *display_scale = (double)SCREEN_WIDTH / win_w; + int h = (int)ceil(SCREEN_HEIGHT / *display_scale); + return (SDL_Rect){ .x = 0, .y = (win_h - h) / 2, .w = win_w, .h = h }; + } else { + // Oberon display is taller than our window + *display_scale = (double)SCREEN_HEIGHT / win_h; + int w = (int)ceil(SCREEN_WIDTH / *display_scale); + return (SDL_Rect){ .x = (win_w - w) / 2, .y = 0, .w = w, .h = win_h }; + } +} + static uint32_t cache[SCREEN_WIDTH * SCREEN_HEIGHT / 32]; static uint32_t buffer[SCREEN_WIDTH * SCREEN_HEIGHT]; -void init_texture(SDL_Texture *texture) { +static void init_texture(SDL_Texture *texture) { memset(cache, 0, sizeof(cache)); for (size_t i = 0; i < sizeof(buffer)/sizeof(buffer[0]); ++i) { buffer[i] = BLACK; @@ -132,7 +234,7 @@ void init_texture(SDL_Texture *texture) { SDL_UpdateTexture(texture, NULL, buffer, SCREEN_WIDTH * 4); } -void update_texture(uint32_t *framebuffer, SDL_Texture *texture) { +static void update_texture(uint32_t *framebuffer, SDL_Texture *texture) { // TODO: move dirty rectangle tracking into emulator core? int dirty_y1 = SCREEN_HEIGHT; int dirty_y2 = 0;