# HG changeset patch # User Michael Pavone # Date 1552278609 25200 # Node ID 3a8c4ee685687680f529b4ad1138a8751284cac2 # Parent 8a29c250f3523c3f85dd76f24c3bbd60cd050159 Added raw fbdev/evdev/ALSA render backend diff -r 8a29c250f352 -r 3a8c4ee68568 Makefile --- a/Makefile Mon Feb 25 21:22:14 2019 -0800 +++ b/Makefile Sun Mar 10 21:30:09 2019 -0700 @@ -38,12 +38,21 @@ LIBS=sdl2 glew FONT:=nuklear_ui/font_mac.o else + +ifdef USE_FBDEV +LIBS=alsa +ifndef NOGL +LIBS+=glesv2 egl +endif +CFLAGS+= -DUSE_GLES -DUSE_FBDEV -pthread +else ifdef USE_GLES LIBS=sdl2 glesv2 CFLAGS+= -DUSE_GLES else LIBS=sdl2 glew gl endif #USE_GLES +endif #USE_FBDEV FONT:=nuklear_ui/font.o endif #Darwin @@ -88,6 +97,9 @@ else CFLAGS:=$(shell pkg-config --cflags-only-I $(LIBS)) $(CFLAGS) LDFLAGS:=-lm $(shell pkg-config --libs $(LIBS)) +ifdef USE_FBDEV +LDFLAGS+= -pthread +endif endif #libblastem.so ifeq ($(OS),Darwin) @@ -168,8 +180,13 @@ endif AUDIOOBJS=ym2612.o psg.o wave.o CONFIGOBJS=config.o tern.o util.o paths.o -NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o controller_info.o -RENDEROBJS=render_sdl.o ppm.o +NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o +RENDEROBJS=ppm.o controller_info.o +ifdef USE_FBDEV +RENDEROBJS+= render_fbdev.o +else +RENDEROBJS+= render_sdl.o +endif ifdef NOZLIB CFLAGS+= -DDISABLE_ZLIB diff -r 8a29c250f352 -r 3a8c4ee68568 controller_info.c --- a/controller_info.c Mon Feb 25 21:22:14 2019 -0800 +++ b/controller_info.c Sun Mar 10 21:30:09 2019 -0700 @@ -1,5 +1,8 @@ #include +#include +#ifndef USE_FBDEV #include "render_sdl.h" +#endif #include "controller_info.h" #include "config.h" #include "util.h" @@ -70,6 +73,7 @@ controller_info get_controller_info(int joystick) { +#ifndef USE_FBDEV load_ctype_config(); char guid_string[33]; SDL_Joystick *stick = render_get_joystick(joystick); @@ -148,6 +152,9 @@ return res; } } +#else + const char *name = "Unknown"; +#endif //default to a 360 return (controller_info){ .type = TYPE_GENERIC_MAPPING, @@ -159,6 +166,7 @@ static void mappings_iter(char *key, tern_val val, uint8_t valtype, void *data) { +#ifndef USE_FBDEV if (valtype != TVAL_NODE) { return; } @@ -169,6 +177,7 @@ SDL_GameControllerAddMapping(full); free(full); } +#endif } void controller_add_mappings(void) @@ -181,6 +190,7 @@ void save_controller_info(int joystick, controller_info *info) { +#ifndef USE_FBDEV char guid_string[33]; SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(render_get_joystick(joystick)), guid_string, sizeof(guid_string)); tern_node *existing = tern_find_node(info_config, guid_string); @@ -188,17 +198,19 @@ existing = tern_insert_ptr(existing, "variant", (void *)variant_names[info->variant]); info_config = tern_insert_node(info_config, guid_string, existing); persist_config_at(info_config, "controller_types.cfg"); - +#endif } void save_controller_mapping(int joystick, char *mapping_string) { +#ifndef USE_FBDEV char guid_string[33]; SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(render_get_joystick(joystick)), guid_string, sizeof(guid_string)); tern_node *existing = tern_find_node(info_config, guid_string); existing = tern_insert_ptr(existing, "mapping", mapping_string); info_config = tern_insert_node(info_config, guid_string, existing); persist_config_at(info_config, "controller_types.cfg"); +#endif } char const *labels_xbox[] = { @@ -255,10 +267,12 @@ const char *get_button_label(controller_info *info, int button) { +#ifndef USE_FBDEV if (button >= SDL_CONTROLLER_BUTTON_DPAD_UP) { static char const * dirs[] = {"Up", "Down", "Left", "Right"}; return dirs[button - SDL_CONTROLLER_BUTTON_DPAD_UP]; } +#endif return label_source(info)[button]; } @@ -267,11 +281,15 @@ }; const char *get_axis_label(controller_info *info, int axis) { +#ifndef USE_FBDEV if (axis < SDL_CONTROLLER_AXIS_TRIGGERLEFT) { return axis_labels[axis]; } else { return label_source(info)[axis - SDL_CONTROLLER_AXIS_TRIGGERLEFT + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER + 1]; } +#else + return NULL; +#endif } char *make_controller_type_key(controller_info *info) @@ -316,6 +334,9 @@ prefix = "Normal "; } else { static const char *parts[] = {"6 button (", NULL, "/", NULL, ") "}; +#ifdef USE_FBDEV + parts[1] = parts[3] = "??"; +#else if (info->variant == VARIANT_6B_BUMPERS) { parts[1] = get_button_label(info, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); parts[3] = get_button_label(info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); @@ -323,6 +344,7 @@ parts[1] = get_button_label(info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); parts[3] = get_axis_label(info, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); } +#endif prefix = alloc_concat_m(5, parts); } char *ret = alloc_concat(prefix, base); diff -r 8a29c250f352 -r 3a8c4ee68568 render.h --- a/render.h Mon Feb 25 21:22:14 2019 -0800 +++ b/render.h Sun Mar 10 21:30:09 2019 -0700 @@ -7,6 +7,10 @@ #define RENDER_H_ #ifndef IS_LIB +#ifdef USE_FBDEV +#include "special_keys_evdev.h" +#define render_relative_mouse(V) +#else #include #define RENDERKEY_UP SDLK_UP #define RENDERKEY_DOWN SDLK_DOWN @@ -62,6 +66,7 @@ #define RENDER_DPAD_RIGHT SDL_HAT_RIGHT #define render_relative_mouse SDL_SetRelativeMouseMode #endif +#endif #define MAX_JOYSTICKS 8 #define MAX_MICE 8 diff -r 8a29c250f352 -r 3a8c4ee68568 render_fbdev.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/render_fbdev.c Sun Mar 10 21:30:09 2019 -0700 @@ -0,0 +1,1981 @@ +/* + Copyright 2013 Michael Pavone + This file is part of BlastEm. + BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "render.h" +#include "blastem.h" +#include "genesis.h" +#include "bindings.h" +#include "util.h" +#include "paths.h" +#include "ppm.h" +#include "png.h" +#include "config.h" +#include "controller_info.h" + +#ifndef DISABLE_OPENGL +#include +#include +#ifdef USE_MALI +//Mali GLES headers don't seem to define GLchar for some reason +typedef char GLchar; +#endif +#endif + +#define MAX_EVENT_POLL_PER_FRAME 2 + +static EGLContext main_context; + +static int main_width, main_height, windowed_width, windowed_height, is_fullscreen; + +static uint8_t render_gl = 1; +static uint8_t scanlines = 0; + +static uint32_t last_frame = 0; +static snd_pcm_uframes_t buffer_samples; +static unsigned int output_channels, sample_rate; +static uint32_t missing_count; + + +static uint8_t quitting = 0; + +struct audio_source { + int16_t *front; + int16_t *back; + double dt; + uint64_t buffer_fraction; + uint64_t buffer_inc; + uint32_t buffer_pos; + uint32_t read_start; + uint32_t read_end; + uint32_t lowpass_alpha; + uint32_t mask; + int16_t last_left; + int16_t last_right; + uint8_t num_channels; + uint8_t front_populated; +}; + +static audio_source *audio_sources[8]; +static audio_source *inactive_audio_sources[8]; +static uint8_t num_audio_sources; +static uint8_t num_inactive_audio_sources; +static uint32_t min_buffered; + +typedef int32_t (*mix_func)(audio_source *audio, void *vstream, int len); + +static int32_t mix_s16(audio_source *audio, void *vstream, int len) +{ + int samples = len/(sizeof(int16_t)*output_channels); + int16_t *stream = vstream; + int16_t *end = stream + output_channels*samples; + int16_t *src = audio->front; + uint32_t i = audio->read_start; + uint32_t i_end = audio->read_end; + int16_t *cur = stream; + size_t first_add = output_channels > 1 ? 1 : 0, second_add = output_channels > 1 ? output_channels - 1 : 1; + if (audio->num_channels == 1) { + while (cur < end && i != i_end) + { + *cur += src[i]; + cur += first_add; + *cur += src[i++]; + cur += second_add; + i &= audio->mask; + } + } else { + while (cur < end && i != i_end) + { + *cur += src[i++]; + cur += first_add; + *cur += src[i++]; + cur += second_add; + i &= audio->mask; + } + } + + if (cur != end) { + printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); + } + if (cur != end) { + //printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); + return (cur-end)/2; + } else { + return ((i_end - i) & audio->mask) / audio->num_channels; + } +} + +static int32_t mix_f32(audio_source *audio, void *vstream, int len) +{ + int samples = len/(sizeof(float)*output_channels); + float *stream = vstream; + float *end = stream + output_channels*samples; + int16_t *src = audio->front; + uint32_t i = audio->read_start; + uint32_t i_end = audio->read_end; + float *cur = stream; + size_t first_add = output_channels > 1 ? 1 : 0, second_add = output_channels > 1 ? output_channels - 1 : 1; + if (audio->num_channels == 1) { + while (cur < end && i != i_end) + { + *cur += ((float)src[i]) / 0x7FFF; + cur += first_add; + *cur += ((float)src[i++]) / 0x7FFF; + cur += second_add; + i &= audio->mask; + } + } else { + while(cur < end && i != i_end) + { + *cur += ((float)src[i++]) / 0x7FFF; + cur += first_add; + *cur += ((float)src[i++]) / 0x7FFF; + cur += second_add; + i &= audio->mask; + } + } + if (cur != end) { + printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); + return (cur-end)/2; + } else { + return ((i_end - i) & audio->mask) / audio->num_channels; + } +} + +static int32_t mix_null(audio_source *audio, void *vstream, int len) +{ + return 0; +} + +static mix_func mix; + +static void render_close_audio() +{ + +} + +#define BUFFER_INC_RES 0x40000000UL + +void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider) +{ + src->buffer_inc = ((BUFFER_INC_RES * (uint64_t)sample_rate) / master_clock) * sample_divider; +} + +audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels) +{ + audio_source *ret = NULL; + uint32_t alloc_size = channels * buffer_samples; + if (num_audio_sources < 8) { + ret = malloc(sizeof(audio_source)); + ret->back = malloc(alloc_size * sizeof(int16_t)); + ret->front = malloc(alloc_size * sizeof(int16_t)); + ret->front_populated = 0; + ret->num_channels = channels; + audio_sources[num_audio_sources++] = ret; + } + if (!ret) { + fatal_error("Too many audio sources!"); + } else { + render_audio_adjust_clock(ret, master_clock, sample_divider); + double lowpass_cutoff = get_lowpass_cutoff(config); + double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); + ret->dt = 1.0 / ((double)master_clock / (double)(sample_divider)); + double alpha = ret->dt / (ret->dt + rc); + ret->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); + ret->buffer_pos = 0; + ret->buffer_fraction = 0; + ret->last_left = ret->last_right = 0; + ret->read_start = 0; + ret->read_end = buffer_samples * channels; + ret->mask = 0xFFFFFFFF; + } + return ret; +} + +void render_pause_source(audio_source *src) +{ + for (uint8_t i = 0; i < num_audio_sources; i++) + { + if (audio_sources[i] == src) { + audio_sources[i] = audio_sources[--num_audio_sources]; + break; + } + } + inactive_audio_sources[num_inactive_audio_sources++] = src; +} + +void render_resume_source(audio_source *src) +{ + if (num_audio_sources < 8) { + audio_sources[num_audio_sources++] = src; + } + for (uint8_t i = 0; i < num_inactive_audio_sources; i++) + { + if (inactive_audio_sources[i] == src) { + inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources]; + } + } +} + +void render_free_source(audio_source *src) +{ + render_pause_source(src); + + free(src->front); + free(src->back); + free(src); +} +snd_pcm_t *audio_handle; +static void do_audio_ready(audio_source *src) +{ + if (src->front_populated) { + fatal_error("Audio source filled up a buffer a second time before other sources finished their first\n"); + } + int16_t *tmp = src->front; + src->front = src->back; + src->back = tmp; + src->front_populated = 1; + src->buffer_pos = 0; + + for (uint8_t i = 0; i < num_audio_sources; i++) + { + if (!audio_sources[i]->front_populated) { + //at least one audio source is not ready yet. + return; + } + } + + size_t bytes = (mix == mix_s16 ? sizeof(int16_t) : sizeof(float)) * output_channels * buffer_samples; + void *buffer = malloc(bytes); + for (uint8_t i = 0; i < num_audio_sources; i++) + { + mix(audio_sources[i], buffer, bytes); + audio_sources[i]->front_populated = 0; + } + int frames = snd_pcm_writei(audio_handle, buffer, buffer_samples); + if (frames < 0) { + frames = snd_pcm_recover(audio_handle, frames, 0); + } + if (frames < 0) { + fprintf(stderr, "Failed to write samples: %s\n", snd_strerror(frames)); + } +} + +static int16_t lowpass_sample(audio_source *src, int16_t last, int16_t current) +{ + int32_t tmp = current * src->lowpass_alpha + last * (0x10000 - src->lowpass_alpha); + current = tmp >> 16; + return current; +} + +static void interp_sample(audio_source *src, int16_t last, int16_t current) +{ + int64_t tmp = last * ((src->buffer_fraction << 16) / src->buffer_inc); + tmp += current * (0x10000 - ((src->buffer_fraction << 16) / src->buffer_inc)); + src->back[src->buffer_pos++] = tmp >> 16; +} + +void render_put_mono_sample(audio_source *src, int16_t value) +{ + value = lowpass_sample(src, src->last_left, value); + src->buffer_fraction += src->buffer_inc; + uint32_t base = 0; + while (src->buffer_fraction > BUFFER_INC_RES) + { + src->buffer_fraction -= BUFFER_INC_RES; + interp_sample(src, src->last_left, value); + + if (((src->buffer_pos - base) & src->mask) >= buffer_samples) { + do_audio_ready(src); + } + src->buffer_pos &= src->mask; + } + src->last_left = value; +} + +void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right) +{ + left = lowpass_sample(src, src->last_left, left); + right = lowpass_sample(src, src->last_right, right); + src->buffer_fraction += src->buffer_inc; + uint32_t base = 0; + while (src->buffer_fraction > BUFFER_INC_RES) + { + src->buffer_fraction -= BUFFER_INC_RES; + + interp_sample(src, src->last_left, left); + interp_sample(src, src->last_right, right); + + if (((src->buffer_pos - base) & src->mask)/2 >= buffer_samples) { + do_audio_ready(src); + } + src->buffer_pos &= src->mask; + } + src->last_left = left; + src->last_right = right; +} + +int render_width() +{ + return main_width; +} + +int render_height() +{ + return main_height; +} + +int render_fullscreen() +{ + return 1; +} + +uint32_t red_shift, blue_shift, green_shift; +uint32_t render_map_color(uint8_t r, uint8_t g, uint8_t b) +{ + return r << red_shift | g << green_shift | b << blue_shift; +} + +#ifndef DISABLE_OPENGL +static GLuint textures[3], buffers[2], vshader, fshader, program, un_textures[2], un_width, un_height, at_pos; + +static GLfloat vertex_data_default[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + -1.0f, 1.0f, + 1.0f, 1.0f +}; + +static GLfloat vertex_data[8]; + +static const GLushort element_data[] = {0, 1, 2, 3}; + +static const GLchar shader_prefix[] = +#ifdef USE_GLES + "#version 100\n"; +#else + "#version 110\n" + "#define lowp\n" + "#define mediump\n" + "#define highp\n"; +#endif + +static GLuint load_shader(char * fname, GLenum shader_type) +{ + char const * parts[] = {get_home_dir(), "/.config/blastem/shaders/", fname}; + char * shader_path = alloc_concat_m(3, parts); + FILE * f = fopen(shader_path, "rb"); + free(shader_path); + GLchar * text; + long fsize; + if (f) { + fsize = file_size(f); + text = malloc(fsize); + if (fread(text, 1, fsize, f) != fsize) { + warning("Error reading from shader file %s\n", fname); + free(text); + return 0; + } + } else { + shader_path = path_append("shaders", fname); + uint32_t fsize32; + text = read_bundled_file(shader_path, &fsize32); + free(shader_path); + if (!text) { + warning("Failed to open shader file %s for reading\n", fname); + return 0; + } + fsize = fsize32; + } + + if (strncmp(text, "#version", strlen("#version"))) { + GLchar *tmp = text; + text = alloc_concat(shader_prefix, tmp); + free(tmp); + fsize += strlen(shader_prefix); + } + GLuint ret = glCreateShader(shader_type); + glShaderSource(ret, 1, (const GLchar **)&text, (const GLint *)&fsize); + free(text); + glCompileShader(ret); + GLint compile_status, loglen; + glGetShaderiv(ret, GL_COMPILE_STATUS, &compile_status); + if (!compile_status) { + glGetShaderiv(ret, GL_INFO_LOG_LENGTH, &loglen); + text = malloc(loglen); + glGetShaderInfoLog(ret, loglen, NULL, text); + warning("Shader %s failed to compile:\n%s\n", fname, text); + free(text); + glDeleteShader(ret); + return 0; + } + return ret; +} +#endif + +static uint32_t texture_buf[513 * 512 * 2]; +#ifdef DISABLE_OPENGL +#define RENDER_FORMAT SDL_PIXELFORMAT_ARGB8888 +#else +#ifdef USE_GLES +#define INTERNAL_FORMAT GL_RGBA +#define SRC_FORMAT GL_RGBA +#define RENDER_FORMAT SDL_PIXELFORMAT_ABGR8888 +#else +#define INTERNAL_FORMAT GL_RGBA8 +#define SRC_FORMAT GL_BGRA +#define RENDER_FORMAT SDL_PIXELFORMAT_ARGB8888 +#endif +static void gl_setup() +{ + tern_val def = {.ptrval = "linear"}; + char *scaling = tern_find_path_default(config, "video\0scaling\0", def, TVAL_PTR).ptrval; + GLint filter = strcmp(scaling, "linear") ? GL_NEAREST : GL_LINEAR; + glGenTextures(3, textures); + for (int i = 0; i < 3; i++) + { + glBindTexture(GL_TEXTURE_2D, textures[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if (i < 2) { + //TODO: Fixme for PAL + invalid display mode + glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, 512, 512, 0, SRC_FORMAT, GL_UNSIGNED_BYTE, texture_buf); + } else { + uint32_t blank = 255 << 24; + glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, 1, 1, 0, SRC_FORMAT, GL_UNSIGNED_BYTE, &blank); + } + } + glGenBuffers(2, buffers); + glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(element_data), element_data, GL_STATIC_DRAW); + def.ptrval = "default.v.glsl"; + vshader = load_shader(tern_find_path_default(config, "video\0vertex_shader\0", def, TVAL_PTR).ptrval, GL_VERTEX_SHADER); + def.ptrval = "default.f.glsl"; + fshader = load_shader(tern_find_path_default(config, "video\0fragment_shader\0", def, TVAL_PTR).ptrval, GL_FRAGMENT_SHADER); + program = glCreateProgram(); + glAttachShader(program, vshader); + glAttachShader(program, fshader); + glLinkProgram(program); + GLint link_status; + glGetProgramiv(program, GL_LINK_STATUS, &link_status); + if (!link_status) { + fputs("Failed to link shader program\n", stderr); + exit(1); + } + un_textures[0] = glGetUniformLocation(program, "textures[0]"); + un_textures[1] = glGetUniformLocation(program, "textures[1]"); + un_width = glGetUniformLocation(program, "width"); + un_height = glGetUniformLocation(program, "height"); + at_pos = glGetAttribLocation(program, "pos"); +} + +static void gl_teardown() +{ + glDeleteProgram(program); + glDeleteShader(vshader); + glDeleteShader(fshader); + glDeleteBuffers(2, buffers); + glDeleteTextures(3, textures); +} +#endif + +static uint8_t texture_init; +static void render_alloc_surfaces() +{ + if (texture_init) { + return; + } + texture_init = 1; +#ifndef DISABLE_OPENGL + if (render_gl) { + gl_setup(); + } +#endif +} + +static void free_surfaces(void) +{ + texture_init = 0; +} + +static char * caption = NULL; +static char * fps_caption = NULL; + +static void render_quit() +{ + render_close_audio(); + free_surfaces(); +#ifndef DISABLE_OPENGL + if (render_gl) { + gl_teardown(); + //FIXME: replace with EGL equivalent + //SDL_GL_DeleteContext(main_context); + } +#endif +} + +static float config_aspect() +{ + static float aspect = 0.0f; + if (aspect == 0.0f) { + char *config_aspect = tern_find_path_default(config, "video\0aspect\0", (tern_val){.ptrval = "4:3"}, TVAL_PTR).ptrval; + if (strcmp("stretch", config_aspect)) { + aspect = 4.0f/3.0f; + char *end; + float aspect_numerator = strtof(config_aspect, &end); + if (aspect_numerator > 0.0f && *end == ':') { + float aspect_denominator = strtof(end+1, &end); + if (aspect_denominator > 0.0f && !*end) { + aspect = aspect_numerator / aspect_denominator; + } + } + } else { + aspect = -1.0f; + } + } + return aspect; +} + +static void update_aspect() +{ + //reset default values +#ifndef DISABLE_OPENGL + memcpy(vertex_data, vertex_data_default, sizeof(vertex_data)); +#endif + if (config_aspect() > 0.0f) { + float aspect = (float)main_width / main_height; + if (fabs(aspect - config_aspect()) < 0.01f) { + //close enough for government work + return; + } +#ifndef DISABLE_OPENGL + if (render_gl) { + for (int i = 0; i < 4; i++) + { + if (aspect > config_aspect()) { + vertex_data[i*2] *= config_aspect()/aspect; + } else { + vertex_data[i*2+1] *= aspect/config_aspect(); + } + } + } else { +#endif + //TODO: Maybe do some stuff for non-integer scaling in raw fbdev copy +#ifndef DISABLE_OPENGL + } +#endif + } +} + +/* +static ui_render_fun on_context_destroyed, on_context_created; +void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create) +{ + on_context_destroyed = destroy; + on_context_created = create; +}*/ + +static uint8_t scancode_map[128] = { + [KEY_A] = 0x1C, + [KEY_B] = 0x32, + [KEY_C] = 0x21, + [KEY_D] = 0x23, + [KEY_E] = 0x24, + [KEY_F] = 0x2B, + [KEY_G] = 0x34, + [KEY_H] = 0x33, + [KEY_I] = 0x43, + [KEY_J] = 0x3B, + [KEY_K] = 0x42, + [KEY_L] = 0x4B, + [KEY_M] = 0x3A, + [KEY_N] = 0x31, + [KEY_O] = 0x44, + [KEY_P] = 0x4D, + [KEY_Q] = 0x15, + [KEY_R] = 0x2D, + [KEY_S] = 0x1B, + [KEY_T] = 0x2C, + [KEY_U] = 0x3C, + [KEY_V] = 0x2A, + [KEY_W] = 0x1D, + [KEY_X] = 0x22, + [KEY_Y] = 0x35, + [KEY_Z] = 0x1A, + [KEY_1] = 0x16, + [KEY_2] = 0x1E, + [KEY_3] = 0x26, + [KEY_4] = 0x25, + [KEY_5] = 0x2E, + [KEY_6] = 0x36, + [KEY_7] = 0x3D, + [KEY_8] = 0x3E, + [KEY_9] = 0x46, + [KEY_0] = 0x45, + [KEY_ENTER] = 0x5A, + [KEY_ESC] = 0x76, + [KEY_SPACE] = 0x29, + [KEY_TAB] = 0x0D, + [KEY_BACKSPACE] = 0x66, + [KEY_MINUS] = 0x4E, + [KEY_EQUAL] = 0x55, + [KEY_LEFTBRACE] = 0x54, + [KEY_RIGHTBRACE] = 0x5B, + [KEY_BACKSLASH] = 0x5D, + [KEY_SEMICOLON] = 0x4C, + [KEY_APOSTROPHE] = 0x52, + [KEY_GRAVE] = 0x0E, + [KEY_COMMA] = 0x41, + [KEY_DOT] = 0x49, + [KEY_SLASH] = 0x4A, + [KEY_CAPSLOCK] = 0x58, + [KEY_F1] = 0x05, + [KEY_F2] = 0x06, + [KEY_F3] = 0x04, + [KEY_F4] = 0x0C, + [KEY_F5] = 0x03, + [KEY_F6] = 0x0B, + [KEY_F7] = 0x83, + [KEY_F8] = 0x0A, + [KEY_F9] = 0x01, + [KEY_F10] = 0x09, + [KEY_F11] = 0x78, + [KEY_F12] = 0x07, + [KEY_LEFTCTRL] = 0x14, + [KEY_LEFTSHIFT] = 0x12, + [KEY_LEFTALT] = 0x11, + [KEY_RIGHTCTRL] = 0x18, + [KEY_RIGHTSHIFT] = 0x59, + [KEY_RIGHTALT] = 0x17, + [KEY_INSERT] = 0x81, + [KEY_PAUSE] = 0x82, + [KEY_SYSRQ] = 0x84, + [KEY_SCROLLLOCK] = 0x7E, + [KEY_DELETE] = 0x85, + [KEY_LEFT] = 0x86, + [KEY_HOME] = 0x87, + [KEY_END] = 0x88, + [KEY_UP] = 0x89, + [KEY_DOWN] = 0x8A, + [KEY_PAGEUP] = 0x8B, + [KEY_PAGEDOWN] = 0x8C, + [KEY_RIGHT] = 0x8D, + [KEY_NUMLOCK] = 0x77, + [KEY_KPSLASH] = 0x80, + [KEY_KPASTERISK] = 0x7C, + [KEY_KPMINUS] = 0x7B, + [KEY_KPPLUS] = 0x79, + [KEY_KPENTER] = 0x19, + [KEY_KP1] = 0x69, + [KEY_KP2] = 0x72, + [KEY_KP3] = 0x7A, + [KEY_KP4] = 0x6B, + [KEY_KP5] = 0x73, + [KEY_KP6] = 0x74, + [KEY_KP7] = 0x6C, + [KEY_KP8] = 0x75, + [KEY_KP9] = 0x7D, + [KEY_KP0] = 0x70, + [KEY_KPDOT] = 0x71, +}; + +#include "special_keys_evdev.h" +static uint8_t sym_map[128] = { + [KEY_A] = 'a', + [KEY_B] = 'b', + [KEY_C] = 'c', + [KEY_D] = 'd', + [KEY_E] = 'e', + [KEY_F] = 'f', + [KEY_G] = 'g', + [KEY_H] = 'h', + [KEY_I] = 'i', + [KEY_J] = 'j', + [KEY_K] = 'k', + [KEY_L] = 'l', + [KEY_M] = 'm', + [KEY_N] = 'n', + [KEY_O] = 'o', + [KEY_P] = 'p', + [KEY_Q] = 'q', + [KEY_R] = 'r', + [KEY_S] = 's', + [KEY_T] = 't', + [KEY_U] = 'u', + [KEY_V] = 'v', + [KEY_W] = 'w', + [KEY_X] = 'x', + [KEY_Y] = 'y', + [KEY_Z] = 'z', + [KEY_1] = '1', + [KEY_2] = '2', + [KEY_3] = '3', + [KEY_4] = '4', + [KEY_5] = '5', + [KEY_6] = '6', + [KEY_7] = '7', + [KEY_8] = '8', + [KEY_9] = '9', + [KEY_0] = '0', + [KEY_ENTER] = '\r', + [KEY_SPACE] = ' ', + [KEY_TAB] = '\t', + [KEY_BACKSPACE] = '\b', + [KEY_MINUS] = '-', + [KEY_EQUAL] = '=', + [KEY_LEFTBRACE] = '[', + [KEY_RIGHTBRACE] = ']', + [KEY_BACKSLASH] = '\\', + [KEY_SEMICOLON] = ';', + [KEY_APOSTROPHE] = '\'', + [KEY_GRAVE] = '`', + [KEY_COMMA] = ',', + [KEY_DOT] = '.', + [KEY_SLASH] = '/', + [KEY_ESC] = RENDERKEY_ESC, + [KEY_F1] = RENDERKEY_F1, + [KEY_F2] = RENDERKEY_F2, + [KEY_F3] = RENDERKEY_F3, + [KEY_F4] = RENDERKEY_F4, + [KEY_F5] = RENDERKEY_F5, + [KEY_F6] = RENDERKEY_F6, + [KEY_F7] = RENDERKEY_F7, + [KEY_F8] = RENDERKEY_F8, + [KEY_F9] = RENDERKEY_F9, + [KEY_F10] = RENDERKEY_F10, + [KEY_F11] = RENDERKEY_F11, + [KEY_F12] = RENDERKEY_F12, + [KEY_LEFTCTRL] = RENDERKEY_LCTRL, + [KEY_LEFTSHIFT] = RENDERKEY_LSHIFT, + [KEY_LEFTALT] = RENDERKEY_LALT, + [KEY_RIGHTCTRL] = RENDERKEY_RCTRL, + [KEY_RIGHTSHIFT] = RENDERKEY_RSHIFT, + [KEY_RIGHTALT] = RENDERKEY_RALT, + [KEY_DELETE] = RENDERKEY_DEL, + [KEY_LEFT] = RENDERKEY_LEFT, + [KEY_HOME] = RENDERKEY_HOME, + [KEY_END] = RENDERKEY_END, + [KEY_UP] = RENDERKEY_UP, + [KEY_DOWN] = RENDERKEY_DOWN, + [KEY_PAGEUP] = RENDERKEY_PAGEUP, + [KEY_PAGEDOWN] = RENDERKEY_PAGEDOWN, + [KEY_RIGHT] = RENDERKEY_RIGHT, + [KEY_KPSLASH] = 0x80, + [KEY_KPASTERISK] = 0x7C, + [KEY_KPMINUS] = 0x7B, + [KEY_KPPLUS] = 0x79, + [KEY_KPENTER] = 0x19, + [KEY_KP1] = 0x69, + [KEY_KP2] = 0x72, + [KEY_KP3] = 0x7A, + [KEY_KP4] = 0x6B, + [KEY_KP5] = 0x73, + [KEY_KP6] = 0x74, + [KEY_KP7] = 0x6C, + [KEY_KP8] = 0x75, + [KEY_KP9] = 0x7D, + [KEY_KP0] = 0x70, + [KEY_KPDOT] = 0x71, +}; + +static drop_handler drag_drop_handler; +void render_set_drag_drop_handler(drop_handler handler) +{ + drag_drop_handler = handler; +} + +/*static event_handler custom_event_handler; +void render_set_event_handler(event_handler handler) +{ + custom_event_handler = handler; +}*/ + +char* render_joystick_type_id(int index) +{ + return ""; + /*SDL_Joystick *stick = render_get_joystick(index); + if (!stick) { + return NULL; + } + char *guid_string = malloc(33); + SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(stick), guid_string, 33); + return guid_string;*/ +} + +static uint32_t overscan_top[NUM_VID_STD] = {2, 21}; +static uint32_t overscan_bot[NUM_VID_STD] = {1, 17}; +static uint32_t overscan_left[NUM_VID_STD] = {13, 13}; +static uint32_t overscan_right[NUM_VID_STD] = {14, 14}; +static vid_std video_standard = VID_NTSC; + +typedef enum { + DEV_NONE, + DEV_KEYBOARD, + DEV_MOUSE, + DEV_GAMEPAD +} device_type; + +static int32_t mouse_x, mouse_y, mouse_accum_x, mouse_accum_y; +static int32_t handle_event(device_type dtype, int device_index, struct input_event *event) +{ + switch (event->type) { + case EV_KEY: + //code is key, value is 1 for keydown, 0 for keyup + if (dtype == DEV_KEYBOARD && event->code < 128) { + //keyboard key that we might have a mapping for + if (event->value) { + handle_keydown(sym_map[event->code], scancode_map[event->code]); + } else { + handle_keyup(sym_map[event->code], scancode_map[event->code]); + } + } else if (dtype == DEV_MOUSE && event->code >= BTN_MOUSE && event->code < BTN_JOYSTICK) { + //mosue button + if (event->value) { + handle_mousedown(device_index, event->code - BTN_LEFT); + } else { + handle_mouseup(device_index, event->code - BTN_LEFT); + } + } else if (dtype == DEV_GAMEPAD && event->code >= BTN_GAMEPAD && event->code < BTN_DIGI) { + //gamepad button + if (event->value) { + handle_joydown(device_index, event->code - BTN_SOUTH); + } else { + handle_joyup(device_index, event->code - BTN_SOUTH); + } + } + break; + case EV_REL: + if (dtype == DEV_MOUSE) { + switch(event->code) + { + case REL_X: + mouse_accum_x += event->value; + break; + case REL_Y: + mouse_accum_y += event->value; + break; + } + } + break; + case EV_ABS: + //TODO: Handle joystick axis/hat motion, absolute mouse movement + break; + case EV_SYN: + if (dtype == DEV_MOUSE && (mouse_accum_x || mouse_accum_y)) { + mouse_x += mouse_accum_x; + mouse_y += mouse_accum_y; + if (mouse_x < 0) { + mouse_x = 0; + } else if (mouse_x >= main_width) { + mouse_x = main_width - 1; + } + if (mouse_y < 0) { + mouse_y = 0; + } else if (mouse_y >= main_height) { + mouse_y = main_height - 1; + } + handle_mouse_moved(device_index, mouse_x, mouse_y, mouse_accum_x, mouse_accum_y); + mouse_accum_x = mouse_accum_y = 0; + } + break; + /* + case SDL_JOYHATMOTION: + handle_joy_dpad(find_joystick_index(event->jhat.which), event->jhat.hat, event->jhat.value); + break; + case SDL_JOYAXISMOTION: + handle_joy_axis(find_joystick_index(event->jaxis.which), event->jaxis.axis, event->jaxis.value); + break;*/ + } + return 0; +} + +#define MAX_DEVICES 16 +static int device_fds[MAX_DEVICES]; +static device_type device_types[MAX_DEVICES]; +static int cur_devices; + +static void drain_events() +{ + struct input_event events[64]; + int index_by_type[3] = {0,0,0}; + for (int i = 0; i < cur_devices; i++) + { + int bytes = sizeof(events); + int device_index = index_by_type[device_types[i]-1]++; + while (bytes == sizeof(events)) + { + bytes = read(device_fds[i], events, sizeof(events)); + if (bytes > 0) { + int num_events = bytes / sizeof(events[0]); + for (int j = 0; j < num_events; j++) + { + handle_event(device_types[i], device_index, events + j); + } + } else if (bytes < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + perror("Failed to read evdev events"); + } + } + } +} + +static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"}; + +static void init_audio() +{ + int res = snd_pcm_open(&audio_handle, "default", SND_PCM_STREAM_PLAYBACK, 0); + if (res < 0) { + fatal_error("Failed to open ALSA device: %s\n", snd_strerror(res)); + } + + snd_pcm_hw_params_t *params; + snd_pcm_hw_params_alloca(¶ms); + res = snd_pcm_hw_params_any(audio_handle, params); + if (res < 0) { + fatal_error("No playback configurations available: %s\n", snd_strerror(res)); + } + res = snd_pcm_hw_params_set_access(audio_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); + if (res < 0) { + fatal_error("Failed to set access type: %s\n", snd_strerror(res)); + } + res = snd_pcm_hw_params_set_format(audio_handle, params, SND_PCM_FORMAT_S16_LE); + if (res < 0) { + //failed to set, signed 16-bit integer, try floating point + res = snd_pcm_hw_params_set_format(audio_handle, params, SND_PCM_FORMAT_FLOAT_LE); + if (res < 0) { + fatal_error("Failed to set an acceptable format: %s\n", snd_strerror(res)); + } + mix = mix_f32; + } else { + mix = mix_s16; + } + + char * rate_str = tern_find_path(config, "audio\0rate\0", TVAL_PTR).ptrval; + sample_rate = rate_str ? atoi(rate_str) : 0; + if (!sample_rate) { + sample_rate = 48000; + } + snd_pcm_hw_params_set_rate_near(audio_handle, params, &sample_rate, NULL); + output_channels = 2; + snd_pcm_hw_params_set_channels_near(audio_handle, params, &output_channels); + + char * samples_str = tern_find_path(config, "audio\0buffer\0", TVAL_PTR).ptrval; + buffer_samples = samples_str ? atoi(samples_str) : 0; + if (!buffer_samples) { + buffer_samples = 512; + } + snd_pcm_hw_params_set_period_size_near(audio_handle, params, &buffer_samples, NULL); + + int dir = 1; + unsigned int periods = 2; + snd_pcm_hw_params_set_periods_near(audio_handle, params, &periods, &dir); + + res = snd_pcm_hw_params(audio_handle, params); + if (res < 0) { + fatal_error("Failed to set ALSA hardware params: %s\n", snd_strerror(res)); + } + + printf("Initialized audio at frequency %d with a %d sample buffer, ", (int)sample_rate, (int)buffer_samples); + if (mix == mix_s16) { + puts("signed 16-bit int format"); + } else { + puts("32-bit float format"); + } +} + +int fbfd; +uint32_t *framebuffer; +uint32_t fb_stride; +#ifndef DISABLE_OPENGL +EGLDisplay egl_display; +EGLSurface main_surface; +uint8_t egl_setup(void) +{ + //Mesa wants the fbdev file descriptor as the display + egl_display = eglGetDisplay((EGLNativeDisplayType)fbfd); + if (egl_display == EGL_NO_DISPLAY) { + //Mali (and possibly others) seems to just want EGL_DEFAULT_DISPLAY + egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (egl_display == EGL_NO_DISPLAY) { + warning("eglGetDisplay failed with error %X\n", eglGetError()); + return 0; + } + } + EGLint major, minor; + if (!eglInitialize(egl_display, &major, &minor)) { + warning("eglInitialize failed with error %X\n", eglGetError()); + return 0; + } + printf("EGL version %d.%d\n", major, minor); + EGLint num_configs; + EGLConfig config; + EGLint const config_attribs[] = { + EGL_RED_SIZE, 5, + EGL_GREEN_SIZE, 5, + EGL_BLUE_SIZE, 5, + EGL_CONFORMANT, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + if (!eglChooseConfig(egl_display, config_attribs, &config, 1, &num_configs)) { + num_configs = 0; + warning("eglChooseConfig failed with error %X\n", eglGetError()); + } + if (!num_configs) { + warning("Failed to choose an EGL config\n"); + goto error; + } + EGLint const context_attribs[] = { +#ifdef EGL_CONTEXT_MAJOR_VERSION + EGL_CONTEXT_MAJOR_VERSION, 2, +#endif + EGL_NONE + }; + main_context = eglCreateContext(egl_display, config, EGL_NO_CONTEXT, context_attribs); + if (main_context == EGL_NO_CONTEXT) { + warning("Failed to create EGL context %X\n", eglGetError()); + goto error; + } +#ifdef USE_MALI + struct mali_native_window native_window = { + .width = main_width, + .height = main_height + }; + main_surface = eglCreateWindowSurface(egl_display, config, &native_window, NULL); +#else + main_surface = eglCreateWindowSurface(egl_display, config, (EGLNativeWindowType)NULL, NULL); +#endif + if (main_surface == EGL_NO_SURFACE) { + warning("Failed to create EGL surface %X\n", eglGetError()); + goto post_context_error; + } + if (eglMakeCurrent(egl_display, main_surface, main_surface, main_context)) { + return 1; + } + eglDestroySurface(egl_display, main_surface); +post_context_error: + eglDestroyContext(egl_display, main_context); +error: + eglTerminate(egl_display); + return 0; +} +#endif +static pthread_mutex_t buffer_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t buffer_cond = PTHREAD_COND_INITIALIZER; +static uint8_t buffer_ready; +static uint32_t *copy_buffer; +static uint32_t last_width, last_height; +static uint32_t max_multiple; +static void do_buffer_copy(void) +{ + uint32_t width_multiple = main_width / last_width; + uint32_t height_multiple = main_height / last_height; + uint32_t multiple = width_multiple < height_multiple ? width_multiple : height_multiple; + if (max_multiple && multiple > max_multiple) { + multiple = max_multiple; + } + uint32_t *cur_line = framebuffer + (main_width - last_width * multiple)/2; + cur_line += fb_stride * (main_height - last_height * multiple) / (2 * sizeof(uint32_t)); + uint32_t *src_line = copy_buffer; + for (uint32_t y = 0; y < last_height; y++) + { + for (uint32_t i = 0; i < multiple; i++) + { + uint32_t *cur = cur_line; + uint32_t *src = src_line; + for (uint32_t x = 0; x < last_width ; x++) + { + uint32_t pixel = *(src++); + for (uint32_t j = 0; j < multiple; j++) + { + *(cur++) = pixel; + } + } + + cur_line += fb_stride / sizeof(uint32_t); + } + src_line += LINEBUF_SIZE; + } +} +static void *buffer_copy(void *data) +{ + pthread_mutex_lock(&buffer_lock); + for(;;) + { + while (!buffer_ready) + { + pthread_cond_wait(&buffer_cond, &buffer_lock); + } + buffer_ready = 0; + do_buffer_copy(); + } + return 0; +} + +static pthread_t buffer_copy_handle; +static uint8_t copy_use_thread; +void window_setup(void) +{ + fbfd = open("/dev/fb0", O_RDWR); + struct fb_fix_screeninfo fixInfo; + struct fb_var_screeninfo varInfo; + ioctl(fbfd, FBIOGET_FSCREENINFO, &fixInfo); + ioctl(fbfd, FBIOGET_VSCREENINFO, &varInfo); + printf("Resolution: %d x %d\n", varInfo.xres, varInfo.yres); + printf("Framebuffer size: %d, line stride: %d\n", fixInfo.smem_len, fixInfo.line_length); + main_width = varInfo.xres; + main_height = varInfo.yres; + fb_stride = fixInfo.line_length; + tern_val def = {.ptrval = "audio"}; + char *sync_src = tern_find_path_default(config, "system\0sync_source\0", def, TVAL_PTR).ptrval; + + const char *vsync; + def.ptrval = "off"; + vsync = tern_find_path_default(config, "video\0vsync\0", def, TVAL_PTR).ptrval; + + + tern_node *video = tern_find_node(config, "video"); + if (video) + { + for (int i = 0; i < NUM_VID_STD; i++) + { + tern_node *std_settings = tern_find_node(video, vid_std_names[i]); + if (std_settings) { + char *val = tern_find_path_default(std_settings, "overscan\0top\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; + if (val) { + overscan_top[i] = atoi(val); + } + val = tern_find_path_default(std_settings, "overscan\0bottom\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; + if (val) { + overscan_bot[i] = atoi(val); + } + val = tern_find_path_default(std_settings, "overscan\0left\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; + if (val) { + overscan_left[i] = atoi(val); + } + val = tern_find_path_default(std_settings, "overscan\0right\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; + if (val) { + overscan_right[i] = atoi(val); + } + } + } + } + render_gl = 0; +#ifndef DISABLE_OPENGL + char *gl_enabled_str = tern_find_path_default(config, "video\0gl\0", def, TVAL_PTR).ptrval; + uint8_t gl_enabled = strcmp(gl_enabled_str, "off") != 0; + if (gl_enabled) + { + render_gl = egl_setup(); + blue_shift = 16; + green_shift = 8; + red_shift = 0; + } + if (!render_gl) { +#endif + framebuffer = mmap(NULL, fixInfo.smem_len, PROT_READ|PROT_WRITE, MAP_SHARED, fbfd, 0); + red_shift = varInfo.red.offset; + green_shift = varInfo.green.offset; + blue_shift = varInfo.blue.offset; + def.ptrval = "0"; + max_multiple = atoi(tern_find_path_default(config, "video\0fbdev\0max_multiple\0", def, TVAL_PTR).ptrval); + def.ptrval = "true"; + copy_use_thread = strcmp(tern_find_path_default(config, "video\0fbdev\0use_thread\0", def, TVAL_PTR).ptrval, "false"); + if (copy_use_thread) { + pthread_create(&buffer_copy_handle, NULL, buffer_copy, NULL); + } +#ifndef DISABLE_OPENGL + } +#endif + + /* +#ifndef DISABLE_OPENGL + char *gl_enabled_str = tern_find_path_default(config, "video\0gl\0", def, TVAL_PTR).ptrval; + uint8_t gl_enabled = strcmp(gl_enabled_str, "off") != 0; + if (gl_enabled) + { + flags |= SDL_WINDOW_OPENGL; + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); +#ifdef USE_GLES + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#endif + } +#endif + main_window = SDL_CreateWindow(caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, main_width, main_height, flags); + if (!main_window) { + fatal_error("Unable to create SDL window: %s\n", SDL_GetError()); + } +#ifndef DISABLE_OPENGL + if (gl_enabled) + { + main_context = SDL_GL_CreateContext(main_window); +#ifdef USE_GLES + int major_version; + if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major_version) == 0 && major_version >= 2) { +#else + GLenum res = glewInit(); + if (res != GLEW_OK) { + warning("Initialization of GLEW failed with code %d\n", res); + } + + if (res == GLEW_OK && GLEW_VERSION_2_0) { +#endif + render_gl = 1; + SDL_GL_MakeCurrent(main_window, main_context); + if (!strcmp("tear", vsync)) { + if (SDL_GL_SetSwapInterval(-1) < 0) { + warning("late tear is not available (%s), using normal vsync\n", SDL_GetError()); + vsync = "on"; + } else { + vsync = NULL; + } + } + if (vsync) { + if (SDL_GL_SetSwapInterval(!strcmp("on", vsync)) < 0) { + warning("Failed to set vsync to %s: %s\n", vsync, SDL_GetError()); + } + } + } else { + warning("OpenGL 2.0 is unavailable, falling back to SDL2 renderer\n"); + } + } + if (!render_gl) { +#endif + flags = SDL_RENDERER_ACCELERATED; + if (!strcmp("on", vsync) || !strcmp("tear", vsync)) { + flags |= SDL_RENDERER_PRESENTVSYNC; + } + main_renderer = SDL_CreateRenderer(main_window, -1, flags); + + if (!main_renderer) { + fatal_error("unable to create SDL renderer: %s\n", SDL_GetError()); + } + SDL_RendererInfo rinfo; + SDL_GetRendererInfo(main_renderer, &rinfo); + printf("SDL2 Render Driver: %s\n", rinfo.name); + main_clip.x = main_clip.y = 0; + main_clip.w = main_width; + main_clip.h = main_height; +#ifndef DISABLE_OPENGL + } +#endif + + SDL_GetWindowSize(main_window, &main_width, &main_height); + printf("Window created with size: %d x %d\n", main_width, main_height);*/ + update_aspect(); + render_alloc_surfaces(); + def.ptrval = "off"; + scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def, TVAL_PTR).ptrval, "on"); +} + +void restore_tty(void) +{ + ioctl(STDIN_FILENO, KDSETMODE, KD_TEXT); +} + +void render_init(int width, int height, char * title, uint8_t fullscreen) +{ + if (height <= 0) { + float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; + height = ((float)width / aspect) + 0.5f; + } + printf("width: %d, height: %d\n", width, height); + windowed_width = width; + windowed_height = height; + + main_width = width; + main_height = height; + + caption = title; + + if (isatty(STDIN_FILENO)) { + ioctl(STDIN_FILENO, KDSETMODE, KD_GRAPHICS); + atexit(restore_tty); + } + + window_setup(); + + init_audio(); + + render_set_video_standard(VID_NTSC); + + DIR *d = opendir("/dev/input"); + struct dirent* entry; + int joystick_counter = 0; + while ((entry = readdir(d)) && cur_devices < MAX_DEVICES) + { + if (!strncmp("event", entry->d_name, strlen("event"))) { + char *filename = alloc_concat("/dev/input/", entry->d_name); + int fd = open(filename, O_RDONLY); + if (fd == -1) { + int errnum = errno; + warning("Failed to open evdev device %s for reading: %s\n", filename, strerror(errnum)); + free(filename); + continue; + } + + unsigned long bits; + if (-1 == ioctl(fd, EVIOCGBIT(0, sizeof(bits)), &bits)) { + int errnum = errno; + warning("Failed get capability bits from evdev device %s: %s\n", filename, strerror(errnum)); + free(filename); + close(fd); + continue; + } + if (!(1 & bits >> EV_KEY)) { + //if it doesn't support key events we don't care about it + free(filename); + close(fd); + continue; + } + unsigned long button_bits[(BTN_THUMBR+8*sizeof(long))/(8*sizeof(long))]; + int res = ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(button_bits)), button_bits); + if (-1 == res) { + int errnum = errno; + warning("Failed get capability bits from evdev device %s: %s\n", filename, strerror(errnum)); + free(filename); + close(fd); + continue; + } + int to_check[] = {KEY_ENTER, BTN_MOUSE, BTN_GAMEPAD}; + device_type dtype = DEV_NONE; + for (int i = 0; i < 4; i++) + { + if (1 & button_bits[to_check[i]/(8*sizeof(button_bits[0]))] >> to_check[i]%(8*sizeof(button_bits[0]))) { + dtype = i + 1; + } + } + if (dtype == DEV_NONE) { + close(fd); + } else { + device_fds[cur_devices] = fd; + device_types[cur_devices] = dtype; + char name[1024]; + char *names[] = {"Keyboard", "Mouse", "Gamepad"}; + ioctl(fd, EVIOCGNAME(sizeof(name)), name); + printf("%s is a %s\n%s\n", filename, names[dtype - 1], name); + //set FD to non-blocking mode for event polling + fcntl(fd, F_SETFL, O_NONBLOCK); + if (dtype == DEV_GAMEPAD) { + handle_joy_added(joystick_counter++); + } + cur_devices++; + } + free(filename); + } + } + + atexit(render_quit); +} +#include +static int in_toggle; +static void update_source(audio_source *src, double rc, uint8_t sync_changed) +{ + double alpha = src->dt / (src->dt + rc); + int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha); + src->lowpass_alpha = lowpass_alpha; +} + +void render_config_updated(void) +{ + + free_surfaces(); +#ifndef DISABLE_OPENGL + if (render_gl) { + /*if (on_context_destroyed) { + on_context_destroyed(); + }*/ + gl_teardown(); + //FIXME: EGL equivalent + //SDL_GL_DeleteContext(main_context); + } else { +#endif +#ifndef DISABLE_OPENGL + } +#endif + //FIXME: EGL equivalent + //SDL_DestroyWindow(main_window); + drain_events(); + + char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval; + if (config_width) { + windowed_width = atoi(config_width); + } + char *config_height = tern_find_path(config, "video\0height\0", TVAL_PTR).ptrval; + if (config_height) { + windowed_height = atoi(config_height); + } else { + float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; + windowed_height = ((float)windowed_width / aspect) + 0.5f; + } + + window_setup(); + update_aspect(); +#ifndef DISABLE_OPENGL + //need to check render_gl again after window_setup as render option could have changed + /*if (render_gl && on_context_created) { + on_context_created(); + }*/ +#endif + + render_close_audio(); + quitting = 0; + init_audio(); + render_set_video_standard(video_standard); + + double lowpass_cutoff = get_lowpass_cutoff(config); + double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); + for (uint8_t i = 0; i < num_audio_sources; i++) + { + update_source(audio_sources[i], rc, 0); + } + for (uint8_t i = 0; i < num_inactive_audio_sources; i++) + { + update_source(inactive_audio_sources[i], rc, 0); + } + drain_events(); +} + +void render_set_video_standard(vid_std std) +{ + video_standard = std; +} + +void render_update_caption(char *title) +{ + caption = title; + free(fps_caption); + fps_caption = NULL; +} + +static char *screenshot_path; +void render_save_screenshot(char *path) +{ + if (screenshot_path) { + free(screenshot_path); + } + screenshot_path = path; +} + +uint8_t render_create_window(char *caption, uint32_t width, uint32_t height, window_close_handler close_handler) +{ + //not supported under fbdev + return 0; +} + +void render_destroy_window(uint8_t which) +{ + //not supported under fbdev +} + +uint32_t *locked_pixels; +uint32_t locked_pitch; +uint32_t texture_off; +uint32_t *render_get_framebuffer(uint8_t which, int *pitch) +{ + if (max_multiple == 1 && !render_gl) { + *pitch = fb_stride; + return framebuffer; + } + *pitch = LINEBUF_SIZE * sizeof(uint32_t); + return texture_buf + texture_off; + /* +#ifndef DISABLE_OPENGL + if (render_gl && which <= FRAMEBUFFER_EVEN) { + *pitch = LINEBUF_SIZE * sizeof(uint32_t); + return texture_buf; + } else { +#endif + if (which >= num_textures) { + warning("Request for invalid framebuffer number %d\n", which); + return NULL; + } + void *pixels; + if (SDL_LockTexture(sdl_textures[which], NULL, &pixels, pitch) < 0) { + warning("Failed to lock texture: %s\n", SDL_GetError()); + return NULL; + } + static uint8_t last; + if (which <= FRAMEBUFFER_EVEN) { + locked_pixels = pixels; + if (which == FRAMEBUFFER_EVEN) { + pixels += *pitch; + } + locked_pitch = *pitch; + if (which != last) { + *pitch *= 2; + } + last = which; + } + return pixels; +#ifndef DISABLE_OPENGL + } +#endif*/ +} + +uint8_t events_processed; +#ifdef __ANDROID__ +#define FPS_INTERVAL 10000 +#else +#define FPS_INTERVAL 1000 +#endif + +static uint8_t interlaced; +void render_update_display(); +void render_framebuffer_updated(uint8_t which, int width) +{ + uint32_t height = which <= FRAMEBUFFER_EVEN + ? (video_standard == VID_NTSC ? 243 : 294) - (overscan_top[video_standard] + overscan_bot[video_standard]) + : 240; + width -= overscan_left[video_standard] + overscan_right[video_standard]; +#ifndef DISABLE_OPENGL + if (render_gl && which <= FRAMEBUFFER_EVEN) { + last_width = width; + glBindTexture(GL_TEXTURE_2D, textures[which]); + 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]); + render_update_display(); + last_height = height; + } else { +#endif + if (max_multiple != 1) { + if (copy_use_thread) { + pthread_mutex_lock(&buffer_lock); + buffer_ready = 1; + last_width = width; + last_height = height; + copy_buffer = texture_buf + texture_off + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]; + texture_off = texture_off ? 0 : LINEBUF_SIZE * 512; + pthread_cond_signal(&buffer_cond); + pthread_mutex_unlock(&buffer_lock); + } else { + last_width = width; + last_height = height; + copy_buffer = texture_buf + texture_off + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]; + do_buffer_copy(); + } + } + if (!events_processed) { + process_events(); + } + events_processed = 0; +#ifndef DISABLE_OPENGL + } +#endif + /* + FILE *screenshot_file = NULL; + uint32_t shot_height, shot_width; + char *ext; + if (screenshot_path && which == FRAMEBUFFER_ODD) { + screenshot_file = fopen(screenshot_path, "wb"); + if (screenshot_file) { +#ifndef DISABLE_ZLIB + ext = path_extension(screenshot_path); +#endif + info_message("Saving screenshot to %s\n", screenshot_path); + } else { + warning("Failed to open screenshot file %s for writing\n", screenshot_path); + } + free(screenshot_path); + screenshot_path = NULL; + shot_height = video_standard == VID_NTSC ? 243 : 294; + shot_width = width; + } + interlaced = last != which; + width -= overscan_left[video_standard] + overscan_right[video_standard]; +#ifndef DISABLE_OPENGL + if (render_gl && which <= FRAMEBUFFER_EVEN) { + SDL_GL_MakeCurrent(main_window, main_context); + glBindTexture(GL_TEXTURE_2D, textures[which]); + 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]); + + if (screenshot_file) { + //properly supporting interlaced modes here is non-trivial, so only save the odd field for now +#ifndef DISABLE_ZLIB + if (!strcasecmp(ext, "png")) { + free(ext); + save_png(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); + } else { + free(ext); +#endif + save_ppm(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); +#ifndef DISABLE_ZLIB + } +#endif + } + } else { +#endif + if (which <= FRAMEBUFFER_EVEN && last != which) { + uint8_t *cur_dst = (uint8_t *)locked_pixels; + uint8_t *cur_saved = (uint8_t *)texture_buf; + uint32_t dst_off = which == FRAMEBUFFER_EVEN ? 0 : locked_pitch; + uint32_t src_off = which == FRAMEBUFFER_EVEN ? locked_pitch : 0; + for (int i = 0; i < height; ++i) + { + //copy saved line from other field + memcpy(cur_dst + dst_off, cur_saved, locked_pitch); + //save line from this field to buffer for next frame + memcpy(cur_saved, cur_dst + src_off, locked_pitch); + cur_dst += locked_pitch * 2; + cur_saved += locked_pitch; + } + height = 480; + } + if (screenshot_file) { + uint32_t shot_pitch = locked_pitch; + if (which == FRAMEBUFFER_EVEN) { + shot_height *= 2; + } else { + shot_pitch *= 2; + } +#ifndef DISABLE_ZLIB + if (!strcasecmp(ext, "png")) { + free(ext); + save_png(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch); + } else { + free(ext); +#endif + save_ppm(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch); +#ifndef DISABLE_ZLIB + } +#endif + } + SDL_UnlockTexture(sdl_textures[which]); +#ifndef DISABLE_OPENGL + } +#endif + last_height = height; + if (which <= FRAMEBUFFER_EVEN) { + render_update_display(); + } else { + SDL_RenderCopy(extra_renderers[which - FRAMEBUFFER_USER_START], sdl_textures[which], NULL, NULL); + SDL_RenderPresent(extra_renderers[which - FRAMEBUFFER_USER_START]); + } + if (screenshot_file) { + fclose(screenshot_file); + } + if (which <= FRAMEBUFFER_EVEN) { + last = which; + static uint32_t frame_counter, start; + frame_counter++; + last_frame= SDL_GetTicks(); + if ((last_frame - start) > FPS_INTERVAL) { + if (start && (last_frame-start)) { + #ifdef __ANDROID__ + info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); + #else + if (!fps_caption) { + fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1); + } + sprintf(fps_caption, "%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); + SDL_SetWindowTitle(main_window, fps_caption); + #endif + } + start = last_frame; + frame_counter = 0; + } + } + if (!sync_to_audio) { + int32_t local_cur_min, local_min_remaining; + SDL_LockAudio(); + if (last_buffered > NO_LAST_BUFFERED) { + average_change *= 0.9f; + average_change += (cur_min_buffered - last_buffered) * 0.1f; + } + local_cur_min = cur_min_buffered; + local_min_remaining = min_remaining_buffer; + last_buffered = cur_min_buffered; + SDL_UnlockAudio(); + float frames_to_problem; + if (average_change < 0) { + frames_to_problem = (float)local_cur_min / -average_change; + } else { + frames_to_problem = (float)local_min_remaining / average_change; + } + float adjust_ratio = 0.0f; + if ( + frames_to_problem < BUFFER_FRAMES_THRESHOLD + || (average_change < 0 && local_cur_min < 3*min_buffered / 4) + || (average_change >0 && local_cur_min > 5 * min_buffered / 4) + || cur_min_buffered < 0 + ) { + + if (cur_min_buffered < 0) { + adjust_ratio = max_adjust; + SDL_PauseAudio(1); + last_buffered = NO_LAST_BUFFERED; + cur_min_buffered = 0; + } else { + adjust_ratio = -1.0 * average_change / ((float)sample_rate / (float)source_hz); + adjust_ratio /= 2.5 * source_hz; + if (fabsf(adjust_ratio) > max_adjust) { + adjust_ratio = adjust_ratio > 0 ? max_adjust : -max_adjust; + } + } + } else if (local_cur_min < min_buffered / 2) { + adjust_ratio = max_adjust; + } + if (adjust_ratio != 0.0f) { + average_change = 0; + for (uint8_t i = 0; i < num_audio_sources; i++) + { + audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5; + } + } + while (source_frame_count > 0) + { + render_update_display(); + source_frame_count--; + } + source_frame++; + if (source_frame >= source_hz) { + source_frame = 0; + } + source_frame_count = frame_repeat[source_frame]; + }*/ +} +/* +static ui_render_fun render_ui; +void render_set_ui_render_fun(ui_render_fun fun) +{ + render_ui = fun; +} +*/ +void render_update_display() +{ +#ifndef DISABLE_OPENGL + if (render_gl) { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(program); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, textures[0]); + glUniform1i(un_textures[0], 0); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, textures[interlaced ? 1 : scanlines ? 2 : 0]); + glUniform1i(un_textures[1], 1); + + glUniform1f(un_width, render_emulated_width()); + glUniform1f(un_height, last_height); + + glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); + glVertexAttribPointer(at_pos, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat[2]), (void *)0); + glEnableVertexAttribArray(at_pos); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); + glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, (void *)0); + + glDisableVertexAttribArray(at_pos); + + /*if (render_ui) { + render_ui(); + }*/ + + eglSwapBuffers(egl_display, main_surface); + } +#endif + if (!events_processed) { + process_events(); + } + events_processed = 0; +} + +uint32_t render_emulated_width() +{ + return last_width - overscan_left[video_standard] - overscan_right[video_standard]; +} + +uint32_t render_emulated_height() +{ + return (video_standard == VID_NTSC ? 243 : 294) - overscan_top[video_standard] - overscan_bot[video_standard]; +} + +uint32_t render_overscan_left() +{ + return overscan_left[video_standard]; +} + +uint32_t render_overscan_top() +{ + return overscan_top[video_standard]; +} + +void render_wait_quit(vdp_context * context) +{ + for(;;) + { + drain_events(); + sleep(1); + } +} + +int render_lookup_button(char *name) +{ + static tern_node *button_lookup; + if (!button_lookup) { + //xbox/sdl style names + button_lookup = tern_insert_int(button_lookup, "a", BTN_SOUTH); + button_lookup = tern_insert_int(button_lookup, "b", BTN_EAST); + button_lookup = tern_insert_int(button_lookup, "x", BTN_WEST); + button_lookup = tern_insert_int(button_lookup, "y", BTN_NORTH); + button_lookup = tern_insert_int(button_lookup, "back", BTN_SELECT); + button_lookup = tern_insert_int(button_lookup, "start", BTN_START); + button_lookup = tern_insert_int(button_lookup, "guid", BTN_MODE); + button_lookup = tern_insert_int(button_lookup, "leftshoulder", BTN_TL); + button_lookup = tern_insert_int(button_lookup, "rightshoulder", BTN_TR); + button_lookup = tern_insert_int(button_lookup, "leftstick", BTN_THUMBL); + button_lookup = tern_insert_int(button_lookup, "rightstick", BTN_THUMBR); + //alternative Playstation-style names + button_lookup = tern_insert_int(button_lookup, "cross", BTN_SOUTH); + button_lookup = tern_insert_int(button_lookup, "circle", BTN_EAST); + button_lookup = tern_insert_int(button_lookup, "square", BTN_WEST); + button_lookup = tern_insert_int(button_lookup, "triangle", BTN_NORTH); + button_lookup = tern_insert_int(button_lookup, "share", BTN_SELECT); + button_lookup = tern_insert_int(button_lookup, "select", BTN_SELECT); + button_lookup = tern_insert_int(button_lookup, "options", BTN_START); + button_lookup = tern_insert_int(button_lookup, "l1", BTN_TL); + button_lookup = tern_insert_int(button_lookup, "r1", BTN_TR); + button_lookup = tern_insert_int(button_lookup, "l3", BTN_THUMBL); + button_lookup = tern_insert_int(button_lookup, "r3", BTN_THUMBR); + } + return (int)tern_find_int(button_lookup, name, KEY_CNT); +} + +int render_lookup_axis(char *name) +{ + static tern_node *axis_lookup; + if (!axis_lookup) { + //xbox/sdl style names + axis_lookup = tern_insert_int(axis_lookup, "leftx", ABS_X); + axis_lookup = tern_insert_int(axis_lookup, "lefty", ABS_Y); + axis_lookup = tern_insert_int(axis_lookup, "lefttrigger", ABS_Z); + axis_lookup = tern_insert_int(axis_lookup, "rightx", ABS_RX); + axis_lookup = tern_insert_int(axis_lookup, "righty", ABS_RY); + axis_lookup = tern_insert_int(axis_lookup, "righttrigger", ABS_RZ); + //alternative Playstation-style names + axis_lookup = tern_insert_int(axis_lookup, "l2", ABS_Z); + axis_lookup = tern_insert_int(axis_lookup, "r2", ABS_RZ); + } + return (int)tern_find_int(axis_lookup, name, ABS_CNT); +} + +int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis) +{ + tern_node *button_lookup, *axis_lookup; + if (is_axis) { + int axis = render_lookup_axis(name); + if (axis == ABS_CNT) { + return RENDER_INVALID_NAME; + } + return RENDER_AXIS_BIT | axis; + } else { + int button = render_lookup_button(name); + if (button != KEY_CNT) { + return button; + } + if (!strcmp("dpup", name)) { + return RENDER_DPAD_BIT | 1; + } + if (!strcmp("dpdown", name)) { + return RENDER_DPAD_BIT | 4; + } + if (!strcmp("dpdleft", name)) { + return RENDER_DPAD_BIT | 8; + } + if (!strcmp("dpright", name)) { + return RENDER_DPAD_BIT | 2; + } + return RENDER_INVALID_NAME; + } +} + +int32_t render_dpad_part(int32_t input) +{ + return input >> 4 & 0xFFFFFF; +} + +uint8_t render_direction_part(int32_t input) +{ + return input & 0xF; +} + +int32_t render_axis_part(int32_t input) +{ + return input & 0xFFFFFFF; +} + +void process_events() +{ + if (events_processed > MAX_EVENT_POLL_PER_FRAME) { + return; + } + drain_events(); + events_processed++; +} + +#define TOGGLE_MIN_DELAY 250 +void render_toggle_fullscreen() +{ + //always fullscreen in fbdev +} + +uint32_t render_audio_buffer() +{ + return buffer_samples; +} + +uint32_t render_sample_rate() +{ + return sample_rate; +} + +void render_errorbox(char *title, char *message) +{ + +} + +void render_warnbox(char *title, char *message) +{ + +} + +void render_infobox(char *title, char *message) +{ + +} + +uint8_t render_has_gl(void) +{ + return render_gl; +} + +uint8_t render_get_active_framebuffer(void) +{ + return FRAMEBUFFER_ODD; +} diff -r 8a29c250f352 -r 3a8c4ee68568 special_keys_evdev.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/special_keys_evdev.h Sun Mar 10 21:30:09 2019 -0700 @@ -0,0 +1,59 @@ +#ifndef SPECIAL_KEYS_EVDEV_H_ +#define SPECIAL_KEYS_EVDEV_H_ + +enum { + RENDERKEY_DOWN = 128, + RENDERKEY_UP, + RENDERKEY_LEFT, + RENDERKEY_RIGHT, + RENDERKEY_ESC, + RENDERKEY_DEL, + RENDERKEY_LSHIFT, + RENDERKEY_RSHIFT, + RENDERKEY_LCTRL, + RENDERKEY_RCTRL, + RENDERKEY_LALT, + RENDERKEY_RALT, + RENDERKEY_HOME, + RENDERKEY_END, + RENDERKEY_PAGEUP, + RENDERKEY_PAGEDOWN, + RENDERKEY_F1, + RENDERKEY_F2, + RENDERKEY_F3, + RENDERKEY_F4, + RENDERKEY_F5, + RENDERKEY_F6, + RENDERKEY_F7, + RENDERKEY_F8, + RENDERKEY_F9, + RENDERKEY_F10, + RENDERKEY_F11, + RENDERKEY_F12, + RENDERKEY_SELECT, + RENDERKEY_PLAY, + RENDERKEY_SEARCH, + RENDERKEY_BACK, + RENDERKEY_NP0, + RENDERKEY_NP1, + RENDERKEY_NP2, + RENDERKEY_NP3, + RENDERKEY_NP4, + RENDERKEY_NP5, + RENDERKEY_NP6, + RENDERKEY_NP7, + RENDERKEY_NP8, + RENDERKEY_NP9, + RENDERKEY_NP_DIV, + RENDERKEY_NP_MUL, + RENDERKEY_NP_MIN, + RENDERKEY_NP_PLUS, + RENDERKEY_NP_ENTER, + RENDERKEY_NP_STOP, + RENDER_DPAD_UP, + RENDER_DPAD_DOWN, + RENDER_DPAD_LEFT, + RENDER_DPAD_RIGHT +}; + +#endif //SPECIAL_KEYS_EVDEV_H_