Skip to content

Commit

Permalink
Merge pull request #62 from alanjian85/master
Browse files Browse the repository at this point in the history
Apply bidirectional queue design to the input system
  • Loading branch information
jserv authored Oct 2, 2022
2 parents 93952a1 + db8620e commit 3517f01
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 60 deletions.
31 changes: 24 additions & 7 deletions docs/syscall.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,32 @@ These system calls are solely for the convenience of accessing the [SDL library]

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.

### `poll_event(event)` - Poll a input specific event from SDL
### `setup_queue` - Setup input system's dedicated event and submission queue

**system call number**: `0xC0DE`

**synopsis**: `int poll_event(void *event)`
**synopsis**: `void *setup_queue(int capacity, unsigned int* event_count)`

`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` is called.
Allocate a continuous memory chunk that has the requested capacity and two closely packed queues, the event queue and the submission queue. The base address of the event queue is located in the returned address, and the submission queue is immediately after the event queue's last element, which is the event queue's base address plus the size of each event element times the given capacity. The capacity must be a power of 2; if it is not, it will be rounded up to the next highest power of 2. It is crucial to initialize the event counter variable before supplying its address to this system call because it serves as an interaction to the user that an event has been added to the event queue.

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.
#### Events

An event entry is made up of a 32-bit value representing the event's type and a `union` buffer containing t1he event's parameters.

* `KEY_EVENT`: Either a key is pressed or released. Its value buffer is made up of a 32-bit universal key code and an 8-bit state flag; if the corresponding character of the pressed key is not printable, the bit right after the most significant bit is set; for example, the "a" key's correspoding character is printable, so its keycode is the ASCII code of the "a" character, which is `0x61`. However, because the left shift key doesn't have a corresponding printable key, its hexadecimal value is `0x400000E1`, with the 31 bit is set.
* `MOUSE_MOTION_EVENT`: The cursor is moved during the current frame. This event contains two signed integer value, which is the delta of x position and y position respectively. If the relative mouse mode is enabled, the mouse movement will never be 0 because the cursor is wrapped within the canvas and is repeated whenever the cursor reaches the border.
* `MOUSE_BUTTON_EVENT`: The state of a mouse button has been changed. Its value buffer contains a 8-bit button value(1 is left, 2 is middle, 3 is right and so on) and an 8-bit boolean flag that indicates whether the mouse button is pressed.

### `submit_queue` - Notify the emulator a submission has been pushed into the submission queue

**system call number**: `0xFEED`

**synopsis**: `void submit_queue(int count)`

To inform the emulator that a batch of submissions should be processed, the application code should push several submissions into the queue first, and then pass the size of the submissions batch to this system call; the submissions will be processed and executed sequentially and immediately.

#### Submissions

The submission entry is structured similarly to an event entry, with a 32-bit type field and an associated dynamic-sized value buffer whose width depends on the type of submission.

* `RELATIVE_MODE_SUBMISSION`: Enable or disable the mouse relative mode. If the mouse relative mode is enabled, the mouse cursor is wrapped within the window border, it's associated with an 8-bit wide boolean value that indicates whether the relative mouse mode should be enbled.
6 changes: 4 additions & 2 deletions src/syscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
#if RV32_HAS(SDL)
#define __SYSCALL_LIST_EXT \
_(draw_frame, 0xBEEF) \
_(poll_event, 0xC0DE)
_(setup_queue, 0xC0DE) \
_(submit_queue, 0xFEED)
#else
#define __SYSCALL_LIST_EXT
#endif
Expand Down Expand Up @@ -316,7 +317,8 @@ static void syscall_open(struct riscv_t *rv)

#if RV32_HAS(SDL)
extern void syscall_draw_frame(struct riscv_t *rv);
extern void syscall_poll_event(struct riscv_t *rv);
extern void syscall_setup_queue(struct riscv_t *rv);
extern void syscall_submit_queue(struct riscv_t *rv);
#endif

void syscall_handler(struct riscv_t *rv)
Expand Down
145 changes: 94 additions & 51 deletions src/syscall_sdl.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@

#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,
Expand Down Expand Up @@ -47,38 +41,78 @@ typedef struct {
} event_t;

typedef struct {
event_t events[EVENT_QUEUE_CAPACITY];
size_t start, end;
bool full;
event_t *base;
size_t end;
} event_queue_t;

enum {
RELATIVE_MODE_SUBMISSION = 0,
};

typedef struct {
uint32_t type;
union {
union {
uint8_t enabled;
} mouse;
};
} submission_t;

typedef struct {
submission_t *base;
size_t start;
} submission_queue_t;

/* SDL-related variables */
static SDL_Window *window = NULL;
static SDL_Renderer *renderer;
static SDL_Texture *texture;
/* Event queue specific variables */
static uint32_t queues_capacity;
static uint32_t event_count;
static event_queue_t event_queue = {
.events = {},
.start = 0,
.base = NULL,
.end = 0,
.full = false,
};
static submission_queue_t submission_queue = {
.base = NULL,
.start = 0,
};

static bool event_pop(event_t *event)
static submission_t submission_pop(void)
{
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;
submission_t submission = submission_queue.base[submission_queue.start++];
submission_queue.start &= queues_capacity - 1;
return submission;
}

static void event_push(event_t event)
static void event_push(struct riscv_t *rv, 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);
event_queue.base[event_queue.end++] = event;
event_queue.end &= queues_capacity - 1;

state_t *s = rv_userdata(rv);
uint32_t count;
memory_read(s->mem, (void *) &count, event_count, sizeof(uint32_t));
count += 1;
memory_write(s->mem, event_count, (void *) &count, sizeof(uint32_t));
}

static uint32_t round_pow2(uint32_t x)
{
#if defined __GNUC__ || defined __clang__
x = 1 << (32 - __builtin_clz(x - 1));
#else
/* Bit Twiddling Hack */
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x++;
#endif
return x;
}

/* check if we need to setup SDL and run event loop */
Expand Down Expand Up @@ -128,7 +162,7 @@ static bool check_sdl(struct riscv_t *rv, uint32_t width, uint32_t height)
.state = (bool) (event.key.state == SDL_PRESSED),
};
memcpy(&new_event.key_event, &key_event, sizeof(key_event));
event_push(new_event);
event_push(rv, new_event);
break;
}
case SDL_MOUSEMOTION: {
Expand All @@ -141,7 +175,7 @@ static bool check_sdl(struct riscv_t *rv, uint32_t width, uint32_t height)
};
memcpy(&new_event.mouse.motion, &mouse_motion,
sizeof(mouse_motion));
event_push(new_event);
event_push(rv, new_event);
break;
}
case SDL_MOUSEBUTTONDOWN:
Expand All @@ -161,7 +195,7 @@ static bool check_sdl(struct riscv_t *rv, uint32_t width, uint32_t height)
};
memcpy(&new_event.mouse.button, &mouse_button,
sizeof(mouse_button));
event_push(new_event);
event_push(rv, new_event);
break;
}
}
Expand Down Expand Up @@ -193,34 +227,43 @@ void syscall_draw_frame(struct riscv_t *rv)
SDL_RenderPresent(renderer);
}

void syscall_poll_event(struct riscv_t *rv)
void syscall_setup_queue(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);
/* setup_queue(capacity, event_count) */
queues_capacity = rv_get_reg(rv, rv_reg_a0);
event_count = rv_get_reg(rv, rv_reg_a1);

event_t event;
if (!event_pop(&event)) {
queues_capacity = round_pow2(queues_capacity);
if (queues_capacity == 0) {
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;
}
/* FIXME: Allocate a memory chunk in the emulator's address space so that
* the user can access it */
void *base = malloc(sizeof(event_t) * queues_capacity +
sizeof(submission_t) * queues_capacity);
event_queue.base = base;
submission_queue.base = base + sizeof(event_t) * queues_capacity;

rv_set_reg(rv, rv_reg_a0, 1);
rv_set_reg(
rv, rv_reg_a0,
(uint32_t) (uintptr_t) base); /* eliminate the "cast from pointer to
integer of different size" warning*/
}

void syscall_submit_queue(struct riscv_t *rv)
{
/* submit_queue(count) */
uint32_t count = rv_get_reg(rv, rv_reg_a0);

while (count--) {
submission_t submission = submission_pop();

switch (submission.type) {
case RELATIVE_MODE_SUBMISSION:
SDL_SetRelativeMouseMode(submission.mouse.enabled);
break;
}
}
}

0 comments on commit 3517f01

Please sign in to comment.