# HG changeset patch # User Michael Pavone # Date 1552632050 25200 # Node ID 0c6d07f9134619173e1ef5ba53f4efda87ecc17f # Parent 30b5952fd32e697e84416a7d2b638422b7044128# Parent 2b661c1e431f183a345ea190248f80a193c40d48 Merge from default diff -r 30b5952fd32e -r 0c6d07f91346 Makefile --- a/Makefile Wed Mar 13 22:01:22 2019 -0700 +++ b/Makefile Thu Mar 14 23:40:50 2019 -0700 @@ -38,6 +38,14 @@ 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 ifdef GLES_LIB LIBS=sdl2 @@ -48,6 +56,7 @@ else LIBS=sdl2 glew gl endif #USE_GLES +endif #USE_FBDEV FONT:=nuklear_ui/font.o endif #Darwin @@ -92,6 +101,9 @@ else CFLAGS:=$(shell pkg-config --cflags-only-I $(LIBS)) $(CFLAGS) LDFLAGS:=-lm $(shell pkg-config --libs $(LIBS)) $(GLES_LIB) +ifdef USE_FBDEV +LDFLAGS+= -pthread +endif endif #libblastem.so ifeq ($(OS),Darwin) @@ -173,8 +185,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 30b5952fd32e -r 0c6d07f91346 bindings.c --- a/bindings.c Wed Mar 13 22:01:22 2019 -0700 +++ b/bindings.c Thu Mar 14 23:40:50 2019 -0700 @@ -1,4 +1,5 @@ #include +#include #include "render.h" #include "system.h" #include "io.h" @@ -557,7 +558,7 @@ { const int gpadslen = strlen("gamepads."); const int mouselen = strlen("mouse."); - if (!strncmp(target, "gamepads.", gpadslen)) { + if (startswith(target, "gamepads.")) { int padnum = target[gpadslen] == 'n' ? device_num + 1 : target[gpadslen] - '0'; if (padnum >= 1 && padnum <= 8) { int button = tern_find_int(padbuttons, target + gpadslen + 1, 0); @@ -575,7 +576,7 @@ } else { warning("Gamepad mapping string '%s' refers to an invalid gamepad number %c\n", target, target[gpadslen]); } - } else if(!strncmp(target, "mouse.", mouselen)) { + } else if(startswith(target, "mouse.")) { int mousenum = target[mouselen] == 'n' ? device_num + 1 : target[mouselen] - '0'; if (mousenum >= 1 && mousenum <= 8) { int button = tern_find_int(mousebuttons, target + mouselen + 1, 0); @@ -593,7 +594,7 @@ } else { warning("Gamepad mapping string '%s' refers to an invalid mouse number %c\n", target, target[mouselen]); } - } else if(!strncmp(target, "ui.", strlen("ui."))) { + } else if(startswith(target, "ui.")) { if (!strcmp(target + 3, "vdp_debug_mode")) { *subtype_a = UI_DEBUG_MODE_INC; } else if(!strcmp(target + 3, "vdp_debug_pal")) { @@ -603,7 +604,7 @@ *subtype_a = UI_ENTER_DEBUGGER; } else if(!strcmp(target + 3, "save_state")) { *subtype_a = UI_SAVE_STATE; - } else if(!strncmp(target + 3, "set_speed.", strlen("set_speed."))) { + } else if(startswith(target + 3, "set_speed.")) { *subtype_a = UI_SET_SPEED; *subtype_b = atoi(target + 3 + strlen("set_speed.")); } else if(!strcmp(target + 3, "next_speed")) { diff -r 30b5952fd32e -r 0c6d07f91346 config.c --- a/config.c Wed Mar 13 22:01:22 2019 -0700 +++ b/config.c Thu Mar 14 23:40:50 2019 -0700 @@ -49,11 +49,11 @@ curline = strip_ws(curline); int len = strlen(curline); if (!len) { - *line = *line + 1; + (*line)++; continue; } if (curline[0] == '#') { - *line = *line + 1; + (*line)++; continue; } if (curline[0] == '}') { @@ -67,7 +67,7 @@ if (*end == '{') { *end = 0; curline = strip_ws(curline); - *line = *line + 1; + (*line)++; head = tern_insert_node(head, curline, parse_config_int(state, 1, line)); } else { char * val = strip_ws(split_keyval(curline)); @@ -77,7 +77,7 @@ } else { fprintf(stderr, "Key %s is missing a value on line %d\n", key, *line); } - *line = *line + 1; + (*line)++; } } return head; @@ -174,11 +174,10 @@ if (!config_size) { goto config_empty; } - char * config_data = malloc(config_size+1); + char *config_data = calloc(config_size + 1, 1); if (fread(config_data, 1, config_size, config_file) != config_size) { goto config_read_fail; } - config_data[config_size] = '\0'; ret = parse_config(config_data); config_read_fail: diff -r 30b5952fd32e -r 0c6d07f91346 controller_info.c --- a/controller_info.c Wed Mar 13 22:01:22 2019 -0700 +++ b/controller_info.c Thu Mar 14 23:40:50 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 30b5952fd32e -r 0c6d07f91346 io.c --- a/io.c Wed Mar 13 22:01:22 2019 -0700 +++ b/io.c Thu Mar 14 23:40:50 2019 -0700 @@ -219,8 +219,7 @@ } const int gamepad_len = strlen("gamepad"); - const int mouse_len = strlen("mouse"); - if (!strncmp(device_type, "gamepad", gamepad_len)) + if (startswith(device_type, "gamepad")) { if ( (device_type[gamepad_len] != '3' && device_type[gamepad_len] != '6' && device_type[gamepad_len] != '2') @@ -236,10 +235,10 @@ port->device_type = IO_GAMEPAD6; } port->device.pad.gamepad_num = device_type[gamepad_len+2] - '0'; - } else if(!strncmp(device_type, "mouse", mouse_len)) { + } else if(startswith(device_type, "mouse")) { if (port->device_type != IO_MOUSE) { port->device_type = IO_MOUSE; - port->device.mouse.mouse_num = device_type[mouse_len+1] - '0'; + port->device.mouse.mouse_num = device_type[strlen("mouse")+1] - '0'; port->device.mouse.last_read_x = 0; port->device.mouse.last_read_y = 0; port->device.mouse.cur_x = 0; diff -r 30b5952fd32e -r 0c6d07f91346 m68k_core.c --- a/m68k_core.c Wed Mar 13 22:01:22 2019 -0700 +++ b/m68k_core.c Thu Mar 14 23:40:50 2019 -0700 @@ -1274,9 +1274,7 @@ m68k_context * init_68k_context(m68k_options * opts, m68k_reset_handler reset_handler) { #ifdef USE_NATIVE - size_t ctx_size = sizeof(m68k_context) + ram_size(&opts->gen) / (1 << opts->gen.ram_flags_shift) / 8; - m68k_context * context = malloc(ctx_size); - memset(context, 0, ctx_size); + m68k_context * context = calloc(1, sizeof(m68k_context) + ram_size(&opts->gen) / (1 << opts->gen.ram_flags_shift) / 8); context->options = opts; #else m68000_base_device *device = malloc(sizeof(m68000_base_device));; diff -r 30b5952fd32e -r 0c6d07f91346 render.h --- a/render.h Wed Mar 13 22:01:22 2019 -0700 +++ b/render.h Thu Mar 14 23:40:50 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 30b5952fd32e -r 0c6d07f91346 render_fbdev.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/render_fbdev.c Thu Mar 14 23:40:50 2019 -0700 @@ -0,0 +1,1863 @@ +/* + 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 + +#define MAX_FB_LINES 590 +static uint32_t texture_buf[MAX_FB_LINES * LINEBUF_SIZE * 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 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; +} + +char* render_joystick_type_id(int index) +{ + return strdup(""); +} + +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() +{ + char *device_name = tern_find_path_default(config, "audio\0alsa_device\0", (tern_val){.ptrval="default"}, TVAL_PTR).ptrval; + int res = snd_pcm_open(&audio_handle, device_name, 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_width_scale, last_height, last_height_scale; +static uint32_t max_multiple; + +static uint32_t mix_pixel(uint32_t last, uint32_t cur, float ratio) +{ + float a,b,c,d; + a = (last & 255) * ratio; + b = (last >> 8 & 255) * ratio; + c = (last >> 16 & 255) * ratio; + d = (last >> 24 & 255) * ratio; + ratio = 1.0f - ratio; + a += (cur & 255) * ratio; + b += (cur >> 8 & 255) * ratio; + c += (cur >> 16 & 255) * ratio; + d += (cur >> 24 & 255) * ratio; + return ((int)d) << 24 | ((int)c) << 16 | ((int)b) << 8 | ((int)a); +} +static void do_buffer_copy(void) +{ + uint32_t width_multiple = main_width / last_width_scale; + uint32_t height_multiple = main_height / last_height_scale; + uint32_t multiple = width_multiple < height_multiple ? width_multiple : height_multiple; + if (max_multiple && multiple > max_multiple) { + multiple = max_multiple; + } + height_multiple = last_height_scale * multiple / last_height; + uint32_t *cur_line = framebuffer + (main_width - last_width_scale * multiple)/2; + cur_line += fb_stride * (main_height - last_height_scale * multiple) / (2 * sizeof(uint32_t)); + uint32_t *src_line = copy_buffer; + if (height_multiple * last_height == multiple * last_height_scale) { + if (last_width == last_width_scale) { + for (uint32_t y = 0; y < last_height; y++) + { + for (uint32_t i = 0; i < height_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; + } + } else { + float scale_multiple = ((float)(last_width_scale * multiple)) / (float)last_width; + float remaining = 0.0f; + uint32_t last_pixel = 0; + for (uint32_t y = 0; y < last_height; y++) + { + for (uint32_t i = 0; i < height_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++); + float count = scale_multiple; + if (remaining > 0.0f) { + *(cur++) = mix_pixel(last_pixel, pixel, remaining); + count -= 1.0f - remaining; + } + for (; count >= 1; count -= 1.0f) + { + *(cur++) = pixel; + } + remaining = count; + last_pixel = pixel; + } + + cur_line += fb_stride / sizeof(uint32_t); + } + src_line += LINEBUF_SIZE; + } + } + } else { + float height_scale = ((float)(last_height_scale * multiple)) / (float)last_height; + float height_remaining = 0.0f; + uint32_t *last_line; + if (last_width == last_width_scale) { + for (uint32_t y = 0; y < last_height; y++) + { + float hcount = height_scale; + if (height_remaining > 0.0f) { + uint32_t *cur = cur_line; + uint32_t *src = src_line; + uint32_t *last = last_line; + for (uint32_t x = 0; x < last_width ; x++) + { + uint32_t mixed = mix_pixel(*(last++), *(src++), height_remaining); + for (uint32_t j = 0; j < multiple; j++) + { + *(cur++) = mixed; + } + } + hcount -= 1.0f - height_remaining; + cur_line += fb_stride / sizeof(uint32_t); + } + for(; hcount >= 1; hcount -= 1.0f) + { + 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); + } + height_remaining = hcount; + last_line = src_line; + src_line += LINEBUF_SIZE; + } + } else { + float scale_multiple = ((float)(last_width_scale * multiple)) / (float)last_width; + float remaining = 0.0f; + uint32_t last_pixel = 0; + for (uint32_t y = 0; y < last_height; y++) + { + float hcount = height_scale; + if (height_remaining > 0.0f) { + uint32_t *cur = cur_line; + uint32_t *src = src_line; + uint32_t *last = last_line; + + for (uint32_t x = 0; x < last_width; x++) + { + uint32_t pixel = mix_pixel(*(last++), *(src++), height_remaining); + float count = scale_multiple; + if (remaining > 0.0f) { + *(cur++) = mix_pixel(last_pixel, pixel, remaining); + count -= 1.0f - remaining; + } + for (; count >= 1.0f; count -= 1.0f) + { + *(cur++) = pixel; + } + remaining = count; + last_pixel = pixel; + } + hcount -= 1.0f - height_remaining; + cur_line += fb_stride / sizeof(uint32_t); + } + + for (; hcount >= 1.0f; hcount -= 1.0f) + { + uint32_t *cur = cur_line; + uint32_t *src = src_line; + for (uint32_t x = 0; x < last_width ; x++) + { + uint32_t pixel = *(src++); + float count = scale_multiple; + if (remaining > 0.0f) { + *(cur++) = mix_pixel(last_pixel, pixel, remaining); + count -= 1.0f - remaining; + } + for (; count >= 1; count -= 1.0f) + { + *(cur++) = pixel; + } + remaining = count; + last_pixel = pixel; + } + + cur_line += fb_stride / sizeof(uint32_t); + } + height_remaining = hcount; + last_line = src_line; + 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 + + 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); + for (int i = 0; i < cur_devices; i++) + { + if (device_types[i] == DEV_KEYBOARD) { + ioctl(device_fds[i], EVIOCGRAB, 0); + } + } +} + +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 < 3; 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); + + if (dtype == DEV_GAMEPAD) { + handle_joy_added(joystick_counter++); + } else if (dtype == DEV_KEYBOARD && isatty(STDIN_FILENO)) { + ioctl(fd, EVIOCGRAB, 1); + } + + //set FD to non-blocking mode for event polling + fcntl(fd, F_SETFL, O_NONBLOCK); + 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 +} + +static uint8_t last_fb; +static uint32_t texture_off; +uint32_t *render_get_framebuffer(uint8_t which, int *pitch) +{ + if (max_multiple == 1 && !render_gl) { + if (last_fb != which) { + *pitch = fb_stride * 2; + return framebuffer + (which == FRAMEBUFFER_EVEN ? fb_stride / sizeof(uint32_t) : 0); + } + *pitch = fb_stride; + return framebuffer; + } + if (!render_gl && last_fb != which) { + *pitch = LINEBUF_SIZE * sizeof(uint32_t) * 2; + return texture_buf + texture_off + (which == FRAMEBUFFER_EVEN ? LINEBUF_SIZE : 0); + } + *pitch = LINEBUF_SIZE * sizeof(uint32_t); + return texture_buf + texture_off; +} + +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_width_scale = LINEBUF_SIZE - (overscan_left[video_standard] + overscan_right[video_standard]); + last_height = last_height_scale = height; + copy_buffer = texture_buf + texture_off + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]; + if (which != last_fb) { + last_height *= 2; + copy_buffer += LINEBUF_SIZE * overscan_top[video_standard]; + 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]; + uint32_t *dst = copy_buffer; + if (which == FRAMEBUFFER_ODD) { + src += LINEBUF_SIZE; + dst += LINEBUF_SIZE; + } + for (int i = 0; i < height; i++) + { + memcpy(dst, src, width * sizeof(uint32_t)); + src += LINEBUF_SIZE * 2; + dst += LINEBUF_SIZE * 2; + } + } + texture_off = texture_off ? 0 : LINEBUF_SIZE * MAX_FB_LINES; + pthread_cond_signal(&buffer_cond); + pthread_mutex_unlock(&buffer_lock); + } else { + last_width = width; + last_width_scale = LINEBUF_SIZE - (overscan_left[video_standard] + overscan_right[video_standard]); + last_height = last_height_scale = height; + copy_buffer = texture_buf + texture_off + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]; + if (which != last_fb) { + last_height *= 2; + copy_buffer += LINEBUF_SIZE * overscan_top[video_standard]; + } + do_buffer_copy(); + } + } + last_fb = which; + if (!events_processed) { + process_events(); + } + events_processed = 0; +#ifndef DISABLE_OPENGL + } +#endif +} + +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 30b5952fd32e -r 0c6d07f91346 special_keys_evdev.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/special_keys_evdev.h Thu Mar 14 23:40:50 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_ diff -r 30b5952fd32e -r 0c6d07f91346 util.c --- a/util.c Wed Mar 13 22:01:22 2019 -0700 +++ b/util.c Thu Mar 14 23:40:50 2019 -0700 @@ -189,6 +189,11 @@ return text+1; } +uint8_t startswith(const char *haystack, const char *prefix) +{ + return !strncmp(haystack, prefix, strlen(prefix)); +} + void bin_to_hex(uint8_t *output, uint8_t *input, uint64_t size) { while (size) diff -r 30b5952fd32e -r 0c6d07f91346 util.h --- a/util.h Wed Mar 13 22:01:22 2019 -0700 +++ b/util.h Thu Mar 14 23:40:50 2019 -0700 @@ -32,6 +32,8 @@ char * strip_ws(char * text); //Inserts a null after the first word, returns a pointer to the second word char * split_keyval(char * text); +//Checks if haystack starts with prefix +uint8_t startswith(const char *haystack, const char *prefix); //Takes a binary byte buffer and produces a lowercase hex string void bin_to_hex(uint8_t *output, uint8_t *input, uint64_t size); //Takes an (optionally) null-terminated UTF16-BE string and converts a maximum of max_size code-units to UTF-8 diff -r 30b5952fd32e -r 0c6d07f91346 vdp.c --- a/vdp.c Wed Mar 13 22:01:22 2019 -0700 +++ b/vdp.c Thu Mar 14 23:40:50 2019 -0700 @@ -51,7 +51,7 @@ #define BORDER_BOT_V28_PAL 32 #define BORDER_BOT_V30_PAL 24 -#define INVALID_LINE 0x200 +#define INVALID_LINE (PAL_INACTIVE_START+BORDER_TOP_V30_PAL+BORDER_BOT_V30_PAL) enum { INACTIVE = 0, diff -r 30b5952fd32e -r 0c6d07f91346 z80_util.c --- a/z80_util.c Wed Mar 13 22:01:22 2019 -0700 +++ b/z80_util.c Thu Mar 14 23:40:50 2019 -0700 @@ -228,12 +228,108 @@ void z80_serialize(z80_context *context, serialize_buffer *buf) { - //TODO: Implement me + save_int8(buf, context->main[1]);//C + save_int8(buf, context->main[0]);//B + save_int8(buf, context->main[3]);//E + save_int8(buf, context->main[2]);//D + save_int8(buf, context->main[5]);//L + save_int8(buf, context->main[4]);//H + save_int8(buf, context->ix);//IXL + save_int8(buf, context->ix >> 8);//IXH + save_int8(buf, context->iy);//IYL + save_int8(buf, context->iy >> 8);//IYH + save_int8(buf, context->i); + save_int8(buf, (context->rhigh & 0x80) | (context->r & 0x7F)); + save_int8(buf, context->main[7]);//A + uint8_t f = context->last_flag_result & 0xA8 + | (context->zflag ? 0x40 : 0) + | (context->chflags & 8 ? 0x10 : 0) + | (context->pvflag ? 4 : 0) + | (context->nflag ? 2 : 0) + | (context->chflags & 0x80 ? 1 : 0); + save_int8(buf, f); + save_int8(buf, context->alt[1]);//C + save_int8(buf, context->alt[0]);//B + save_int8(buf, context->alt[3]);//E + save_int8(buf, context->alt[2]);//D + save_int8(buf, context->alt[5]);//L + save_int8(buf, context->alt[4]);//H + save_int8(buf, 0);//non-existant alt ixl + save_int8(buf, 0);//non-existant alt ixh + save_int8(buf, 0);//non-existant alt iyl + save_int8(buf, 0);//non-existant alt iyh + save_int8(buf, 0);//non-existant alt i + save_int8(buf, 0);//non-existant alt r + save_int8(buf, context->alt[7]);//A + save_int8(buf, context->alt[6]);//F + + save_int16(buf, context->pc); + save_int16(buf, context->sp); + save_int8(buf, context->imode); + save_int8(buf, context->iff1); + save_int8(buf, context->iff2); + uint8_t is_nmi = context->nmi_cycle != 0xFFFFFFFF && (context->nmi_cycle < context->int_cycle || !context->iff1); + save_int8(buf, is_nmi);//int_is_nmi + save_int8(buf, context->busack); + save_int32(buf, context->cycles); + save_int32(buf, is_nmi ? context->nmi_cycle : context->int_cycle);//int_cycle + save_int32(buf, 0);//int_enable_cycle + save_int32(buf, context->int_cycle); + save_int32(buf, context->int_end_cycle); + save_int32(buf, context->nmi_cycle); } void z80_deserialize(deserialize_buffer *buf, void *vcontext) { - //TODO: Implement me + z80_context *context = vcontext; + context->main[1] = load_int8(buf);//C + context->main[0] = load_int8(buf);//B + context->main[3] = load_int8(buf);//E + context->main[2] = load_int8(buf);//D + context->main[5] = load_int8(buf);//L + context->main[4] = load_int8(buf);//H + context->ix = load_int8(buf);//IXL + context->ix |= load_int8(buf) << 8;//IXH + context->iy = load_int8(buf);//IYL + context->iy |= load_int8(buf) << 8;//IYH + context->i = load_int8(buf); + context->r = load_int8(buf); + context->rhigh = context->r & 0x80; + context->main[7] = load_int8(buf);//A + context->last_flag_result = load_int8(buf); + context->zflag = context->last_flag_result & 0x40; + context->chflags = context->last_flag_result & 0x10 ? 8 : 0; + context->pvflag = context->last_flag_result & 4; + context->nflag = context->last_flag_result & 2; + context->chflags |= context->last_flag_result & 1 ? 0x80 : 0; + context->alt[1] = load_int8(buf);//C + context->alt[0] = load_int8(buf);//B + context->alt[3] = load_int8(buf);//E + context->alt[2] = load_int8(buf);//D + context->alt[5] = load_int8(buf);//L + context->alt[4] = load_int8(buf);//H + load_int8(buf);//non-existant alt ixl + load_int8(buf);//non-existant alt ixh + load_int8(buf);//non-existant alt iyl + load_int8(buf);//non-existant alt iyh + load_int8(buf);//non-existant alt i + load_int8(buf);//non-existant alt r + context->alt[7] = load_int8(buf);//A + context->alt[6] = load_int8(buf);//F + + context->pc = load_int16(buf); + context->sp = load_int16(buf); + context->imode = load_int8(buf); + context->iff1 = load_int8(buf); + context->iff2 = load_int8(buf); + load_int8(buf);//int_is_nmi + context->busack = load_int8(buf); + context->cycles = load_int32(buf); + load_int32(buf);//int_cycle + load_int32(buf);//int_enable_cycle + context->int_cycle = load_int32(buf); + context->int_end_cycle = load_int32(buf); + context->nmi_cycle = load_int32(buf); } void zinsert_breakpoint(z80_context * context, uint16_t address, uint8_t * bp_handler)