Mirai's Miscellaneous Misadventures

M50 / engines / fbdev.c

1/* license: AGPLv3 or later */
2/* copyright 2024 zamfofex */
3
4#include <unistd.h>
5#include <stdlib.h>
6#include <termios.h>
7#include <time.h>
8#include <fcntl.h>
9#include <stdint.h>
10#include <string.h>
11#include <sys/mman.h>
12#include <sys/ioctl.h>
13#include <linux/fb.h>
14#include <linux/kd.h>
15#include <linux/input.h>
16
17#include <mimimi.h>
18
19struct mimimi_fbdev
20{
21	unsigned char *buffer;
22	int width, height;
23	int x_offset, y_offset;
24	int line_length;
25	int bytes_per_pixel;
26};
27
28static struct termios mimimi_fbdev_tio;
29
30static void mimimi_fbdev_exit(void)
31{
32	ioctl(0, KDSETMODE, KD_TEXT);
33	tcsetattr(0, TCSANOW, &mimimi_fbdev_tio);
34}
35
36static void mimimi_fbdev_stamp(void *data, int x0, int y0, void *texture)
37{
38	static unsigned char rch[] = {0x11, 0x44, 0x77, 0x99, 0xCC, 0xFF};
39	static unsigned char gch[] = {0x11, 0x33, 0x55, 0x77, 0x99, 0xBB, 0xDD, 0xFF};
40	static unsigned char bch[] = {0x22, 0x55, 0x88, 0xBB, 0xEE};
41	
42	struct mimimi_image *image;
43	struct mimimi_fbdev *fbdev_data;
44	int x, y;
45	int offset_x, offset_y, offset;
46	unsigned char color;
47	unsigned char r, g, b;
48	
49	image = texture;
50	fbdev_data = data;
51	
52	for (y = 0 ; y < image->height ; y++)
53	for (x = 0 ; x < image->width ; x++)
54	{
55		if (y + y0 < 0) continue;
56		if (x + x0 < 0) continue;
57		
58		if (y + y0 >= fbdev_data->height) continue;
59		if (x + x0 >= fbdev_data->width) continue;
60		
61		offset_x = (x + x0 + fbdev_data->x_offset) * fbdev_data->bytes_per_pixel;
62		offset_y = (y + y0 + fbdev_data->y_offset) * fbdev_data->line_length;
63		
64		offset = offset_x + offset_y;
65		
66		color = image->colors[x + y * image->width];
67		if (color == 0) continue;
68		
69		if (fbdev_data->bytes_per_pixel == 4)
70			fbdev_data->buffer[offset + 3] = 0xFF;
71		
72		if (color < 0x10)
73		{
74			fbdev_data->buffer[offset + 2] = color * 0x11;
75			fbdev_data->buffer[offset + 1] = color * 0x11;
76			fbdev_data->buffer[offset + 0] = color * 0x11;
77			continue;
78		}
79		
80		color -= 0x10;
81		r = rch[(color / 40) % 6];
82		g = gch[(color / 5) % 8];
83		b = bch[(color / 1) % 5];
84		
85		fbdev_data->buffer[offset + 2] = r;
86		fbdev_data->buffer[offset + 1] = g;
87		fbdev_data->buffer[offset + 0] = b;
88	}
89}
90
91static void *mimimi_fbdev_texture(void *data, struct mimimi_image *image)
92{
93	(void) data;
94	return image;
95}
96
97static void mimimi_fbdev_invalidate(void *data, void *texture)
98{
99	(void) data;
100	(void) texture;
101}
102
103int mimimi_fbdev(void *(*start)(struct mimimi_engine *engine), void (*tick)(void *chapter, unsigned char left, unsigned char right))
104{
105	static char *file_names[] =
106	{
107		"/dev/input/event0", "/dev/input/event1", "/dev/input/event2", "/dev/input/event3",
108		"/dev/input/event4", "/dev/input/event5", "/dev/input/event6", "/dev/input/event7",
109		"/dev/input/event8", "/dev/input/event9", "/dev/input/event10", "/dev/input/event11",
110		"/dev/input/event12", "/dev/input/event13", "/dev/input/event14", "/dev/input/event15",
111		"/dev/input/event16", "/dev/input/event17", "/dev/input/event18", "/dev/input/event19",
112		"/dev/input/event20", "/dev/input/event21", "/dev/input/event22", "/dev/input/event23",
113		"/dev/input/event24", "/dev/input/event25", "/dev/input/event26", "/dev/input/event27",
114		"/dev/input/event28", "/dev/input/event29", "/dev/input/event30", "/dev/input/event31",
115	};
116	
117	void *chapter;
118	int fd, in_fd;
119	struct fb_fix_screeninfo finfo;
120	struct fb_var_screeninfo vinfo;
121	void *aux;
122	unsigned char *buffer;
123	int bytes_per_pixel;
124	unsigned int width, height;
125	struct mimimi_engine engine;
126	struct mimimi_fbdev data;
127	int in_count;
128	int in[32];
129	struct timeval past, tv;
130	struct timespec ts;
131	long int delay;
132	unsigned long int usec;
133	int i;
134	int nfds;
135	int available;
136	struct input_event event;
137	ssize_t size, r;
138	unsigned char left, right;
139	fd_set fds;
140	tcflag_t flag;
141	
142	fd = open("/dev/fb0", O_RDWR);
143	if (fd < 0) return 1;
144	if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) < 0) return 1;
145	if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) < 0) return 1;
146	
147	aux = malloc(finfo.smem_len);
148	if (aux == NULL) return 1;
149	
150	buffer = mmap(NULL, finfo.smem_len, PROT_WRITE, MAP_SHARED, fd, 0);
151	if ((intptr_t) buffer == -1) return 1;
152	
153	bytes_per_pixel = vinfo.bits_per_pixel / 8;
154	if (bytes_per_pixel != 4 && bytes_per_pixel != 3) return 1;
155	
156	width = 512;
157	height = 256;
158	
159	if (vinfo.xres < width) width = vinfo.xres;
160	if (vinfo.yres < height) height = vinfo.yres;
161	
162	tcgetattr(0, &mimimi_fbdev_tio);
163	flag = mimimi_fbdev_tio.c_lflag;
164	mimimi_fbdev_tio.c_lflag &= ~ICANON & ~ECHO;
165	
166	/* ioctl(0, KDSETMODE, KD_GRAPHICS); */
167	tcsetattr(0, TCSANOW, &mimimi_fbdev_tio);
168	mimimi_fbdev_tio.c_lflag = flag;
169	
170	atexit(&mimimi_fbdev_exit);
171	
172	data.buffer = aux;
173	data.width = width;
174	data.height = height;
175	data.x_offset = vinfo.xoffset;
176	data.y_offset = vinfo.yoffset;
177	data.line_length = finfo.line_length;
178	data.bytes_per_pixel = bytes_per_pixel;
179	
180	engine.data = &data;
181	engine.stamp = &mimimi_fbdev_stamp;
182	engine.texture = &mimimi_fbdev_texture;
183	engine.invalidate = &mimimi_fbdev_invalidate;
184	engine.size.width = width;
185	engine.size.height = height;
186	
187	for (in_count = 0 ; in_count < 32 ; in_count++)
188	{
189		in_fd = open(file_names[in_count], O_RDONLY);
190		if (in_fd < 0) break;
191		in[in_count] = in_fd;
192	}
193	
194	left = 0;
195	right = 0;
196	
197	past.tv_sec = 0;
198	past.tv_usec = 0;
199	
200	chapter = (*start)(&engine);
201	
202	for (;;)
203	{
204		clock_gettime(CLOCK_MONOTONIC, &ts);
205		tv.tv_sec = ts.tv_sec;
206		tv.tv_usec = ts.tv_nsec / 1000;
207		
208		delay =
209			(tv.tv_sec - past.tv_sec) * 1000000 +
210			(tv.tv_usec - past.tv_usec);
211		
212		if (delay < 0) delay = 0;
213		
214		past.tv_sec = tv.tv_sec;
215		past.tv_usec = tv.tv_usec;
216		
217		usec = 0;
218		if (delay < 33333) usec = 33333 - delay;
219		
220		tv.tv_sec = usec / 1000000;
221		tv.tv_usec = usec % 1000000;
222		
223		nfds = 0;
224		FD_ZERO(&fds);
225		for (i = 0 ; i < in_count ; i++)
226		{
227			if (nfds < in[i]) nfds = in[i];
228			FD_SET(in[i], &fds);
229		}
230		
231		for (;;)
232		{
233			available = select(nfds, &fds, NULL, NULL, &tv);
234			if (available == -1) return 1;
235			if (available == 0) break;
236			
237			tv.tv_sec = 0;
238			tv.tv_usec = 0;
239			
240			for (i = 0 ; i < in_count ; i++)
241			{
242				in_fd = in[i];
243				if (FD_ISSET(in_fd, &fds) == 0) continue;
244				
245				size = sizeof event;
246				r = read(in_fd, &event, size);
247				if (r < size) return 1;
248				
249				if (event.type != EV_KEY) continue;
250				if (event.value == 2) continue;
251				
252				switch (event.code)
253				{
254				case KEY_LEFT:
255				case KEY_A:
256					left = event.value;
257					break;
258				case KEY_RIGHT:
259				case KEY_D:
260					right = event.value;
261					break;
262				case KEY_Q:
263					if (event.value == 0) return 0;
264					break;
265				}
266			}
267		}
268		
269		(*tick)(chapter, left, right);
270		memcpy(buffer, aux, finfo.smem_len);
271	}
272}