Mercurial > repos > blastem
comparison render_fbdev.c @ 2053:3414a4423de1 segacd
Merge from default
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Sat, 15 Jan 2022 13:15:21 -0800 |
parents | 2fd0a8cb1c80 |
children |
comparison
equal
deleted
inserted
replaced
1692:5dacaef602a7 | 2053:3414a4423de1 |
---|---|
1 /* | |
2 Copyright 2013 Michael Pavone | |
3 This file is part of BlastEm. | |
4 BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. | |
5 */ | |
6 #include <stdlib.h> | |
7 #include <stdio.h> | |
8 #include <string.h> | |
9 #include <math.h> | |
10 #include <linux/fb.h> | |
11 #include <linux/input.h> | |
12 #include <linux/kd.h> | |
13 #include <alsa/asoundlib.h> | |
14 #include <sys/types.h> | |
15 #include <sys/stat.h> | |
16 #include <sys/ioctl.h> | |
17 #include <sys/mman.h> | |
18 #include <fcntl.h> | |
19 #include <unistd.h> | |
20 #include <pthread.h> | |
21 #include <dirent.h> | |
22 #include "render.h" | |
23 #include "blastem.h" | |
24 #include "genesis.h" | |
25 #include "bindings.h" | |
26 #include "util.h" | |
27 #include "paths.h" | |
28 #include "ppm.h" | |
29 #include "png.h" | |
30 #include "config.h" | |
31 #include "controller_info.h" | |
32 | |
33 #ifndef DISABLE_OPENGL | |
34 #include <EGL/egl.h> | |
35 #include <GLES2/gl2.h> | |
36 #ifdef USE_MALI | |
37 //Mali GLES headers don't seem to define GLchar for some reason | |
38 typedef char GLchar; | |
39 #endif | |
40 #endif | |
41 | |
42 #define MAX_EVENT_POLL_PER_FRAME 2 | |
43 | |
44 static EGLContext main_context; | |
45 | |
46 static int main_width, main_height, windowed_width, windowed_height, is_fullscreen; | |
47 | |
48 static uint8_t render_gl = 1; | |
49 static uint8_t scanlines = 0; | |
50 | |
51 static uint32_t last_frame = 0; | |
52 static snd_pcm_uframes_t buffer_samples; | |
53 static size_t buffer_bytes; | |
54 static unsigned int output_channels, sample_rate; | |
55 | |
56 | |
57 static uint8_t quitting = 0; | |
58 | |
59 | |
60 static void render_close_audio() | |
61 { | |
62 | |
63 } | |
64 | |
65 static snd_pcm_t *audio_handle; | |
66 static void *output_buffer; | |
67 void render_do_audio_ready(audio_source *src) | |
68 { | |
69 if (src->front_populated) { | |
70 fatal_error("Audio source filled up a buffer a second time before other sources finished their first\n"); | |
71 } | |
72 int16_t *tmp = src->front; | |
73 src->front = src->back; | |
74 src->back = tmp; | |
75 src->front_populated = 1; | |
76 src->buffer_pos = 0; | |
77 | |
78 if (!all_sources_ready()) { | |
79 return; | |
80 } | |
81 mix_and_convert(output_buffer, buffer_bytes, NULL); | |
82 | |
83 int frames = snd_pcm_writei(audio_handle, output_buffer, buffer_samples); | |
84 if (frames < 0) { | |
85 frames = snd_pcm_recover(audio_handle, frames, 0); | |
86 } | |
87 if (frames < 0) { | |
88 fprintf(stderr, "Failed to write samples: %s\n", snd_strerror(frames)); | |
89 } | |
90 } | |
91 | |
92 int render_width() | |
93 { | |
94 return main_width; | |
95 } | |
96 | |
97 int render_height() | |
98 { | |
99 return main_height; | |
100 } | |
101 | |
102 int render_fullscreen() | |
103 { | |
104 return 1; | |
105 } | |
106 | |
107 uint32_t red_shift, blue_shift, green_shift; | |
108 uint32_t render_map_color(uint8_t r, uint8_t g, uint8_t b) | |
109 { | |
110 return r << red_shift | g << green_shift | b << blue_shift; | |
111 } | |
112 | |
113 #ifndef DISABLE_OPENGL | |
114 static GLuint textures[3], buffers[2], vshader, fshader, program, un_textures[2], un_width, un_height, at_pos; | |
115 | |
116 static GLfloat vertex_data_default[] = { | |
117 -1.0f, -1.0f, | |
118 1.0f, -1.0f, | |
119 -1.0f, 1.0f, | |
120 1.0f, 1.0f | |
121 }; | |
122 | |
123 static GLfloat vertex_data[8]; | |
124 | |
125 static const GLushort element_data[] = {0, 1, 2, 3}; | |
126 | |
127 static const GLchar shader_prefix[] = | |
128 #ifdef USE_GLES | |
129 "#version 100\n"; | |
130 #else | |
131 "#version 110\n" | |
132 "#define lowp\n" | |
133 "#define mediump\n" | |
134 "#define highp\n"; | |
135 #endif | |
136 | |
137 static GLuint load_shader(char * fname, GLenum shader_type) | |
138 { | |
139 char const * parts[] = {get_home_dir(), "/.config/blastem/shaders/", fname}; | |
140 char * shader_path = alloc_concat_m(3, parts); | |
141 FILE * f = fopen(shader_path, "rb"); | |
142 free(shader_path); | |
143 GLchar * text; | |
144 long fsize; | |
145 if (f) { | |
146 fsize = file_size(f); | |
147 text = malloc(fsize); | |
148 if (fread(text, 1, fsize, f) != fsize) { | |
149 warning("Error reading from shader file %s\n", fname); | |
150 free(text); | |
151 return 0; | |
152 } | |
153 } else { | |
154 shader_path = path_append("shaders", fname); | |
155 uint32_t fsize32; | |
156 text = read_bundled_file(shader_path, &fsize32); | |
157 free(shader_path); | |
158 if (!text) { | |
159 warning("Failed to open shader file %s for reading\n", fname); | |
160 return 0; | |
161 } | |
162 fsize = fsize32; | |
163 } | |
164 | |
165 if (strncmp(text, "#version", strlen("#version"))) { | |
166 GLchar *tmp = text; | |
167 text = alloc_concat(shader_prefix, tmp); | |
168 free(tmp); | |
169 fsize += strlen(shader_prefix); | |
170 } | |
171 GLuint ret = glCreateShader(shader_type); | |
172 glShaderSource(ret, 1, (const GLchar **)&text, (const GLint *)&fsize); | |
173 free(text); | |
174 glCompileShader(ret); | |
175 GLint compile_status, loglen; | |
176 glGetShaderiv(ret, GL_COMPILE_STATUS, &compile_status); | |
177 if (!compile_status) { | |
178 glGetShaderiv(ret, GL_INFO_LOG_LENGTH, &loglen); | |
179 text = malloc(loglen); | |
180 glGetShaderInfoLog(ret, loglen, NULL, text); | |
181 warning("Shader %s failed to compile:\n%s\n", fname, text); | |
182 free(text); | |
183 glDeleteShader(ret); | |
184 return 0; | |
185 } | |
186 return ret; | |
187 } | |
188 #endif | |
189 | |
190 #define MAX_FB_LINES 590 | |
191 static uint32_t texture_buf[MAX_FB_LINES * LINEBUF_SIZE * 2]; | |
192 #ifdef DISABLE_OPENGL | |
193 #define RENDER_FORMAT SDL_PIXELFORMAT_ARGB8888 | |
194 #else | |
195 #ifdef USE_GLES | |
196 #define INTERNAL_FORMAT GL_RGBA | |
197 #define SRC_FORMAT GL_RGBA | |
198 #define RENDER_FORMAT SDL_PIXELFORMAT_ABGR8888 | |
199 #else | |
200 #define INTERNAL_FORMAT GL_RGBA8 | |
201 #define SRC_FORMAT GL_BGRA | |
202 #define RENDER_FORMAT SDL_PIXELFORMAT_ARGB8888 | |
203 #endif | |
204 static void gl_setup() | |
205 { | |
206 tern_val def = {.ptrval = "linear"}; | |
207 char *scaling = tern_find_path_default(config, "video\0scaling\0", def, TVAL_PTR).ptrval; | |
208 GLint filter = strcmp(scaling, "linear") ? GL_NEAREST : GL_LINEAR; | |
209 glGenTextures(3, textures); | |
210 for (int i = 0; i < 3; i++) | |
211 { | |
212 glBindTexture(GL_TEXTURE_2D, textures[i]); | |
213 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); | |
214 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); | |
215 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
216 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
217 if (i < 2) { | |
218 //TODO: Fixme for PAL + invalid display mode | |
219 glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, 512, 512, 0, SRC_FORMAT, GL_UNSIGNED_BYTE, texture_buf); | |
220 } else { | |
221 uint32_t blank = 255 << 24; | |
222 glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, 1, 1, 0, SRC_FORMAT, GL_UNSIGNED_BYTE, &blank); | |
223 } | |
224 } | |
225 glGenBuffers(2, buffers); | |
226 glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); | |
227 glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW); | |
228 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); | |
229 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(element_data), element_data, GL_STATIC_DRAW); | |
230 def.ptrval = "default.v.glsl"; | |
231 vshader = load_shader(tern_find_path_default(config, "video\0vertex_shader\0", def, TVAL_PTR).ptrval, GL_VERTEX_SHADER); | |
232 def.ptrval = "default.f.glsl"; | |
233 fshader = load_shader(tern_find_path_default(config, "video\0fragment_shader\0", def, TVAL_PTR).ptrval, GL_FRAGMENT_SHADER); | |
234 program = glCreateProgram(); | |
235 glAttachShader(program, vshader); | |
236 glAttachShader(program, fshader); | |
237 glLinkProgram(program); | |
238 GLint link_status; | |
239 glGetProgramiv(program, GL_LINK_STATUS, &link_status); | |
240 if (!link_status) { | |
241 fputs("Failed to link shader program\n", stderr); | |
242 exit(1); | |
243 } | |
244 un_textures[0] = glGetUniformLocation(program, "textures[0]"); | |
245 un_textures[1] = glGetUniformLocation(program, "textures[1]"); | |
246 un_width = glGetUniformLocation(program, "width"); | |
247 un_height = glGetUniformLocation(program, "height"); | |
248 at_pos = glGetAttribLocation(program, "pos"); | |
249 } | |
250 | |
251 static void gl_teardown() | |
252 { | |
253 glDeleteProgram(program); | |
254 glDeleteShader(vshader); | |
255 glDeleteShader(fshader); | |
256 glDeleteBuffers(2, buffers); | |
257 glDeleteTextures(3, textures); | |
258 } | |
259 #endif | |
260 | |
261 static uint8_t texture_init; | |
262 static void render_alloc_surfaces() | |
263 { | |
264 if (texture_init) { | |
265 return; | |
266 } | |
267 texture_init = 1; | |
268 #ifndef DISABLE_OPENGL | |
269 if (render_gl) { | |
270 gl_setup(); | |
271 } | |
272 #endif | |
273 } | |
274 | |
275 static void free_surfaces(void) | |
276 { | |
277 texture_init = 0; | |
278 } | |
279 | |
280 static char * caption = NULL; | |
281 static char * fps_caption = NULL; | |
282 | |
283 static void render_quit() | |
284 { | |
285 render_close_audio(); | |
286 free_surfaces(); | |
287 #ifndef DISABLE_OPENGL | |
288 if (render_gl) { | |
289 gl_teardown(); | |
290 //FIXME: replace with EGL equivalent | |
291 //SDL_GL_DeleteContext(main_context); | |
292 } | |
293 #endif | |
294 } | |
295 | |
296 static float config_aspect() | |
297 { | |
298 static float aspect = 0.0f; | |
299 if (aspect == 0.0f) { | |
300 char *config_aspect = tern_find_path_default(config, "video\0aspect\0", (tern_val){.ptrval = "4:3"}, TVAL_PTR).ptrval; | |
301 if (strcmp("stretch", config_aspect)) { | |
302 aspect = 4.0f/3.0f; | |
303 char *end; | |
304 float aspect_numerator = strtof(config_aspect, &end); | |
305 if (aspect_numerator > 0.0f && *end == ':') { | |
306 float aspect_denominator = strtof(end+1, &end); | |
307 if (aspect_denominator > 0.0f && !*end) { | |
308 aspect = aspect_numerator / aspect_denominator; | |
309 } | |
310 } | |
311 } else { | |
312 aspect = -1.0f; | |
313 } | |
314 } | |
315 return aspect; | |
316 } | |
317 | |
318 static void update_aspect() | |
319 { | |
320 //reset default values | |
321 #ifndef DISABLE_OPENGL | |
322 memcpy(vertex_data, vertex_data_default, sizeof(vertex_data)); | |
323 #endif | |
324 if (config_aspect() > 0.0f) { | |
325 float aspect = (float)main_width / main_height; | |
326 if (fabs(aspect - config_aspect()) < 0.01f) { | |
327 //close enough for government work | |
328 return; | |
329 } | |
330 #ifndef DISABLE_OPENGL | |
331 if (render_gl) { | |
332 for (int i = 0; i < 4; i++) | |
333 { | |
334 if (aspect > config_aspect()) { | |
335 vertex_data[i*2] *= config_aspect()/aspect; | |
336 } else { | |
337 vertex_data[i*2+1] *= aspect/config_aspect(); | |
338 } | |
339 } | |
340 } else { | |
341 #endif | |
342 //TODO: Maybe do some stuff for non-integer scaling in raw fbdev copy | |
343 #ifndef DISABLE_OPENGL | |
344 } | |
345 #endif | |
346 } | |
347 } | |
348 | |
349 static uint8_t scancode_map[128] = { | |
350 [KEY_A] = 0x1C, | |
351 [KEY_B] = 0x32, | |
352 [KEY_C] = 0x21, | |
353 [KEY_D] = 0x23, | |
354 [KEY_E] = 0x24, | |
355 [KEY_F] = 0x2B, | |
356 [KEY_G] = 0x34, | |
357 [KEY_H] = 0x33, | |
358 [KEY_I] = 0x43, | |
359 [KEY_J] = 0x3B, | |
360 [KEY_K] = 0x42, | |
361 [KEY_L] = 0x4B, | |
362 [KEY_M] = 0x3A, | |
363 [KEY_N] = 0x31, | |
364 [KEY_O] = 0x44, | |
365 [KEY_P] = 0x4D, | |
366 [KEY_Q] = 0x15, | |
367 [KEY_R] = 0x2D, | |
368 [KEY_S] = 0x1B, | |
369 [KEY_T] = 0x2C, | |
370 [KEY_U] = 0x3C, | |
371 [KEY_V] = 0x2A, | |
372 [KEY_W] = 0x1D, | |
373 [KEY_X] = 0x22, | |
374 [KEY_Y] = 0x35, | |
375 [KEY_Z] = 0x1A, | |
376 [KEY_1] = 0x16, | |
377 [KEY_2] = 0x1E, | |
378 [KEY_3] = 0x26, | |
379 [KEY_4] = 0x25, | |
380 [KEY_5] = 0x2E, | |
381 [KEY_6] = 0x36, | |
382 [KEY_7] = 0x3D, | |
383 [KEY_8] = 0x3E, | |
384 [KEY_9] = 0x46, | |
385 [KEY_0] = 0x45, | |
386 [KEY_ENTER] = 0x5A, | |
387 [KEY_ESC] = 0x76, | |
388 [KEY_SPACE] = 0x29, | |
389 [KEY_TAB] = 0x0D, | |
390 [KEY_BACKSPACE] = 0x66, | |
391 [KEY_MINUS] = 0x4E, | |
392 [KEY_EQUAL] = 0x55, | |
393 [KEY_LEFTBRACE] = 0x54, | |
394 [KEY_RIGHTBRACE] = 0x5B, | |
395 [KEY_BACKSLASH] = 0x5D, | |
396 [KEY_SEMICOLON] = 0x4C, | |
397 [KEY_APOSTROPHE] = 0x52, | |
398 [KEY_GRAVE] = 0x0E, | |
399 [KEY_COMMA] = 0x41, | |
400 [KEY_DOT] = 0x49, | |
401 [KEY_SLASH] = 0x4A, | |
402 [KEY_CAPSLOCK] = 0x58, | |
403 [KEY_F1] = 0x05, | |
404 [KEY_F2] = 0x06, | |
405 [KEY_F3] = 0x04, | |
406 [KEY_F4] = 0x0C, | |
407 [KEY_F5] = 0x03, | |
408 [KEY_F6] = 0x0B, | |
409 [KEY_F7] = 0x83, | |
410 [KEY_F8] = 0x0A, | |
411 [KEY_F9] = 0x01, | |
412 [KEY_F10] = 0x09, | |
413 [KEY_F11] = 0x78, | |
414 [KEY_F12] = 0x07, | |
415 [KEY_LEFTCTRL] = 0x14, | |
416 [KEY_LEFTSHIFT] = 0x12, | |
417 [KEY_LEFTALT] = 0x11, | |
418 [KEY_RIGHTCTRL] = 0x18, | |
419 [KEY_RIGHTSHIFT] = 0x59, | |
420 [KEY_RIGHTALT] = 0x17, | |
421 [KEY_INSERT] = 0x81, | |
422 [KEY_PAUSE] = 0x82, | |
423 [KEY_SYSRQ] = 0x84, | |
424 [KEY_SCROLLLOCK] = 0x7E, | |
425 [KEY_DELETE] = 0x85, | |
426 [KEY_LEFT] = 0x86, | |
427 [KEY_HOME] = 0x87, | |
428 [KEY_END] = 0x88, | |
429 [KEY_UP] = 0x89, | |
430 [KEY_DOWN] = 0x8A, | |
431 [KEY_PAGEUP] = 0x8B, | |
432 [KEY_PAGEDOWN] = 0x8C, | |
433 [KEY_RIGHT] = 0x8D, | |
434 [KEY_NUMLOCK] = 0x77, | |
435 [KEY_KPSLASH] = 0x80, | |
436 [KEY_KPASTERISK] = 0x7C, | |
437 [KEY_KPMINUS] = 0x7B, | |
438 [KEY_KPPLUS] = 0x79, | |
439 [KEY_KPENTER] = 0x19, | |
440 [KEY_KP1] = 0x69, | |
441 [KEY_KP2] = 0x72, | |
442 [KEY_KP3] = 0x7A, | |
443 [KEY_KP4] = 0x6B, | |
444 [KEY_KP5] = 0x73, | |
445 [KEY_KP6] = 0x74, | |
446 [KEY_KP7] = 0x6C, | |
447 [KEY_KP8] = 0x75, | |
448 [KEY_KP9] = 0x7D, | |
449 [KEY_KP0] = 0x70, | |
450 [KEY_KPDOT] = 0x71, | |
451 }; | |
452 | |
453 #include "special_keys_evdev.h" | |
454 static uint8_t sym_map[128] = { | |
455 [KEY_A] = 'a', | |
456 [KEY_B] = 'b', | |
457 [KEY_C] = 'c', | |
458 [KEY_D] = 'd', | |
459 [KEY_E] = 'e', | |
460 [KEY_F] = 'f', | |
461 [KEY_G] = 'g', | |
462 [KEY_H] = 'h', | |
463 [KEY_I] = 'i', | |
464 [KEY_J] = 'j', | |
465 [KEY_K] = 'k', | |
466 [KEY_L] = 'l', | |
467 [KEY_M] = 'm', | |
468 [KEY_N] = 'n', | |
469 [KEY_O] = 'o', | |
470 [KEY_P] = 'p', | |
471 [KEY_Q] = 'q', | |
472 [KEY_R] = 'r', | |
473 [KEY_S] = 's', | |
474 [KEY_T] = 't', | |
475 [KEY_U] = 'u', | |
476 [KEY_V] = 'v', | |
477 [KEY_W] = 'w', | |
478 [KEY_X] = 'x', | |
479 [KEY_Y] = 'y', | |
480 [KEY_Z] = 'z', | |
481 [KEY_1] = '1', | |
482 [KEY_2] = '2', | |
483 [KEY_3] = '3', | |
484 [KEY_4] = '4', | |
485 [KEY_5] = '5', | |
486 [KEY_6] = '6', | |
487 [KEY_7] = '7', | |
488 [KEY_8] = '8', | |
489 [KEY_9] = '9', | |
490 [KEY_0] = '0', | |
491 [KEY_ENTER] = '\r', | |
492 [KEY_SPACE] = ' ', | |
493 [KEY_TAB] = '\t', | |
494 [KEY_BACKSPACE] = '\b', | |
495 [KEY_MINUS] = '-', | |
496 [KEY_EQUAL] = '=', | |
497 [KEY_LEFTBRACE] = '[', | |
498 [KEY_RIGHTBRACE] = ']', | |
499 [KEY_BACKSLASH] = '\\', | |
500 [KEY_SEMICOLON] = ';', | |
501 [KEY_APOSTROPHE] = '\'', | |
502 [KEY_GRAVE] = '`', | |
503 [KEY_COMMA] = ',', | |
504 [KEY_DOT] = '.', | |
505 [KEY_SLASH] = '/', | |
506 [KEY_ESC] = RENDERKEY_ESC, | |
507 [KEY_F1] = RENDERKEY_F1, | |
508 [KEY_F2] = RENDERKEY_F2, | |
509 [KEY_F3] = RENDERKEY_F3, | |
510 [KEY_F4] = RENDERKEY_F4, | |
511 [KEY_F5] = RENDERKEY_F5, | |
512 [KEY_F6] = RENDERKEY_F6, | |
513 [KEY_F7] = RENDERKEY_F7, | |
514 [KEY_F8] = RENDERKEY_F8, | |
515 [KEY_F9] = RENDERKEY_F9, | |
516 [KEY_F10] = RENDERKEY_F10, | |
517 [KEY_F11] = RENDERKEY_F11, | |
518 [KEY_F12] = RENDERKEY_F12, | |
519 [KEY_LEFTCTRL] = RENDERKEY_LCTRL, | |
520 [KEY_LEFTSHIFT] = RENDERKEY_LSHIFT, | |
521 [KEY_LEFTALT] = RENDERKEY_LALT, | |
522 [KEY_RIGHTCTRL] = RENDERKEY_RCTRL, | |
523 [KEY_RIGHTSHIFT] = RENDERKEY_RSHIFT, | |
524 [KEY_RIGHTALT] = RENDERKEY_RALT, | |
525 [KEY_DELETE] = RENDERKEY_DEL, | |
526 [KEY_LEFT] = RENDERKEY_LEFT, | |
527 [KEY_HOME] = RENDERKEY_HOME, | |
528 [KEY_END] = RENDERKEY_END, | |
529 [KEY_UP] = RENDERKEY_UP, | |
530 [KEY_DOWN] = RENDERKEY_DOWN, | |
531 [KEY_PAGEUP] = RENDERKEY_PAGEUP, | |
532 [KEY_PAGEDOWN] = RENDERKEY_PAGEDOWN, | |
533 [KEY_RIGHT] = RENDERKEY_RIGHT, | |
534 [KEY_KPSLASH] = 0x80, | |
535 [KEY_KPASTERISK] = 0x7C, | |
536 [KEY_KPMINUS] = 0x7B, | |
537 [KEY_KPPLUS] = 0x79, | |
538 [KEY_KPENTER] = 0x19, | |
539 [KEY_KP1] = 0x69, | |
540 [KEY_KP2] = 0x72, | |
541 [KEY_KP3] = 0x7A, | |
542 [KEY_KP4] = 0x6B, | |
543 [KEY_KP5] = 0x73, | |
544 [KEY_KP6] = 0x74, | |
545 [KEY_KP7] = 0x6C, | |
546 [KEY_KP8] = 0x75, | |
547 [KEY_KP9] = 0x7D, | |
548 [KEY_KP0] = 0x70, | |
549 [KEY_KPDOT] = 0x71, | |
550 }; | |
551 | |
552 static drop_handler drag_drop_handler; | |
553 void render_set_drag_drop_handler(drop_handler handler) | |
554 { | |
555 drag_drop_handler = handler; | |
556 } | |
557 | |
558 char* render_joystick_type_id(int index) | |
559 { | |
560 return strdup(""); | |
561 } | |
562 | |
563 static uint32_t overscan_top[NUM_VID_STD] = {2, 21}; | |
564 static uint32_t overscan_bot[NUM_VID_STD] = {1, 17}; | |
565 static uint32_t overscan_left[NUM_VID_STD] = {13, 13}; | |
566 static uint32_t overscan_right[NUM_VID_STD] = {14, 14}; | |
567 static vid_std video_standard = VID_NTSC; | |
568 | |
569 typedef enum { | |
570 DEV_NONE, | |
571 DEV_KEYBOARD, | |
572 DEV_MOUSE, | |
573 DEV_GAMEPAD | |
574 } device_type; | |
575 | |
576 static int32_t mouse_x, mouse_y, mouse_accum_x, mouse_accum_y; | |
577 static int32_t handle_event(device_type dtype, int device_index, struct input_event *event) | |
578 { | |
579 switch (event->type) { | |
580 case EV_KEY: | |
581 //code is key, value is 1 for keydown, 0 for keyup | |
582 if (dtype == DEV_KEYBOARD && event->code < 128) { | |
583 //keyboard key that we might have a mapping for | |
584 if (event->value) { | |
585 handle_keydown(sym_map[event->code], scancode_map[event->code]); | |
586 } else { | |
587 handle_keyup(sym_map[event->code], scancode_map[event->code]); | |
588 } | |
589 } else if (dtype == DEV_MOUSE && event->code >= BTN_MOUSE && event->code < BTN_JOYSTICK) { | |
590 //mosue button | |
591 if (event->value) { | |
592 handle_mousedown(device_index, event->code - BTN_LEFT); | |
593 } else { | |
594 handle_mouseup(device_index, event->code - BTN_LEFT); | |
595 } | |
596 } else if (dtype == DEV_GAMEPAD && event->code >= BTN_GAMEPAD && event->code < BTN_DIGI) { | |
597 //gamepad button | |
598 if (event->value) { | |
599 handle_joydown(device_index, event->code - BTN_SOUTH); | |
600 } else { | |
601 handle_joyup(device_index, event->code - BTN_SOUTH); | |
602 } | |
603 } | |
604 break; | |
605 case EV_REL: | |
606 if (dtype == DEV_MOUSE) { | |
607 switch(event->code) | |
608 { | |
609 case REL_X: | |
610 mouse_accum_x += event->value; | |
611 break; | |
612 case REL_Y: | |
613 mouse_accum_y += event->value; | |
614 break; | |
615 } | |
616 } | |
617 break; | |
618 case EV_ABS: | |
619 //TODO: Handle joystick axis/hat motion, absolute mouse movement | |
620 break; | |
621 case EV_SYN: | |
622 if (dtype == DEV_MOUSE && (mouse_accum_x || mouse_accum_y)) { | |
623 mouse_x += mouse_accum_x; | |
624 mouse_y += mouse_accum_y; | |
625 if (mouse_x < 0) { | |
626 mouse_x = 0; | |
627 } else if (mouse_x >= main_width) { | |
628 mouse_x = main_width - 1; | |
629 } | |
630 if (mouse_y < 0) { | |
631 mouse_y = 0; | |
632 } else if (mouse_y >= main_height) { | |
633 mouse_y = main_height - 1; | |
634 } | |
635 handle_mouse_moved(device_index, mouse_x, mouse_y, mouse_accum_x, mouse_accum_y); | |
636 mouse_accum_x = mouse_accum_y = 0; | |
637 } | |
638 break; | |
639 /* | |
640 case SDL_JOYHATMOTION: | |
641 handle_joy_dpad(find_joystick_index(event->jhat.which), event->jhat.hat, event->jhat.value); | |
642 break; | |
643 case SDL_JOYAXISMOTION: | |
644 handle_joy_axis(find_joystick_index(event->jaxis.which), event->jaxis.axis, event->jaxis.value); | |
645 break;*/ | |
646 } | |
647 return 0; | |
648 } | |
649 | |
650 #define MAX_DEVICES 16 | |
651 static int device_fds[MAX_DEVICES]; | |
652 static device_type device_types[MAX_DEVICES]; | |
653 static int cur_devices; | |
654 | |
655 static void drain_events() | |
656 { | |
657 struct input_event events[64]; | |
658 int index_by_type[3] = {0,0,0}; | |
659 for (int i = 0; i < cur_devices; i++) | |
660 { | |
661 int bytes = sizeof(events); | |
662 int device_index = index_by_type[device_types[i]-1]++; | |
663 while (bytes == sizeof(events)) | |
664 { | |
665 bytes = read(device_fds[i], events, sizeof(events)); | |
666 if (bytes > 0) { | |
667 int num_events = bytes / sizeof(events[0]); | |
668 for (int j = 0; j < num_events; j++) | |
669 { | |
670 handle_event(device_types[i], device_index, events + j); | |
671 } | |
672 } else if (bytes < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { | |
673 perror("Failed to read evdev events"); | |
674 } | |
675 } | |
676 } | |
677 } | |
678 | |
679 static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"}; | |
680 | |
681 static void init_audio() | |
682 { | |
683 char *device_name = tern_find_path_default(config, "audio\0alsa_device\0", (tern_val){.ptrval="default"}, TVAL_PTR).ptrval; | |
684 int res = snd_pcm_open(&audio_handle, device_name, SND_PCM_STREAM_PLAYBACK, 0); | |
685 if (res < 0) { | |
686 fatal_error("Failed to open ALSA device: %s\n", snd_strerror(res)); | |
687 } | |
688 | |
689 snd_pcm_hw_params_t *params; | |
690 snd_pcm_hw_params_alloca(¶ms); | |
691 res = snd_pcm_hw_params_any(audio_handle, params); | |
692 if (res < 0) { | |
693 fatal_error("No playback configurations available: %s\n", snd_strerror(res)); | |
694 } | |
695 res = snd_pcm_hw_params_set_access(audio_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); | |
696 if (res < 0) { | |
697 fatal_error("Failed to set access type: %s\n", snd_strerror(res)); | |
698 } | |
699 res = snd_pcm_hw_params_set_format(audio_handle, params, SND_PCM_FORMAT_S16_LE); | |
700 if (res < 0) { | |
701 //failed to set, signed 16-bit integer, try floating point | |
702 res = snd_pcm_hw_params_set_format(audio_handle, params, SND_PCM_FORMAT_FLOAT_LE); | |
703 if (res < 0) { | |
704 fatal_error("Failed to set an acceptable format: %s\n", snd_strerror(res)); | |
705 } | |
706 mix = mix_f32; | |
707 } else { | |
708 mix = mix_s16; | |
709 } | |
710 | |
711 char * rate_str = tern_find_path(config, "audio\0rate\0", TVAL_PTR).ptrval; | |
712 sample_rate = rate_str ? atoi(rate_str) : 0; | |
713 if (!sample_rate) { | |
714 sample_rate = 48000; | |
715 } | |
716 snd_pcm_hw_params_set_rate_near(audio_handle, params, &sample_rate, NULL); | |
717 output_channels = 2; | |
718 snd_pcm_hw_params_set_channels_near(audio_handle, params, &output_channels); | |
719 | |
720 char * samples_str = tern_find_path(config, "audio\0buffer\0", TVAL_PTR).ptrval; | |
721 buffer_samples = samples_str ? atoi(samples_str) : 0; | |
722 if (!buffer_samples) { | |
723 buffer_samples = 512; | |
724 } | |
725 snd_pcm_hw_params_set_period_size_near(audio_handle, params, &buffer_samples, NULL); | |
726 | |
727 int dir = 1; | |
728 unsigned int periods = 2; | |
729 snd_pcm_hw_params_set_periods_near(audio_handle, params, &periods, &dir); | |
730 | |
731 res = snd_pcm_hw_params(audio_handle, params); | |
732 if (res < 0) { | |
733 fatal_error("Failed to set ALSA hardware params: %s\n", snd_strerror(res)); | |
734 } | |
735 | |
736 printf("Initialized audio at frequency %d with a %d sample buffer, ", (int)sample_rate, (int)buffer_samples); | |
737 if (mix == mix_s16) { | |
738 puts("signed 16-bit int format"); | |
739 } else { | |
740 puts("32-bit float format"); | |
741 } | |
742 } | |
743 | |
744 int fbfd; | |
745 uint32_t *framebuffer; | |
746 uint32_t fb_stride; | |
747 #ifndef DISABLE_OPENGL | |
748 EGLDisplay egl_display; | |
749 EGLSurface main_surface; | |
750 uint8_t egl_setup(void) | |
751 { | |
752 //Mesa wants the fbdev file descriptor as the display | |
753 egl_display = eglGetDisplay((EGLNativeDisplayType)fbfd); | |
754 if (egl_display == EGL_NO_DISPLAY) { | |
755 //Mali (and possibly others) seems to just want EGL_DEFAULT_DISPLAY | |
756 egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); | |
757 if (egl_display == EGL_NO_DISPLAY) { | |
758 warning("eglGetDisplay failed with error %X\n", eglGetError()); | |
759 return 0; | |
760 } | |
761 } | |
762 EGLint major, minor; | |
763 if (!eglInitialize(egl_display, &major, &minor)) { | |
764 warning("eglInitialize failed with error %X\n", eglGetError()); | |
765 return 0; | |
766 } | |
767 printf("EGL version %d.%d\n", major, minor); | |
768 EGLint num_configs; | |
769 EGLConfig config; | |
770 EGLint const config_attribs[] = { | |
771 EGL_RED_SIZE, 5, | |
772 EGL_GREEN_SIZE, 5, | |
773 EGL_BLUE_SIZE, 5, | |
774 EGL_CONFORMANT, EGL_OPENGL_ES2_BIT, | |
775 EGL_NONE | |
776 }; | |
777 if (!eglChooseConfig(egl_display, config_attribs, &config, 1, &num_configs)) { | |
778 num_configs = 0; | |
779 warning("eglChooseConfig failed with error %X\n", eglGetError()); | |
780 } | |
781 if (!num_configs) { | |
782 warning("Failed to choose an EGL config\n"); | |
783 goto error; | |
784 } | |
785 EGLint const context_attribs[] = { | |
786 #ifdef EGL_CONTEXT_MAJOR_VERSION | |
787 EGL_CONTEXT_MAJOR_VERSION, 2, | |
788 #endif | |
789 EGL_NONE | |
790 }; | |
791 main_context = eglCreateContext(egl_display, config, EGL_NO_CONTEXT, context_attribs); | |
792 if (main_context == EGL_NO_CONTEXT) { | |
793 warning("Failed to create EGL context %X\n", eglGetError()); | |
794 goto error; | |
795 } | |
796 #ifdef USE_MALI | |
797 struct mali_native_window native_window = { | |
798 .width = main_width, | |
799 .height = main_height | |
800 }; | |
801 main_surface = eglCreateWindowSurface(egl_display, config, &native_window, NULL); | |
802 #else | |
803 main_surface = eglCreateWindowSurface(egl_display, config, (EGLNativeWindowType)NULL, NULL); | |
804 #endif | |
805 if (main_surface == EGL_NO_SURFACE) { | |
806 warning("Failed to create EGL surface %X\n", eglGetError()); | |
807 goto post_context_error; | |
808 } | |
809 if (eglMakeCurrent(egl_display, main_surface, main_surface, main_context)) { | |
810 return 1; | |
811 } | |
812 eglDestroySurface(egl_display, main_surface); | |
813 post_context_error: | |
814 eglDestroyContext(egl_display, main_context); | |
815 error: | |
816 eglTerminate(egl_display); | |
817 return 0; | |
818 } | |
819 #endif | |
820 static pthread_mutex_t buffer_lock = PTHREAD_MUTEX_INITIALIZER; | |
821 static pthread_cond_t buffer_cond = PTHREAD_COND_INITIALIZER; | |
822 static uint8_t buffer_ready; | |
823 static uint32_t *copy_buffer; | |
824 static uint32_t last_width, last_width_scale, last_height, last_height_scale; | |
825 static uint32_t max_multiple; | |
826 | |
827 static uint32_t mix_pixel(uint32_t last, uint32_t cur, float ratio) | |
828 { | |
829 float a,b,c,d; | |
830 a = (last & 255) * ratio; | |
831 b = (last >> 8 & 255) * ratio; | |
832 c = (last >> 16 & 255) * ratio; | |
833 d = (last >> 24 & 255) * ratio; | |
834 ratio = 1.0f - ratio; | |
835 a += (cur & 255) * ratio; | |
836 b += (cur >> 8 & 255) * ratio; | |
837 c += (cur >> 16 & 255) * ratio; | |
838 d += (cur >> 24 & 255) * ratio; | |
839 return ((int)d) << 24 | ((int)c) << 16 | ((int)b) << 8 | ((int)a); | |
840 } | |
841 static void do_buffer_copy(void) | |
842 { | |
843 uint32_t width_multiple = main_width / last_width_scale; | |
844 uint32_t height_multiple = main_height / last_height_scale; | |
845 uint32_t multiple = width_multiple < height_multiple ? width_multiple : height_multiple; | |
846 if (max_multiple && multiple > max_multiple) { | |
847 multiple = max_multiple; | |
848 } | |
849 height_multiple = last_height_scale * multiple / last_height; | |
850 uint32_t *cur_line = framebuffer + (main_width - last_width_scale * multiple)/2; | |
851 cur_line += fb_stride * (main_height - last_height_scale * multiple) / (2 * sizeof(uint32_t)); | |
852 uint32_t *src_line = copy_buffer; | |
853 if (height_multiple * last_height == multiple * last_height_scale) { | |
854 if (last_width == last_width_scale) { | |
855 for (uint32_t y = 0; y < last_height; y++) | |
856 { | |
857 for (uint32_t i = 0; i < height_multiple; i++) | |
858 { | |
859 uint32_t *cur = cur_line; | |
860 uint32_t *src = src_line; | |
861 for (uint32_t x = 0; x < last_width ; x++) | |
862 { | |
863 uint32_t pixel = *(src++); | |
864 for (uint32_t j = 0; j < multiple; j++) | |
865 { | |
866 *(cur++) = pixel; | |
867 } | |
868 } | |
869 | |
870 cur_line += fb_stride / sizeof(uint32_t); | |
871 } | |
872 src_line += LINEBUF_SIZE; | |
873 } | |
874 } else { | |
875 float scale_multiple = ((float)(last_width_scale * multiple)) / (float)last_width; | |
876 float remaining = 0.0f; | |
877 uint32_t last_pixel = 0; | |
878 for (uint32_t y = 0; y < last_height; y++) | |
879 { | |
880 for (uint32_t i = 0; i < height_multiple; i++) | |
881 { | |
882 uint32_t *cur = cur_line; | |
883 uint32_t *src = src_line; | |
884 for (uint32_t x = 0; x < last_width ; x++) | |
885 { | |
886 uint32_t pixel = *(src++); | |
887 float count = scale_multiple; | |
888 if (remaining > 0.0f) { | |
889 *(cur++) = mix_pixel(last_pixel, pixel, remaining); | |
890 count -= 1.0f - remaining; | |
891 } | |
892 for (; count >= 1; count -= 1.0f) | |
893 { | |
894 *(cur++) = pixel; | |
895 } | |
896 remaining = count; | |
897 last_pixel = pixel; | |
898 } | |
899 | |
900 cur_line += fb_stride / sizeof(uint32_t); | |
901 } | |
902 src_line += LINEBUF_SIZE; | |
903 } | |
904 } | |
905 } else { | |
906 float height_scale = ((float)(last_height_scale * multiple)) / (float)last_height; | |
907 float height_remaining = 0.0f; | |
908 uint32_t *last_line; | |
909 if (last_width == last_width_scale) { | |
910 for (uint32_t y = 0; y < last_height; y++) | |
911 { | |
912 float hcount = height_scale; | |
913 if (height_remaining > 0.0f) { | |
914 uint32_t *cur = cur_line; | |
915 uint32_t *src = src_line; | |
916 uint32_t *last = last_line; | |
917 for (uint32_t x = 0; x < last_width ; x++) | |
918 { | |
919 uint32_t mixed = mix_pixel(*(last++), *(src++), height_remaining); | |
920 for (uint32_t j = 0; j < multiple; j++) | |
921 { | |
922 *(cur++) = mixed; | |
923 } | |
924 } | |
925 hcount -= 1.0f - height_remaining; | |
926 cur_line += fb_stride / sizeof(uint32_t); | |
927 } | |
928 for(; hcount >= 1; hcount -= 1.0f) | |
929 { | |
930 uint32_t *cur = cur_line; | |
931 uint32_t *src = src_line; | |
932 for (uint32_t x = 0; x < last_width ; x++) | |
933 { | |
934 uint32_t pixel = *(src++); | |
935 for (uint32_t j = 0; j < multiple; j++) | |
936 { | |
937 *(cur++) = pixel; | |
938 } | |
939 } | |
940 | |
941 cur_line += fb_stride / sizeof(uint32_t); | |
942 } | |
943 height_remaining = hcount; | |
944 last_line = src_line; | |
945 src_line += LINEBUF_SIZE; | |
946 } | |
947 } else { | |
948 float scale_multiple = ((float)(last_width_scale * multiple)) / (float)last_width; | |
949 float remaining = 0.0f; | |
950 uint32_t last_pixel = 0; | |
951 for (uint32_t y = 0; y < last_height; y++) | |
952 { | |
953 float hcount = height_scale; | |
954 if (height_remaining > 0.0f) { | |
955 uint32_t *cur = cur_line; | |
956 uint32_t *src = src_line; | |
957 uint32_t *last = last_line; | |
958 | |
959 for (uint32_t x = 0; x < last_width; x++) | |
960 { | |
961 uint32_t pixel = mix_pixel(*(last++), *(src++), height_remaining); | |
962 float count = scale_multiple; | |
963 if (remaining > 0.0f) { | |
964 *(cur++) = mix_pixel(last_pixel, pixel, remaining); | |
965 count -= 1.0f - remaining; | |
966 } | |
967 for (; count >= 1.0f; count -= 1.0f) | |
968 { | |
969 *(cur++) = pixel; | |
970 } | |
971 remaining = count; | |
972 last_pixel = pixel; | |
973 } | |
974 hcount -= 1.0f - height_remaining; | |
975 cur_line += fb_stride / sizeof(uint32_t); | |
976 } | |
977 | |
978 for (; hcount >= 1.0f; hcount -= 1.0f) | |
979 { | |
980 uint32_t *cur = cur_line; | |
981 uint32_t *src = src_line; | |
982 for (uint32_t x = 0; x < last_width ; x++) | |
983 { | |
984 uint32_t pixel = *(src++); | |
985 float count = scale_multiple; | |
986 if (remaining > 0.0f) { | |
987 *(cur++) = mix_pixel(last_pixel, pixel, remaining); | |
988 count -= 1.0f - remaining; | |
989 } | |
990 for (; count >= 1; count -= 1.0f) | |
991 { | |
992 *(cur++) = pixel; | |
993 } | |
994 remaining = count; | |
995 last_pixel = pixel; | |
996 } | |
997 | |
998 cur_line += fb_stride / sizeof(uint32_t); | |
999 } | |
1000 height_remaining = hcount; | |
1001 last_line = src_line; | |
1002 src_line += LINEBUF_SIZE; | |
1003 } | |
1004 } | |
1005 } | |
1006 } | |
1007 static void *buffer_copy(void *data) | |
1008 { | |
1009 pthread_mutex_lock(&buffer_lock); | |
1010 for(;;) | |
1011 { | |
1012 while (!buffer_ready) | |
1013 { | |
1014 pthread_cond_wait(&buffer_cond, &buffer_lock); | |
1015 } | |
1016 buffer_ready = 0; | |
1017 do_buffer_copy(); | |
1018 } | |
1019 return 0; | |
1020 } | |
1021 | |
1022 static pthread_t buffer_copy_handle; | |
1023 static uint8_t copy_use_thread; | |
1024 void window_setup(void) | |
1025 { | |
1026 fbfd = open("/dev/fb0", O_RDWR); | |
1027 struct fb_fix_screeninfo fixInfo; | |
1028 struct fb_var_screeninfo varInfo; | |
1029 ioctl(fbfd, FBIOGET_FSCREENINFO, &fixInfo); | |
1030 ioctl(fbfd, FBIOGET_VSCREENINFO, &varInfo); | |
1031 printf("Resolution: %d x %d\n", varInfo.xres, varInfo.yres); | |
1032 printf("Framebuffer size: %d, line stride: %d\n", fixInfo.smem_len, fixInfo.line_length); | |
1033 main_width = varInfo.xres; | |
1034 main_height = varInfo.yres; | |
1035 fb_stride = fixInfo.line_length; | |
1036 tern_val def = {.ptrval = "audio"}; | |
1037 char *sync_src = tern_find_path_default(config, "system\0sync_source\0", def, TVAL_PTR).ptrval; | |
1038 | |
1039 const char *vsync; | |
1040 def.ptrval = "off"; | |
1041 vsync = tern_find_path_default(config, "video\0vsync\0", def, TVAL_PTR).ptrval; | |
1042 | |
1043 | |
1044 tern_node *video = tern_find_node(config, "video"); | |
1045 if (video) | |
1046 { | |
1047 for (int i = 0; i < NUM_VID_STD; i++) | |
1048 { | |
1049 tern_node *std_settings = tern_find_node(video, vid_std_names[i]); | |
1050 if (std_settings) { | |
1051 char *val = tern_find_path_default(std_settings, "overscan\0top\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
1052 if (val) { | |
1053 overscan_top[i] = atoi(val); | |
1054 } | |
1055 val = tern_find_path_default(std_settings, "overscan\0bottom\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
1056 if (val) { | |
1057 overscan_bot[i] = atoi(val); | |
1058 } | |
1059 val = tern_find_path_default(std_settings, "overscan\0left\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
1060 if (val) { | |
1061 overscan_left[i] = atoi(val); | |
1062 } | |
1063 val = tern_find_path_default(std_settings, "overscan\0right\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
1064 if (val) { | |
1065 overscan_right[i] = atoi(val); | |
1066 } | |
1067 } | |
1068 } | |
1069 } | |
1070 render_gl = 0; | |
1071 #ifndef DISABLE_OPENGL | |
1072 char *gl_enabled_str = tern_find_path_default(config, "video\0gl\0", def, TVAL_PTR).ptrval; | |
1073 uint8_t gl_enabled = strcmp(gl_enabled_str, "off") != 0; | |
1074 if (gl_enabled) | |
1075 { | |
1076 render_gl = egl_setup(); | |
1077 blue_shift = 16; | |
1078 green_shift = 8; | |
1079 red_shift = 0; | |
1080 } | |
1081 if (!render_gl) { | |
1082 #endif | |
1083 framebuffer = mmap(NULL, fixInfo.smem_len, PROT_READ|PROT_WRITE, MAP_SHARED, fbfd, 0); | |
1084 red_shift = varInfo.red.offset; | |
1085 green_shift = varInfo.green.offset; | |
1086 blue_shift = varInfo.blue.offset; | |
1087 def.ptrval = "0"; | |
1088 max_multiple = atoi(tern_find_path_default(config, "video\0fbdev\0max_multiple\0", def, TVAL_PTR).ptrval); | |
1089 def.ptrval = "true"; | |
1090 copy_use_thread = strcmp(tern_find_path_default(config, "video\0fbdev\0use_thread\0", def, TVAL_PTR).ptrval, "false"); | |
1091 if (copy_use_thread) { | |
1092 pthread_create(&buffer_copy_handle, NULL, buffer_copy, NULL); | |
1093 } | |
1094 #ifndef DISABLE_OPENGL | |
1095 } | |
1096 #endif | |
1097 | |
1098 update_aspect(); | |
1099 render_alloc_surfaces(); | |
1100 def.ptrval = "off"; | |
1101 scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def, TVAL_PTR).ptrval, "on"); | |
1102 } | |
1103 | |
1104 void restore_tty(void) | |
1105 { | |
1106 ioctl(STDIN_FILENO, KDSETMODE, KD_TEXT); | |
1107 for (int i = 0; i < cur_devices; i++) | |
1108 { | |
1109 if (device_types[i] == DEV_KEYBOARD) { | |
1110 ioctl(device_fds[i], EVIOCGRAB, 0); | |
1111 } | |
1112 } | |
1113 } | |
1114 | |
1115 void render_init(int width, int height, char * title, uint8_t fullscreen) | |
1116 { | |
1117 if (height <= 0) { | |
1118 float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; | |
1119 height = ((float)width / aspect) + 0.5f; | |
1120 } | |
1121 printf("width: %d, height: %d\n", width, height); | |
1122 windowed_width = width; | |
1123 windowed_height = height; | |
1124 | |
1125 main_width = width; | |
1126 main_height = height; | |
1127 | |
1128 caption = title; | |
1129 | |
1130 if (isatty(STDIN_FILENO)) { | |
1131 ioctl(STDIN_FILENO, KDSETMODE, KD_GRAPHICS); | |
1132 atexit(restore_tty); | |
1133 } | |
1134 | |
1135 window_setup(); | |
1136 | |
1137 init_audio(); | |
1138 | |
1139 render_set_video_standard(VID_NTSC); | |
1140 | |
1141 DIR *d = opendir("/dev/input"); | |
1142 struct dirent* entry; | |
1143 int joystick_counter = 0; | |
1144 while ((entry = readdir(d)) && cur_devices < MAX_DEVICES) | |
1145 { | |
1146 if (!strncmp("event", entry->d_name, strlen("event"))) { | |
1147 char *filename = alloc_concat("/dev/input/", entry->d_name); | |
1148 int fd = open(filename, O_RDONLY); | |
1149 if (fd == -1) { | |
1150 int errnum = errno; | |
1151 warning("Failed to open evdev device %s for reading: %s\n", filename, strerror(errnum)); | |
1152 free(filename); | |
1153 continue; | |
1154 } | |
1155 | |
1156 unsigned long bits; | |
1157 if (-1 == ioctl(fd, EVIOCGBIT(0, sizeof(bits)), &bits)) { | |
1158 int errnum = errno; | |
1159 warning("Failed get capability bits from evdev device %s: %s\n", filename, strerror(errnum)); | |
1160 free(filename); | |
1161 close(fd); | |
1162 continue; | |
1163 } | |
1164 if (!(1 & bits >> EV_KEY)) { | |
1165 //if it doesn't support key events we don't care about it | |
1166 free(filename); | |
1167 close(fd); | |
1168 continue; | |
1169 } | |
1170 unsigned long button_bits[(BTN_THUMBR+8*sizeof(long))/(8*sizeof(long))]; | |
1171 int res = ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(button_bits)), button_bits); | |
1172 if (-1 == res) { | |
1173 int errnum = errno; | |
1174 warning("Failed get capability bits from evdev device %s: %s\n", filename, strerror(errnum)); | |
1175 free(filename); | |
1176 close(fd); | |
1177 continue; | |
1178 } | |
1179 int to_check[] = {KEY_ENTER, BTN_MOUSE, BTN_GAMEPAD}; | |
1180 device_type dtype = DEV_NONE; | |
1181 for (int i = 0; i < 3; i++) | |
1182 { | |
1183 if (1 & button_bits[to_check[i]/(8*sizeof(button_bits[0]))] >> to_check[i]%(8*sizeof(button_bits[0]))) { | |
1184 dtype = i + 1; | |
1185 } | |
1186 } | |
1187 if (dtype == DEV_NONE) { | |
1188 close(fd); | |
1189 } else { | |
1190 device_fds[cur_devices] = fd; | |
1191 device_types[cur_devices] = dtype; | |
1192 char name[1024]; | |
1193 char *names[] = {"Keyboard", "Mouse", "Gamepad"}; | |
1194 ioctl(fd, EVIOCGNAME(sizeof(name)), name); | |
1195 printf("%s is a %s\n%s\n", filename, names[dtype - 1], name); | |
1196 | |
1197 if (dtype == DEV_GAMEPAD) { | |
1198 handle_joy_added(joystick_counter++); | |
1199 } else if (dtype == DEV_KEYBOARD && isatty(STDIN_FILENO)) { | |
1200 ioctl(fd, EVIOCGRAB, 1); | |
1201 } | |
1202 | |
1203 //set FD to non-blocking mode for event polling | |
1204 fcntl(fd, F_SETFL, O_NONBLOCK); | |
1205 cur_devices++; | |
1206 } | |
1207 free(filename); | |
1208 } | |
1209 } | |
1210 | |
1211 atexit(render_quit); | |
1212 } | |
1213 #include<unistd.h> | |
1214 static int in_toggle; | |
1215 static void update_source(audio_source *src, double rc, uint8_t sync_changed) | |
1216 { | |
1217 double alpha = src->dt / (src->dt + rc); | |
1218 int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha); | |
1219 src->lowpass_alpha = lowpass_alpha; | |
1220 } | |
1221 | |
1222 void render_config_updated(void) | |
1223 { | |
1224 | |
1225 free_surfaces(); | |
1226 #ifndef DISABLE_OPENGL | |
1227 if (render_gl) { | |
1228 /*if (on_context_destroyed) { | |
1229 on_context_destroyed(); | |
1230 }*/ | |
1231 gl_teardown(); | |
1232 //FIXME: EGL equivalent | |
1233 //SDL_GL_DeleteContext(main_context); | |
1234 } else { | |
1235 #endif | |
1236 #ifndef DISABLE_OPENGL | |
1237 } | |
1238 #endif | |
1239 //FIXME: EGL equivalent | |
1240 //SDL_DestroyWindow(main_window); | |
1241 drain_events(); | |
1242 | |
1243 char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval; | |
1244 if (config_width) { | |
1245 windowed_width = atoi(config_width); | |
1246 } | |
1247 char *config_height = tern_find_path(config, "video\0height\0", TVAL_PTR).ptrval; | |
1248 if (config_height) { | |
1249 windowed_height = atoi(config_height); | |
1250 } else { | |
1251 float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; | |
1252 windowed_height = ((float)windowed_width / aspect) + 0.5f; | |
1253 } | |
1254 | |
1255 window_setup(); | |
1256 update_aspect(); | |
1257 #ifndef DISABLE_OPENGL | |
1258 //need to check render_gl again after window_setup as render option could have changed | |
1259 /*if (render_gl && on_context_created) { | |
1260 on_context_created(); | |
1261 }*/ | |
1262 #endif | |
1263 | |
1264 render_close_audio(); | |
1265 quitting = 0; | |
1266 init_audio(); | |
1267 render_set_video_standard(video_standard); | |
1268 | |
1269 double lowpass_cutoff = get_lowpass_cutoff(config); | |
1270 double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); | |
1271 for (uint8_t i = 0; i < num_audio_sources; i++) | |
1272 { | |
1273 update_source(audio_sources[i], rc, 0); | |
1274 } | |
1275 for (uint8_t i = 0; i < num_inactive_audio_sources; i++) | |
1276 { | |
1277 update_source(inactive_audio_sources[i], rc, 0); | |
1278 } | |
1279 drain_events(); | |
1280 } | |
1281 | |
1282 void render_set_video_standard(vid_std std) | |
1283 { | |
1284 video_standard = std; | |
1285 } | |
1286 | |
1287 void render_update_caption(char *title) | |
1288 { | |
1289 caption = title; | |
1290 free(fps_caption); | |
1291 fps_caption = NULL; | |
1292 } | |
1293 | |
1294 static char *screenshot_path; | |
1295 void render_save_screenshot(char *path) | |
1296 { | |
1297 if (screenshot_path) { | |
1298 free(screenshot_path); | |
1299 } | |
1300 screenshot_path = path; | |
1301 } | |
1302 | |
1303 uint8_t render_create_window(char *caption, uint32_t width, uint32_t height, window_close_handler close_handler) | |
1304 { | |
1305 //not supported under fbdev | |
1306 return 0; | |
1307 } | |
1308 | |
1309 void render_destroy_window(uint8_t which) | |
1310 { | |
1311 //not supported under fbdev | |
1312 } | |
1313 | |
1314 static uint8_t last_fb; | |
1315 static uint32_t texture_off; | |
1316 uint32_t *render_get_framebuffer(uint8_t which, int *pitch) | |
1317 { | |
1318 if (max_multiple == 1 && !render_gl) { | |
1319 if (last_fb != which) { | |
1320 *pitch = fb_stride * 2; | |
1321 return framebuffer + (which == FRAMEBUFFER_EVEN ? fb_stride / sizeof(uint32_t) : 0); | |
1322 } | |
1323 *pitch = fb_stride; | |
1324 return framebuffer; | |
1325 } | |
1326 if (!render_gl && last_fb != which) { | |
1327 *pitch = LINEBUF_SIZE * sizeof(uint32_t) * 2; | |
1328 return texture_buf + texture_off + (which == FRAMEBUFFER_EVEN ? LINEBUF_SIZE : 0); | |
1329 } | |
1330 *pitch = LINEBUF_SIZE * sizeof(uint32_t); | |
1331 return texture_buf + texture_off; | |
1332 } | |
1333 | |
1334 uint8_t events_processed; | |
1335 #ifdef __ANDROID__ | |
1336 #define FPS_INTERVAL 10000 | |
1337 #else | |
1338 #define FPS_INTERVAL 1000 | |
1339 #endif | |
1340 | |
1341 static uint8_t interlaced; | |
1342 void render_update_display(); | |
1343 void render_framebuffer_updated(uint8_t which, int width) | |
1344 { | |
1345 uint32_t height = which <= FRAMEBUFFER_EVEN | |
1346 ? (video_standard == VID_NTSC ? 243 : 294) - (overscan_top[video_standard] + overscan_bot[video_standard]) | |
1347 : 240; | |
1348 width -= overscan_left[video_standard] + overscan_right[video_standard]; | |
1349 #ifndef DISABLE_OPENGL | |
1350 if (render_gl && which <= FRAMEBUFFER_EVEN) { | |
1351 last_width = width; | |
1352 glBindTexture(GL_TEXTURE_2D, textures[which]); | |
1353 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LINEBUF_SIZE, height, SRC_FORMAT, GL_UNSIGNED_BYTE, texture_buf + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]); | |
1354 render_update_display(); | |
1355 last_height = height; | |
1356 } else { | |
1357 #endif | |
1358 if (max_multiple != 1) { | |
1359 if (copy_use_thread) { | |
1360 pthread_mutex_lock(&buffer_lock); | |
1361 buffer_ready = 1; | |
1362 last_width = width; | |
1363 last_width_scale = LINEBUF_SIZE - (overscan_left[video_standard] + overscan_right[video_standard]); | |
1364 last_height = last_height_scale = height; | |
1365 copy_buffer = texture_buf + texture_off + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]; | |
1366 if (which != last_fb) { | |
1367 last_height *= 2; | |
1368 copy_buffer += LINEBUF_SIZE * overscan_top[video_standard]; | |
1369 uint32_t *src = texture_buf + (texture_off ? 0 : LINEBUF_SIZE * MAX_FB_LINES) + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]; | |
1370 uint32_t *dst = copy_buffer; | |
1371 if (which == FRAMEBUFFER_ODD) { | |
1372 src += LINEBUF_SIZE; | |
1373 dst += LINEBUF_SIZE; | |
1374 } | |
1375 for (int i = 0; i < height; i++) | |
1376 { | |
1377 memcpy(dst, src, width * sizeof(uint32_t)); | |
1378 src += LINEBUF_SIZE * 2; | |
1379 dst += LINEBUF_SIZE * 2; | |
1380 } | |
1381 } | |
1382 texture_off = texture_off ? 0 : LINEBUF_SIZE * MAX_FB_LINES; | |
1383 pthread_cond_signal(&buffer_cond); | |
1384 pthread_mutex_unlock(&buffer_lock); | |
1385 } else { | |
1386 last_width = width; | |
1387 last_width_scale = LINEBUF_SIZE - (overscan_left[video_standard] + overscan_right[video_standard]); | |
1388 last_height = last_height_scale = height; | |
1389 copy_buffer = texture_buf + texture_off + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]; | |
1390 if (which != last_fb) { | |
1391 last_height *= 2; | |
1392 copy_buffer += LINEBUF_SIZE * overscan_top[video_standard]; | |
1393 } | |
1394 do_buffer_copy(); | |
1395 } | |
1396 } | |
1397 last_fb = which; | |
1398 if (!events_processed) { | |
1399 process_events(); | |
1400 } | |
1401 events_processed = 0; | |
1402 #ifndef DISABLE_OPENGL | |
1403 } | |
1404 #endif | |
1405 } | |
1406 | |
1407 void render_update_display() | |
1408 { | |
1409 #ifndef DISABLE_OPENGL | |
1410 if (render_gl) { | |
1411 glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |
1412 glClear(GL_COLOR_BUFFER_BIT); | |
1413 | |
1414 glUseProgram(program); | |
1415 glActiveTexture(GL_TEXTURE0); | |
1416 glBindTexture(GL_TEXTURE_2D, textures[0]); | |
1417 glUniform1i(un_textures[0], 0); | |
1418 | |
1419 glActiveTexture(GL_TEXTURE1); | |
1420 glBindTexture(GL_TEXTURE_2D, textures[interlaced ? 1 : scanlines ? 2 : 0]); | |
1421 glUniform1i(un_textures[1], 1); | |
1422 | |
1423 glUniform1f(un_width, render_emulated_width()); | |
1424 glUniform1f(un_height, last_height); | |
1425 | |
1426 glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); | |
1427 glVertexAttribPointer(at_pos, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat[2]), (void *)0); | |
1428 glEnableVertexAttribArray(at_pos); | |
1429 | |
1430 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); | |
1431 glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, (void *)0); | |
1432 | |
1433 glDisableVertexAttribArray(at_pos); | |
1434 | |
1435 /*if (render_ui) { | |
1436 render_ui(); | |
1437 }*/ | |
1438 | |
1439 eglSwapBuffers(egl_display, main_surface); | |
1440 } | |
1441 #endif | |
1442 if (!events_processed) { | |
1443 process_events(); | |
1444 } | |
1445 events_processed = 0; | |
1446 } | |
1447 | |
1448 uint32_t render_emulated_width() | |
1449 { | |
1450 return last_width - overscan_left[video_standard] - overscan_right[video_standard]; | |
1451 } | |
1452 | |
1453 uint32_t render_emulated_height() | |
1454 { | |
1455 return (video_standard == VID_NTSC ? 243 : 294) - overscan_top[video_standard] - overscan_bot[video_standard]; | |
1456 } | |
1457 | |
1458 uint32_t render_overscan_left() | |
1459 { | |
1460 return overscan_left[video_standard]; | |
1461 } | |
1462 | |
1463 uint32_t render_overscan_top() | |
1464 { | |
1465 return overscan_top[video_standard]; | |
1466 } | |
1467 | |
1468 void render_wait_quit(vdp_context * context) | |
1469 { | |
1470 for(;;) | |
1471 { | |
1472 drain_events(); | |
1473 sleep(1); | |
1474 } | |
1475 } | |
1476 | |
1477 int render_lookup_button(char *name) | |
1478 { | |
1479 static tern_node *button_lookup; | |
1480 if (!button_lookup) { | |
1481 //xbox/sdl style names | |
1482 button_lookup = tern_insert_int(button_lookup, "a", BTN_SOUTH); | |
1483 button_lookup = tern_insert_int(button_lookup, "b", BTN_EAST); | |
1484 button_lookup = tern_insert_int(button_lookup, "x", BTN_WEST); | |
1485 button_lookup = tern_insert_int(button_lookup, "y", BTN_NORTH); | |
1486 button_lookup = tern_insert_int(button_lookup, "back", BTN_SELECT); | |
1487 button_lookup = tern_insert_int(button_lookup, "start", BTN_START); | |
1488 button_lookup = tern_insert_int(button_lookup, "guid", BTN_MODE); | |
1489 button_lookup = tern_insert_int(button_lookup, "leftshoulder", BTN_TL); | |
1490 button_lookup = tern_insert_int(button_lookup, "rightshoulder", BTN_TR); | |
1491 button_lookup = tern_insert_int(button_lookup, "leftstick", BTN_THUMBL); | |
1492 button_lookup = tern_insert_int(button_lookup, "rightstick", BTN_THUMBR); | |
1493 //alternative Playstation-style names | |
1494 button_lookup = tern_insert_int(button_lookup, "cross", BTN_SOUTH); | |
1495 button_lookup = tern_insert_int(button_lookup, "circle", BTN_EAST); | |
1496 button_lookup = tern_insert_int(button_lookup, "square", BTN_WEST); | |
1497 button_lookup = tern_insert_int(button_lookup, "triangle", BTN_NORTH); | |
1498 button_lookup = tern_insert_int(button_lookup, "share", BTN_SELECT); | |
1499 button_lookup = tern_insert_int(button_lookup, "select", BTN_SELECT); | |
1500 button_lookup = tern_insert_int(button_lookup, "options", BTN_START); | |
1501 button_lookup = tern_insert_int(button_lookup, "l1", BTN_TL); | |
1502 button_lookup = tern_insert_int(button_lookup, "r1", BTN_TR); | |
1503 button_lookup = tern_insert_int(button_lookup, "l3", BTN_THUMBL); | |
1504 button_lookup = tern_insert_int(button_lookup, "r3", BTN_THUMBR); | |
1505 } | |
1506 return (int)tern_find_int(button_lookup, name, KEY_CNT); | |
1507 } | |
1508 | |
1509 int render_lookup_axis(char *name) | |
1510 { | |
1511 static tern_node *axis_lookup; | |
1512 if (!axis_lookup) { | |
1513 //xbox/sdl style names | |
1514 axis_lookup = tern_insert_int(axis_lookup, "leftx", ABS_X); | |
1515 axis_lookup = tern_insert_int(axis_lookup, "lefty", ABS_Y); | |
1516 axis_lookup = tern_insert_int(axis_lookup, "lefttrigger", ABS_Z); | |
1517 axis_lookup = tern_insert_int(axis_lookup, "rightx", ABS_RX); | |
1518 axis_lookup = tern_insert_int(axis_lookup, "righty", ABS_RY); | |
1519 axis_lookup = tern_insert_int(axis_lookup, "righttrigger", ABS_RZ); | |
1520 //alternative Playstation-style names | |
1521 axis_lookup = tern_insert_int(axis_lookup, "l2", ABS_Z); | |
1522 axis_lookup = tern_insert_int(axis_lookup, "r2", ABS_RZ); | |
1523 } | |
1524 return (int)tern_find_int(axis_lookup, name, ABS_CNT); | |
1525 } | |
1526 | |
1527 int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis) | |
1528 { | |
1529 tern_node *button_lookup, *axis_lookup; | |
1530 if (is_axis) { | |
1531 int axis = render_lookup_axis(name); | |
1532 if (axis == ABS_CNT) { | |
1533 return RENDER_INVALID_NAME; | |
1534 } | |
1535 return RENDER_AXIS_BIT | axis; | |
1536 } else { | |
1537 int button = render_lookup_button(name); | |
1538 if (button != KEY_CNT) { | |
1539 return button; | |
1540 } | |
1541 if (!strcmp("dpup", name)) { | |
1542 return RENDER_DPAD_BIT | 1; | |
1543 } | |
1544 if (!strcmp("dpdown", name)) { | |
1545 return RENDER_DPAD_BIT | 4; | |
1546 } | |
1547 if (!strcmp("dpdleft", name)) { | |
1548 return RENDER_DPAD_BIT | 8; | |
1549 } | |
1550 if (!strcmp("dpright", name)) { | |
1551 return RENDER_DPAD_BIT | 2; | |
1552 } | |
1553 return RENDER_INVALID_NAME; | |
1554 } | |
1555 } | |
1556 | |
1557 int32_t render_dpad_part(int32_t input) | |
1558 { | |
1559 return input >> 4 & 0xFFFFFF; | |
1560 } | |
1561 | |
1562 uint8_t render_direction_part(int32_t input) | |
1563 { | |
1564 return input & 0xF; | |
1565 } | |
1566 | |
1567 int32_t render_axis_part(int32_t input) | |
1568 { | |
1569 return input & 0xFFFFFFF; | |
1570 } | |
1571 | |
1572 void process_events() | |
1573 { | |
1574 if (events_processed > MAX_EVENT_POLL_PER_FRAME) { | |
1575 return; | |
1576 } | |
1577 drain_events(); | |
1578 events_processed++; | |
1579 } | |
1580 | |
1581 #define TOGGLE_MIN_DELAY 250 | |
1582 void render_toggle_fullscreen() | |
1583 { | |
1584 //always fullscreen in fbdev | |
1585 } | |
1586 | |
1587 uint32_t render_audio_buffer() | |
1588 { | |
1589 return buffer_samples; | |
1590 } | |
1591 | |
1592 uint32_t render_sample_rate() | |
1593 { | |
1594 return sample_rate; | |
1595 } | |
1596 | |
1597 void render_errorbox(char *title, char *message) | |
1598 { | |
1599 | |
1600 } | |
1601 | |
1602 void render_warnbox(char *title, char *message) | |
1603 { | |
1604 | |
1605 } | |
1606 | |
1607 void render_infobox(char *title, char *message) | |
1608 { | |
1609 | |
1610 } | |
1611 | |
1612 uint8_t render_has_gl(void) | |
1613 { | |
1614 return render_gl; | |
1615 } | |
1616 | |
1617 uint8_t render_get_active_framebuffer(void) | |
1618 { | |
1619 return FRAMEBUFFER_ODD; | |
1620 } |