Skip to content

Commit 99d11d7

Browse files
committed
tests: Provide minimal cross-platform GUI library
Fenster [1] offers an extremely minimalistic and specific approach to displaying a cross-platform 2D canvas, characterized by its ease of use — simply include its header and begin making API calls. Integrating Fenster with rv32emu's SDL-based system calls could significantly enhance usability and simplify any future porting efforts, making it an ideal choice for streamlined, cross-platform graphical implementation. This porting effort was done by Alan Jian. [1] https://github.com/zserge/fenster Close #332
1 parent 2cbd4aa commit 99d11d7

File tree

2 files changed

+315
-0
lines changed

2 files changed

+315
-0
lines changed

docs/syscall.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ Any other system calls will fail with an "unknown syscall" error.
116116

117117
These system calls are solely for the convenience of accessing the [SDL library](https://www.libsdl.org/) and [SDL2_Mixer](https://wiki.libsdl.org/SDL2_mixer) and are only intended for the presentation of RISC-V graphics applications. They are not present in the ABI interface of POSIX or Linux.
118118

119+
Check the [fenster.h](tests/fenster.h) header, which offers an extremely minimalistic and specific approach to displaying a 2D canvas.
120+
This integrates with rv32emu's SDL-based system calls, significantly enhancing usability and simplifying any future porting efforts.
121+
119122
### `draw_frame` - Draw a frame around the SDL window
120123

121124
**system call number**: `0xBEEF`

tests/fenster.h

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
/* Fenster - the most minimal cross-platform GUI library
2+
*
3+
* Copyright (c) 2022 Serge Zaitsev
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
* SOFTWARE.
22+
*
23+
* Source: https://github.com/zserge/fenster
24+
*
25+
* Modified by Alan Jian <[email protected]>
26+
* - Port Fenster to rv32emu.
27+
* - Use the unique SDL-oriented system calls of rv32emu and clock_gettime()
28+
*/
29+
30+
#ifndef FENSTER_H
31+
#define FENSTER_H
32+
33+
#include <stdint.h>
34+
#include <stdlib.h>
35+
#include <string.h>
36+
#include <time.h>
37+
38+
struct fenster {
39+
const char *title;
40+
const int width, height;
41+
uint32_t *buf;
42+
int keys[256]; /* keys are mostly ASCII, but arrows are 17..20 */
43+
int mod; /* mod is 4 bits mask, ctrl=1, shift=2, alt=4, meta=8 */
44+
int x, y;
45+
int mouse;
46+
void *event_queue;
47+
uint32_t event_count;
48+
size_t event_queue_start;
49+
};
50+
51+
#ifndef FENSTER_API
52+
#define FENSTER_API extern
53+
#endif
54+
FENSTER_API int fenster_open(struct fenster *f);
55+
FENSTER_API int fenster_loop(struct fenster *f);
56+
FENSTER_API void fenster_close(struct fenster *f);
57+
FENSTER_API void fenster_sleep(int64_t ms);
58+
FENSTER_API int64_t fenster_time(void);
59+
#define fenster_pixel(f, x, y) ((f)->buf[((y) * (f)->width) + (x)])
60+
61+
#ifndef FENSTER_HEADER
62+
#define RV_QUEUE_CAPACITY 128
63+
64+
enum {
65+
RV_KEYCODE_RETURN = 0x0000000D,
66+
RV_KEYCODE_UP = 0x40000052,
67+
RV_KEYCODE_DOWN = 0x40000051,
68+
RV_KEYCODE_RIGHT = 0x4000004F,
69+
RV_KEYCODE_LEFT = 0x40000050,
70+
RV_KEYCODE_LCTRL = 0x400000E0,
71+
RV_KEYCODE_RCTRL = 0x400000E4,
72+
RV_KEYCODE_LSHIFT = 0x400000E1,
73+
RV_KEYCODE_RSHIFT = 0x400000E5,
74+
RV_KEYCODE_LALT = 0x400000E2,
75+
RV_KEYCODE_RALT = 0x400000E6,
76+
RV_KEYCODE_LMETA = 0x400000E3,
77+
RV_KEYCODE_RMETA = 0x400000E7,
78+
};
79+
80+
typedef struct {
81+
uint32_t keycode;
82+
uint8_t state;
83+
uint16_t mod;
84+
} rv_key_rv_event_t;
85+
86+
typedef struct {
87+
int32_t x, y, xrel, yrel;
88+
} rv_mouse_motion_t;
89+
90+
enum {
91+
RV_MOUSE_BUTTON_LEFT = 1,
92+
};
93+
94+
typedef struct {
95+
uint8_t button;
96+
uint8_t state;
97+
} rv_mouse_button_t;
98+
99+
enum {
100+
RV_KEY_EVENT = 0,
101+
RV_MOUSE_MOTION_EVENT = 1,
102+
RV_MOUSE_BUTTON_EVENT = 2,
103+
RV_QUIT_EVENT = 3,
104+
};
105+
106+
typedef struct {
107+
uint32_t type;
108+
union {
109+
rv_key_rv_event_t key_event;
110+
union {
111+
rv_mouse_motion_t motion;
112+
rv_mouse_button_t button;
113+
} mouse;
114+
};
115+
} rv_event_t;
116+
117+
enum {
118+
RV_RELATIVE_MODE_SUBMISSION = 0,
119+
RV_WINDOW_TITLE_SUBMISSION = 1,
120+
};
121+
122+
typedef struct {
123+
uint8_t enabled;
124+
} rv_mouse_rv_submission_t;
125+
126+
typedef struct {
127+
uint32_t title;
128+
uint32_t size;
129+
} rv_title_rv_submission_t;
130+
131+
typedef struct {
132+
uint32_t type;
133+
union {
134+
rv_mouse_rv_submission_t mouse;
135+
rv_title_rv_submission_t title;
136+
};
137+
} rv_submission_t;
138+
139+
FENSTER_API int fenster_open(struct fenster *f)
140+
{
141+
f->event_queue = malloc((sizeof(rv_event_t) + sizeof(rv_submission_t)) *
142+
RV_QUEUE_CAPACITY);
143+
f->event_count = 0;
144+
register int a0 __asm("a0") = (int) f->event_queue;
145+
register int a1 __asm("a1") = RV_QUEUE_CAPACITY;
146+
register int a2 __asm("a2") = (int) &f->event_count;
147+
register int a7 __asm("a7") = 0xc0de;
148+
__asm volatile("scall" : : "r"(a0), "r"(a1), "r"(a2), "r"(a7) : "memory");
149+
f->event_queue_start = 0;
150+
151+
rv_submission_t submission = {
152+
.type = RV_WINDOW_TITLE_SUBMISSION,
153+
.title.title = (uint32_t) f->title,
154+
.title.size = strlen(f->title),
155+
};
156+
rv_submission_t *submission_queue =
157+
(rv_submission_t *) ((rv_event_t *) f->event_queue + RV_QUEUE_CAPACITY);
158+
submission_queue[0] = submission;
159+
a0 = 1;
160+
a7 = 0xfeed;
161+
__asm volatile("scall" : : "r"(a0), "r"(a7) : "memory");
162+
163+
return 0;
164+
}
165+
166+
FENSTER_API void fenster_close(struct fenster *f)
167+
{
168+
free(f->event_queue);
169+
}
170+
171+
FENSTER_API int fenster_loop(struct fenster *f)
172+
{
173+
for (; f->event_count > 0; f->event_count--) {
174+
rv_event_t event =
175+
((rv_event_t *) f->event_queue)[f->event_queue_start];
176+
switch (event.type) {
177+
case RV_KEY_EVENT: {
178+
uint32_t keycode = event.key_event.keycode;
179+
uint8_t state = event.key_event.state;
180+
181+
if (keycode == RV_KEYCODE_RETURN)
182+
f->keys[10] = state;
183+
else if (keycode < 128)
184+
f->keys[keycode] = state;
185+
186+
if (keycode == RV_KEYCODE_UP)
187+
f->keys[17] = state;
188+
if (keycode == RV_KEYCODE_DOWN)
189+
f->keys[18] = state;
190+
if (keycode == RV_KEYCODE_RIGHT)
191+
f->keys[19] = state;
192+
if (keycode == RV_KEYCODE_LEFT)
193+
f->keys[20] = state;
194+
195+
if (keycode == RV_KEYCODE_LCTRL || keycode == RV_KEYCODE_RCTRL)
196+
f->mod = state ? f->mod | 1 : f->mod & 0b1110;
197+
if (keycode == RV_KEYCODE_LSHIFT || keycode == RV_KEYCODE_RSHIFT)
198+
f->mod = state ? f->mod | 2 : f->mod & 0b1101;
199+
if (keycode == RV_KEYCODE_LALT || keycode == RV_KEYCODE_RALT)
200+
f->mod = state ? f->mod | 4 : f->mod & 0b1011;
201+
if (keycode == RV_KEYCODE_LMETA || keycode == RV_KEYCODE_RMETA)
202+
f->mod = state ? f->mod | 8 : f->mod & 0b0111;
203+
204+
break;
205+
}
206+
case RV_MOUSE_MOTION_EVENT:
207+
f->x = event.mouse.motion.x;
208+
f->y = event.mouse.motion.y;
209+
break;
210+
case RV_MOUSE_BUTTON_EVENT:
211+
if (event.mouse.button.button == RV_MOUSE_BUTTON_LEFT)
212+
f->mouse = event.mouse.button.state;
213+
break;
214+
case RV_QUIT_EVENT:
215+
return -1;
216+
}
217+
f->event_queue_start =
218+
(f->event_queue_start + 1) & (RV_QUEUE_CAPACITY - 1);
219+
}
220+
221+
register int a0 __asm("a0") = (uintptr_t) f->buf;
222+
register int a1 __asm("a1") = f->width;
223+
register int a2 __asm("a2") = f->height;
224+
register int a7 __asm("a7") = 0xbeef;
225+
__asm volatile("scall" : : "r"(a0), "r"(a1), "r"(a2), "r"(a7) : "memory");
226+
return 0;
227+
}
228+
229+
#undef RV_QUEUE_CAPACITY
230+
231+
FENSTER_API int64_t fenster_time(void)
232+
{
233+
return (int64_t) clock() / (CLOCKS_PER_SEC / 1000.0f);
234+
}
235+
FENSTER_API void fenster_sleep(int64_t ms)
236+
{
237+
int64_t start = fenster_time();
238+
while (fenster_time() - start < ms)
239+
;
240+
}
241+
242+
#ifdef __cplusplus
243+
class Fenster
244+
{
245+
struct fenster f;
246+
int64_t now;
247+
248+
public:
249+
Fenster(const int w, const int h, const char *title)
250+
: f{.title = title, .width = w, .height = h}
251+
{
252+
this->f.buf = new uint32_t[w * h];
253+
this->now = fenster_time();
254+
fenster_open(&this->f);
255+
}
256+
~Fenster()
257+
{
258+
fenster_close(&this->f);
259+
delete[] this->f.buf;
260+
}
261+
bool loop(const int fps)
262+
{
263+
int64_t t = fenster_time();
264+
if (t - this->now < 1000 / fps) {
265+
fenster_sleep(t - now);
266+
}
267+
this->now = t;
268+
return fenster_loop(&this->f) == 0;
269+
}
270+
inline uint32_t &px(const int x, const int y)
271+
{
272+
return fenster_pixel(&this->f, x, y);
273+
}
274+
bool key(int c) { return c >= 0 && c < 128 ? this->f.keys[c] : false; }
275+
int x() { return this->f.x; }
276+
int y() { return this->f.y; }
277+
int mouse() { return this->f.mouse; }
278+
int mod() { return this->f.mod; }
279+
};
280+
#endif /* __cplusplus */
281+
282+
#endif /* !FENSTER_HEADER */
283+
#endif /* FENSTER_H */
284+
285+
#if 0
286+
/* A very minimal example of a Fenster app:
287+
* - Opens a window
288+
* - Starts a loop
289+
* - Changes pixel colours based on some "shader" formula
290+
* - Sleeps if needed to maintain a certain frame rate
291+
* - Closes a window
292+
*/
293+
#include "fenster.h"
294+
enum { W = 320, H = 240 };
295+
int main()
296+
{
297+
uint32_t buf[W * H];
298+
struct fenster f = {.title = "hello", .width = W, .height = H, .buf = buf};
299+
fenster_open(&f);
300+
uint32_t t = 0;
301+
while (fenster_loop(&f) == 0) {
302+
t++;
303+
for (int i = 0; i < 320; i++) {
304+
for (int j = 0; j < 240; j++)
305+
fenster_pixel(&f, i, j) = i ^ j ^ t; /* Munching squares */
306+
}
307+
fenster_sleep(100);
308+
}
309+
fenster_close(&f);
310+
return 0;
311+
}
312+
#endif

0 commit comments

Comments
 (0)