diff --git a/docs/syscall.md b/docs/syscall.md new file mode 100644 index 00000000..b453221c --- /dev/null +++ b/docs/syscall.md @@ -0,0 +1,34 @@ +# System Calls + +## Experimental Display and Event System Calls + +These system calls are solely for the convenience of accessing the [SDL library](https://www.libsdl.org/) and are only intended for the presentation of RISC-V graphics applications. They are not present in the ABI interface of POSIX or Linux. + +### `draw_frame` - Draw a frame around the SDL window + +**system call number**: `0xBEEF` + +**synopsis**: `void draw_frame(void *screen, int width, int height)` + +If a window does not already exist, one will be created with the specified `width` and `height`. The `screen` buffer will replace the content of the framebuffer, passing a different `width` or `height` compared to the size of the window is undefined behavior. This system call additionally polls events from the SDL library, and, if necessary, update the internal input specific event queue. + +### `draw_frame_pal` - Draw a frame with indexed-coloring around the SDL window + +**system call number**: `0xBABE` + +**synopsis**: `void draw_frame_pal(void *screen, void *pal, int width, int height)` + +Similar to `draw_frame`, but with indexed-coloring instead, `pal` is a RGB look up table with 256 different colors, all pixels in the framebuffer will be automatically mapped before being drawn on the screen. + +### `poll_event(event)` - Poll a input specific event from SDL + +**system call number**: `0xC0DE` + +**synopsis**: `int poll_event(void *event)` + +`event` will be filled with the polled event data, it should have a 32-bit `type` field, and associated with an appropriately sized value buffer. The internal event queue will be updated everytime `draw_frame` or `draw_frame_pal` is called. + +Currently accepted event types include the following: +* `KEY_EVENT` - `0x0`: Triggered when the status of keyboard changes, either when a key is pressed or released, and it returns a 32-bit keycode and a 8-bit key state, the values of the hexadecimal keycodes are listed in [SDL Keycode Lookup Table](https://wiki.libsdl.org/SDLKeycodeLookup) +* `MOUSE_MOTION_EVENT` - `0x1`: A mouse move event, with relative position information, two 32-bit signed integers that correspond to the x and y delta value, the mouse is continually wrapped in the window border by default. +* `MOUSE_BUTTON_EVENT` - `0x2`: the user code receives this event whenever the state of a mouse button changes, whether a button is pressed or released, and it returns a 8-bit value that indicates which button is updated(1 is left, 2 is middle, 3 is right and so on), as well as a 8-bit state value that indicates whether the button is pressed. \ No newline at end of file diff --git a/syscall.c b/syscall.c index 804bd53d..a4b5ceea 100644 --- a/syscall.c +++ b/syscall.c @@ -35,9 +35,10 @@ _(open, 1024) #ifdef ENABLE_SDL -#define __SYSCALL_LIST_EXT \ - _(draw_frame, 0xBEEF) \ - _(draw_frame_pal, 0xBABE) +#define __SYSCALL_LIST_EXT \ + _(draw_frame, 0xBEEF) \ + _(draw_frame_pal, 0xBABE) \ + _(poll_event, 0xC0DE) #else #define __SYSCALL_LIST_EXT #endif @@ -308,6 +309,7 @@ static void syscall_open(struct riscv_t *rv) #ifdef ENABLE_SDL extern void syscall_draw_frame(struct riscv_t *rv); extern void syscall_draw_frame_pal(struct riscv_t *rv); +extern void syscall_poll_event(struct riscv_t *rv); #endif void syscall_handler(struct riscv_t *rv) diff --git a/syscall_sdl.c b/syscall_sdl.c index fde7fc26..19cf7889 100644 --- a/syscall_sdl.c +++ b/syscall_sdl.c @@ -9,9 +9,77 @@ #include "state.h" +/* For optimization, the capcity of event queues must be the power of two to + * avoid the expensive modulo operation, the details are explained here: + * https://stackoverflow.com/questions/10527581/why-must-a-ring-buffer-size-be-a-power-of-2 + */ +#define EVENT_QUEUE_CAPACITY 128 + +enum { + KEY_EVENT = 0, + MOUSE_MOTION_EVENT = 1, + MOUSE_BUTTON_EVENT = 2, +}; + +typedef struct { + uint32_t keycode; + uint8_t state; +} key_event_t; + +typedef struct { + int32_t xrel, yrel; +} mouse_motion_t; + +typedef struct { + uint8_t button; + uint8_t state; +} mouse_button_t; + +typedef struct { + uint32_t type; + union { + key_event_t key_event; + union { + mouse_motion_t motion; + mouse_button_t button; + } mouse; + }; +} event_t; + +typedef struct { + event_t events[EVENT_QUEUE_CAPACITY]; + size_t start, end; + bool full; +} event_queue_t; + static SDL_Window *window = NULL; static SDL_Renderer *renderer; static SDL_Texture *texture; +static event_queue_t event_queue = { + .events = {}, + .start = 0, + .end = 0, + .full = false, +}; + +static bool event_pop(event_t *event) +{ + if (event_queue.start == event_queue.end) + return false; + *event = event_queue.events[event_queue.start++]; + event_queue.start &= EVENT_QUEUE_CAPACITY - 1; + event_queue.full = false; + return true; +} + +static void event_push(event_t event) +{ + if (event_queue.full) + return; + event_queue.events[event_queue.end++] = event; + event_queue.end &= EVENT_QUEUE_CAPACITY - 1; + event_queue.full = (event_queue.start == event_queue.end); +} /* check if we need to setup SDL and run event loop */ static bool check_sdl(struct riscv_t *rv, uint32_t width, uint32_t height) @@ -43,10 +111,59 @@ static bool check_sdl(struct riscv_t *rv, uint32_t width, uint32_t height) rv_halt(rv); return false; case SDL_KEYDOWN: - if (event.key.keysym.sym == SDLK_ESCAPE) { - rv_halt(rv); - return false; + if (event.key.keysym.sym == SDLK_ESCAPE && + SDL_GetRelativeMouseMode() == SDL_TRUE) { + SDL_SetRelativeMouseMode(SDL_FALSE); + break; } + /* fall through */ + case SDL_KEYUP: { + if (event.key.repeat) + break; + event_t new_event = { + .type = KEY_EVENT, + }; + key_event_t key_event = { + .keycode = event.key.keysym.sym, + .state = (bool) (event.key.state == SDL_PRESSED), + }; + memcpy(&new_event.key_event, &key_event, sizeof(key_event)); + event_push(new_event); + break; + } + case SDL_MOUSEMOTION: { + event_t new_event = { + .type = MOUSE_MOTION_EVENT, + }; + mouse_motion_t mouse_motion = { + .xrel = event.motion.xrel, + .yrel = event.motion.yrel, + }; + memcpy(&new_event.mouse.motion, &mouse_motion, + sizeof(mouse_motion)); + event_push(new_event); + break; + } + case SDL_MOUSEBUTTONDOWN: + if (event.button.button == SDL_BUTTON_LEFT && + SDL_GetRelativeMouseMode() == SDL_FALSE) { + SDL_SetRelativeMouseMode(SDL_TRUE); + break; + } + /* fall through */ + case SDL_MOUSEBUTTONUP: { + event_t new_event = { + .type = MOUSE_BUTTON_EVENT, + }; + mouse_button_t mouse_button = { + .button = event.button.button, + .state = (bool) (event.button.state == SDL_PRESSED), + }; + memcpy(&new_event.mouse.button, &mouse_button, + sizeof(mouse_button)); + event_push(new_event); + break; + } } } return true; @@ -56,7 +173,7 @@ void syscall_draw_frame(struct riscv_t *rv) { state_t *s = rv_userdata(rv); /* access userdata */ - /* draw(screen, width, height) */ + /* draw_frame(screen, width, height) */ const uint32_t screen = rv_get_reg(rv, rv_reg_a0); const uint32_t width = rv_get_reg(rv, rv_reg_a1); const uint32_t height = rv_get_reg(rv, rv_reg_a2); @@ -80,8 +197,8 @@ void syscall_draw_frame_pal(struct riscv_t *rv) { state_t *s = rv_userdata(rv); /* access userdata */ - /* draw(screen, width, height) */ - const uint32_t buf = rv_get_reg(rv, rv_reg_a0); + /* draw_frame_pal(screen, pal, width, height) */ + const uint32_t screen = rv_get_reg(rv, rv_reg_a0); const uint32_t pal = rv_get_reg(rv, rv_reg_a1); const uint32_t width = rv_get_reg(rv, rv_reg_a2); const uint32_t height = rv_get_reg(rv, rv_reg_a3); @@ -89,11 +206,10 @@ void syscall_draw_frame_pal(struct riscv_t *rv) if (!check_sdl(rv, width, height)) return; - /* read directly into video memory */ uint8_t *i = malloc(width * height); uint8_t *j = malloc(256 * 3); - memory_read(s->mem, i, buf, width * height); + memory_read(s->mem, i, screen, width * height); memory_read(s->mem, j, pal, 256 * 3); int pitch = 0; @@ -118,3 +234,35 @@ void syscall_draw_frame_pal(struct riscv_t *rv) free(i); free(j); } + +void syscall_poll_event(struct riscv_t *rv) +{ + state_t *s = rv_userdata(rv); /* access userdata */ + + /* poll_event(event) */ + const uint32_t base = rv_get_reg(rv, rv_reg_a0); + + event_t event; + if (!event_pop(&event)) { + rv_set_reg(rv, rv_reg_a0, 0); + return; + } + + memory_write(s->mem, base + 0, (const uint8_t *) &event.type, 4); + switch (event.type) { + case KEY_EVENT: + memory_write(s->mem, base + 4, (const uint8_t *) &event.key_event, + sizeof(key_event_t)); + break; + case MOUSE_MOTION_EVENT: + memory_write(s->mem, base + 4, (const uint8_t *) &event.mouse.motion, + sizeof(mouse_motion_t)); + break; + case MOUSE_BUTTON_EVENT: + memory_write(s->mem, base + 4, (const uint8_t *) &event.mouse.button, + sizeof(mouse_button_t)); + break; + } + + rv_set_reg(rv, rv_reg_a0, 1); +} \ No newline at end of file