diff --git a/Makefile b/Makefile index 3972d52e75..1c42120fc7 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,19 @@ TARGET = ssnes OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o -LIBS = -lsamplerate +LIBS = +DEFINES = -DHAVE_CONFIG_H + +ifeq ($(HAVE_SRC), 1) + LIBS += $(SRC_LIBS) + DEFINES += $(SRC_CFLAGS) +endif ifeq ($(HAVE_RSOUND), 1) OBJ += audio/rsound.o LIBS += -lrsound endif + ifeq ($(HAVE_OSS), 1) OBJ += audio/oss.o endif @@ -30,15 +37,23 @@ ifeq ($(HAVE_JACK),1) LIBS += -ljack endif -ifeq ($(HAVE_GLFW), 1) - OBJ += gfx/gl.o - LIBS += -lglfw +ifeq ($(HAVE_SDL), 1) + OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o + LIBS += $(SDL_LIBS) -lGL + DEFINES += $(SDL_CFLAGS) endif ifeq ($(HAVE_CG), 1) + OBJ += gfx/shader_cg.o LIBS += -lCg -lCgGL endif +ifeq ($(HAVE_XML), 1) + OBJ += gfx/shader_glsl.o + LIBS += $(XML_LIBS) + DEFINES += $(XML_CFLAGS) +endif + ifeq ($(HAVE_FILTER), 1) OBJ += hqflt/hq.o OBJ += hqflt/grayscale.o @@ -47,6 +62,12 @@ ifeq ($(HAVE_FILTER), 1) OBJ += hqflt/snes_ntsc/snes_ntsc.o endif +ifeq ($(HAVE_FFMPEG), 1) + OBJ += record/ffemu.o + LIBS += $(AVCODEC_LIBS) $(AVCORE_LIBS) $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) + DEFINES += $(AVCODEC_CFLAGS) $(AVCORE_CFLAGS) $(AVFORMAT_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS) +endif + ifeq ($(HAVE_DYNAMIC), 1) LIBS += -ldl else @@ -62,13 +83,13 @@ config.mk: configure qb/* @exit 1 ssnes: $(OBJ) - $(CXX) -o $@ $(OBJ) $(LIBS) $(CFLAGS) + $(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS) %.o: %.c config.h config.mk - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CFLAGS) $(DEFINES) -c -o $@ $< install: $(TARGET) - install -m755 $(TARGET) $(DESTDIR)/$(PREFIX)/bin + install -m755 $(TARGET) $(DESTDIR)$(PREFIX)/bin install -m644 ssnes.cfg $(DESTDIR)/etc/ssnes.cfg uninstall: $(TARGET) @@ -79,6 +100,7 @@ clean: rm -f audio/*.o rm -f conf/*.o rm -f gfx/*.o + rm -f record/*.o rm -f hqflt/*.o rm -f hqflt/snes_ntsc/*.o rm -f $(TARGET) diff --git a/Makefile.win32 b/Makefile.win32 new file mode 100644 index 0000000000..d66d1cc925 --- /dev/null +++ b/Makefile.win32 @@ -0,0 +1,59 @@ +TARGET = ssnes.exe +OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o + +CC = gcc +CXX = g++ + +HAVE_SRC = 1 +HAVE_SDL = 1 +HAVE_XML = 1 +libsnes ?= -lsnes + +LIBS = +DEFINES = -I. +LDFLAGS = -L. -static-libgcc + +SRC_LIBS = -lsamplerate-0 +SDL_LIBS = -lSDLmain -lSDL +SDL_CFLAGS = -ISDL + +ifeq ($(HAVE_SRC), 1) + LIBS += $(SRC_LIBS) + DEFINES += $(SRC_CFLAGS) -DHAVE_SRC +endif + +ifeq ($(HAVE_SDL), 1) + OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o + LIBS += $(SDL_LIBS) -lopengl32 + DEFINES += $(SDL_CFLAGS) -DHAVE_SDL +endif + +ifeq ($(HAVE_XML), 1) + OBJ += gfx/shader_glsl.o + DEFINES += $(XML_CFLAGS) -DHAVE_XML + LIBS += -lxml2 +endif + +LIBS += $(libsnes) + +CFLAGS = -Wall -O3 -g -std=gnu99 -I. + +all: $(TARGET) + +$(TARGET): $(OBJ) + $(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS) + +%.o: %.c + $(CC) $(CFLAGS) $(DEFINES) -c -o $@ $< + +clean: + rm -f *.o + rm -f audio/*.o + rm -f conf/*.o + rm -f gfx/*.o + rm -f record/*.o + rm -f hqflt/*.o + rm -f hqflt/snes_ntsc/*.o + rm -f $(TARGET) + +.PHONY: all install uninstall clean diff --git a/README.md b/README.md index a6ce0b1d0c..57a4be2f72 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,13 @@ It is used through command-line. SSNES requires these libraries to build: - [libsnes](http://byuu.org/bsnes/) - - GLFW + - SDL - libsamplerate SSNES can utilize these libraries if enabled: - nvidia-cg-toolkit + - libxml2 (bSNES XML shaders) SSNES needs one of these audio driver libraries: @@ -67,13 +68,14 @@ Do note that these two options are mutually exclusive. -# Filters and Cg shader support +# Filters, bSNES XML shaders and Cg shader support This is not strictly not necessary for an emulator, but it can be enabled if desired. -For best performance, Cg shaders are recommended as they do not eat up valuable CPU time. -Cg shaders are compiled at run-time, and shaders could be dropped in. +For best performance, Cg shaders or bSNES XML shaders are recommended as they do not eat up valuable CPU time (assuming your GPU can handle the shaders). +Cg shaders and XML shaders (GLSL) are compiled at run-time. All shaders share a common interface to pass some essential arguments such as texture size and viewport size. (Common for pixel art scalers) Some Cg shaders are included in hqflt/cg/ and could be used as an example. +bSNES XML shaders can be found on various places on the net. Best place to start looking are the bSNES forums. -While these shaders are Cg, they closely resemble the GLSL shaders found in bSNES shader pack, so porting them is trivial. +The Cg shaders closely resemble the GLSL shaders found in bSNES shader pack, so porting them is trivial if desired. diff --git a/audio/buffer.c b/audio/buffer.c new file mode 100644 index 0000000000..f3df435acb --- /dev/null +++ b/audio/buffer.c @@ -0,0 +1,121 @@ +/* RSound - A PCM audio client/server + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * RSound is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RSound is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RSound. + * If not, see . + */ + +#include "buffer.h" + +struct rsound_fifo_buffer +{ + char *buffer; + size_t bufsize; + size_t first; + size_t end; +}; + +rsound_fifo_buffer_t* rsnd_fifo_new(size_t size) +{ + rsound_fifo_buffer_t *buf = calloc(1, sizeof(*buf)); + if (buf == NULL) + return NULL; + + buf->buffer = calloc(1, size + 1); + if (buf->buffer == NULL) + { + free(buf); + return NULL; + } + buf->bufsize = size + 1; + + return buf; +} + +void rsnd_fifo_free(rsound_fifo_buffer_t* buffer) +{ + assert(buffer); + assert(buffer->buffer); + + free(buffer->buffer); + free(buffer); +} + +size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer) +{ + assert(buffer); + assert(buffer->buffer); + + size_t first = buffer->first; + size_t end = buffer->end; + if (end < first) + end += buffer->bufsize; + return end - first; +} + +size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer) +{ + assert(buffer); + assert(buffer->buffer); + + size_t first = buffer->first; + size_t end = buffer->end; + if (end < first) + end += buffer->bufsize; + + return (buffer->bufsize - 1) - (end - first); +} + +void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t size) +{ + assert(buffer); + assert(buffer->buffer); + assert(in_buf); + assert(rsnd_fifo_write_avail(buffer) >= size); + + size_t first_write = size; + size_t rest_write = 0; + if (buffer->end + size > buffer->bufsize) + { + first_write = buffer->bufsize - buffer->end; + rest_write = size - first_write; + } + + memcpy(buffer->buffer + buffer->end, in_buf, first_write); + if (rest_write > 0) + memcpy(buffer->buffer, (const char*)in_buf + first_write, rest_write); + + buffer->end = (buffer->end + size) % buffer->bufsize; +} + + +void rsnd_fifo_read(rsound_fifo_buffer_t* buffer, void* in_buf, size_t size) +{ + assert(buffer); + assert(buffer->buffer); + assert(in_buf); + assert(rsnd_fifo_read_avail(buffer) >= size); + + size_t first_read = size; + size_t rest_read = 0; + if (buffer->first + size > buffer->bufsize) + { + first_read = buffer->bufsize - buffer->first; + rest_read = size - first_read; + } + + memcpy(in_buf, (const char*)buffer->buffer + buffer->first, first_read); + if (rest_read > 0) + memcpy((char*)in_buf + first_read, buffer->buffer, rest_read); + + buffer->first = (buffer->first + size) % buffer->bufsize; +} + diff --git a/audio/buffer.h b/audio/buffer.h new file mode 100644 index 0000000000..63678d2223 --- /dev/null +++ b/audio/buffer.h @@ -0,0 +1,36 @@ +/* RSound - A PCM audio client/server + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * RSound is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RSound is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RSound. + * If not, see . + */ + +#ifndef __BUFFER_H +#define __BUFFER_H + +#include +#include +#include +#include + +#ifndef RSD_FIFO_BUF_TYPEDEF +#define RSD_FIFO_BUF_TYPEDEF +typedef struct rsound_fifo_buffer rsound_fifo_buffer_t; +#endif + +rsound_fifo_buffer_t* rsnd_fifo_new(size_t size); +void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t size); +void rsnd_fifo_read(rsound_fifo_buffer_t* buffer, void* in_buf, size_t size); +void rsnd_fifo_free(rsound_fifo_buffer_t* buffer); +size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer); +size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer); + +#endif diff --git a/audio/sdl.c b/audio/sdl.c new file mode 100644 index 0000000000..f5748e339f --- /dev/null +++ b/audio/sdl.c @@ -0,0 +1,217 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + + +#include "driver.h" +#include +#include +#include +#include +#include + +#include "SDL.h" +#include "SDL_audio.h" +#include "SDL_thread.h" +#include "general.h" +#include "buffer.h" + +typedef struct sdl_audio +{ + bool nonblock; + + SDL_mutex *lock; + SDL_cond *cond; + rsound_fifo_buffer_t *buffer; +} sdl_audio_t; + +static void sdl_audio_cb(void *data, Uint8 *stream, int len) +{ + sdl_audio_t *sdl = data; + + size_t avail = rsnd_fifo_read_avail(sdl->buffer); + size_t write_size = len > avail ? avail : len; + rsnd_fifo_read(sdl->buffer, stream, write_size); + SDL_CondSignal(sdl->cond); + + // If underrun, fill rest with silence. + memset(stream + write_size, 0, len - write_size); +} + +// Interesting hack from http://www-graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 +static inline uint32_t next_pow2(uint32_t v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; +} + +static inline int find_num_frames(int rate, int latency) +{ + int frames = (rate * latency) / 1000; + // SDL only likes 2^n sized buffers. + return next_pow2(frames); +} + +static void* sdl_audio_init(const char* device, int rate, int latency) +{ + (void)device; + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) + return NULL; + + sdl_audio_t *sdl = calloc(1, sizeof(*sdl)); + if (!sdl) + return NULL; + + // We have to buffer up some data ourselves, so we let SDL carry approx half of the latency. SDL double buffers audio and we do as well. + int frames = find_num_frames(rate, latency / 4); + + SDL_AudioSpec spec = { + .freq = rate, + .format = AUDIO_S16SYS, + .channels = 2, + .samples = frames, // This is in audio frames, not samples ... :( + .callback = sdl_audio_cb, + .userdata = sdl + }; + + SDL_AudioSpec out; + + if (SDL_OpenAudio(&spec, &out) < 0) + { + SSNES_ERR("Failed to open SDL audio: %s\n", SDL_GetError()); + free(sdl); + return 0; + } + g_settings.audio.out_rate = out.freq; + + sdl->lock = SDL_CreateMutex(); + sdl->cond = SDL_CreateCond(); + + SSNES_LOG("SDL audio: Requested %d ms latency, got %d ms\n", latency, (int)(out.samples * 4 * 1000 / g_settings.audio.out_rate)); + + // Create a buffer twice as big as needed and prefill the buffer. + size_t bufsize = out.samples * 4 * sizeof(int16_t); + void *tmp = calloc(1, bufsize); + sdl->buffer = rsnd_fifo_new(bufsize); + if (tmp) + { + rsnd_fifo_write(sdl->buffer, tmp, bufsize); + free(tmp); + } + + SDL_PauseAudio(0); + return sdl; +} + +static ssize_t sdl_audio_write(void* data, const void* buf, size_t size) +{ + sdl_audio_t *sdl = data; + + ssize_t ret = 0; + if (sdl->nonblock) + { + SDL_LockAudio(); + size_t avail = rsnd_fifo_write_avail(sdl->buffer); + size_t write_amt = avail > size ? size : avail; + rsnd_fifo_write(sdl->buffer, buf, write_amt); + SDL_UnlockAudio(); + ret = write_amt; + } + else + { + size_t written = 0; + while (written < size) + { + SDL_LockAudio(); + size_t avail = rsnd_fifo_write_avail(sdl->buffer); + + if (avail == 0) + { + SDL_UnlockAudio(); + SDL_mutexP(sdl->lock); + SDL_CondWait(sdl->cond, sdl->lock); + SDL_mutexV(sdl->lock); + } + else + { + size_t write_amt = size - written > avail ? avail : size - written; + rsnd_fifo_write(sdl->buffer, (const char*)buf + written, write_amt); + SDL_UnlockAudio(); + written += write_amt; + } + } + ret = written; + } + + return ret; +} + +static bool sdl_audio_stop(void *data) +{ + (void)data; + SDL_PauseAudio(1); + return true; +} + +static bool sdl_audio_start(void *data) +{ + (void)data; + SDL_PauseAudio(0); + return true; +} + +static void sdl_audio_set_nonblock_state(void *data, bool state) +{ + sdl_audio_t *sdl = data; + sdl->nonblock = state; +} + +static void sdl_audio_free(void *data) +{ + SDL_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + + sdl_audio_t *sdl = data; + if (sdl) + { + rsnd_fifo_free(sdl->buffer); + SDL_DestroyMutex(sdl->lock); + SDL_DestroyCond(sdl->cond); + } + free(sdl); +} + +const audio_driver_t audio_sdl = { + .init = sdl_audio_init, + .write = sdl_audio_write, + .stop = sdl_audio_stop, + .start = sdl_audio_start, + .set_nonblock_state = sdl_audio_set_nonblock_state, + .free = sdl_audio_free, + .ident = "sdl" +}; + + + + + + diff --git a/conf/config_file.c b/conf/config_file.c index 441256f9cd..dc945c2e6a 100644 --- a/conf/config_file.c +++ b/conf/config_file.c @@ -132,7 +132,7 @@ static void print_config(config_file_t *conf) struct entry_list *tmp = conf->entries; while (tmp != NULL) { - printf("Key: \"%s\", Value: \"%s\"\n", tmp->key, tmp->value); + SSNES_LOG("Config => Key: \"%s\", Value: \"%s\"\n", tmp->key, tmp->value); tmp = tmp->next; } } diff --git a/config.def.h b/config.def.h index d2ce444381..1a0e8881b0 100644 --- a/config.def.h +++ b/config.def.h @@ -19,14 +19,28 @@ // // -#ifndef __CONFIG_H -#define __CONFIG_H +#ifndef __CONFIG_DEF_H +#define __CONFIG_DEF_H -#include #include #include "libsnes.hpp" #include "driver.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SDL +#include +#else +#error HAVE_SDL is not defined! +#endif + +#ifdef HAVE_SRC #include +#else +#error HAVE_SRC is not defined! +#endif ///////////////// Drivers @@ -38,10 +52,30 @@ #define AUDIO_ROAR 4 #define AUDIO_AL 5 #define AUDIO_JACK 6 +#define AUDIO_SDL 8 +//////////////////////// +#define INPUT_SDL 7 //////////////////////// #define VIDEO_DEFAULT_DRIVER VIDEO_GL + +#if HAVE_ALSA #define AUDIO_DEFAULT_DRIVER AUDIO_ALSA +#elif HAVE_OSS +#define AUDIO_DEFAULT_DRIVER AUDIO_OSS +#elif HAVE_JACK +#define AUDIO_DEFAULT_DRIVER AUDIO_JACK +#elif HAVE_RSOUND +#define AUDIO_DEFAULT_DRIVER AUDIO_RSOUND +#elif HAVE_ROAR +#define AUDIO_DEFAULT_DRIVER AUDIO_ROAR +#elif HAVE_AL +#define AUDIO_DEFAULT_DRIVER AUDIO_AL +#elif HAVE_SDL +#define AUDIO_DEFAULT_DRIVER AUDIO_SDL +#endif + +#define INPUT_DEFAULT_DRIVER INPUT_SDL //////////////// @@ -66,6 +100,8 @@ static const bool video_smooth = true; // On resize and fullscreen, rendering area will stay 4:3 static const bool force_aspect = true; +#define SNES_ASPECT_RATIO (4.0/3) + //////////////// // Audio //////////////// @@ -78,7 +114,7 @@ static const unsigned out_rate = 48000; // Input samplerate from libSNES. // Lower this (slightly) if you are experiencing frequent audio dropouts while vsync is enabled. -static const unsigned in_rate = 31950; +static const unsigned in_rate = 31980; // Audio device (e.g. hw:0,0 or /dev/audio). If NULL, will use defaults. static const char* audio_device = NULL; @@ -101,7 +137,7 @@ static const bool audio_sync = true; // Axis threshold (between 0.0 and 1.0) // How far an axis must be tilted to result in a button press -#define AXIS_THRESHOLD 0.8 +#define AXIS_THRESHOLD 0.5 #define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF) #define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U) @@ -115,48 +151,47 @@ static const bool audio_sync = true; // Player 1 static const struct snes_keybind snes_keybinds_1[] = { // SNES button | keyboard key | js btn | js axis | - { SNES_DEVICE_ID_JOYPAD_A, 'X', 1, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_B, 'Z', 0, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_X, 'S', 3, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_Y, 'A', 2, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_L, 'Q', 4, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_R, 'W', 5, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_LEFT, GLFW_KEY_LEFT, 11, AXIS_NEG(0) }, - { SNES_DEVICE_ID_JOYPAD_RIGHT, GLFW_KEY_RIGHT, 12, AXIS_POS(0) }, - { SNES_DEVICE_ID_JOYPAD_UP, GLFW_KEY_UP, 13, AXIS_POS(1) }, - { SNES_DEVICE_ID_JOYPAD_DOWN, GLFW_KEY_DOWN, 14, AXIS_NEG(1) }, - { SNES_DEVICE_ID_JOYPAD_START, GLFW_KEY_ENTER, 7, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_SELECT, GLFW_KEY_RSHIFT, 6, AXIS_NONE }, - { SNES_FAST_FORWARD_KEY, GLFW_KEY_SPACE, 10, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_A, SDLK_x, 1, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_B, SDLK_z, 0, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_X, SDLK_s, 3, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_Y, SDLK_a, 2, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_L, SDLK_q, 4, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_R, SDLK_w, 5, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_LEFT, 11, AXIS_NEG(0) }, + { SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_RIGHT, 12, AXIS_POS(0) }, + { SNES_DEVICE_ID_JOYPAD_UP, SDLK_UP, 13, AXIS_POS(1) }, + { SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_DOWN, 14, AXIS_NEG(1) }, + { SNES_DEVICE_ID_JOYPAD_START, SDLK_RETURN, 7, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_RSHIFT, 6, AXIS_NONE }, + { SSNES_FAST_FORWARD_KEY, SDLK_SPACE, 10, AXIS_NONE }, { -1 } }; // Player 2 static const struct snes_keybind snes_keybinds_2[] = { // SNES button | keyboard key | js btn | js axis | - { SNES_DEVICE_ID_JOYPAD_A, 'B', 1, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_B, 'V', 0, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_X, 'G', 3, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_Y, 'F', 2, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_L, 'R', 4, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_R, 'T', 5, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_LEFT, 'J', 11, AXIS_NEG(0) }, - { SNES_DEVICE_ID_JOYPAD_RIGHT, 'L', 12, AXIS_POS(0) }, - { SNES_DEVICE_ID_JOYPAD_UP, 'I', 13, AXIS_POS(1) }, - { SNES_DEVICE_ID_JOYPAD_DOWN, 'K', 14, AXIS_NEG(1) }, - { SNES_DEVICE_ID_JOYPAD_START, 'P', 6, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_SELECT, 'O', 7, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) }, + { SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) }, + { SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_POS(1) }, + { SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_NEG(1) }, + { SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE }, { -1 } }; ///// Save state -#define SAVE_STATE_KEY GLFW_KEY_F2 +#define SAVE_STATE_KEY SDLK_F2 ///// Load state -#define LOAD_STATE_KEY GLFW_KEY_F4 +#define LOAD_STATE_KEY SDLK_F4 //// Toggles between fullscreen and windowed mode. -#define TOGGLE_FULLSCREEN 'F' - +#define TOGGLE_FULLSCREEN SDLK_f #endif diff --git a/driver.c b/driver.c index 4e694af930..67ce93c661 100644 --- a/driver.c +++ b/driver.c @@ -21,7 +21,10 @@ #include #include #include "hqflt/filters.h" + +#ifdef HAVE_CONFIG_H #include "config.h" +#endif static const audio_driver_t *audio_drivers[] = { #ifdef HAVE_ALSA @@ -42,14 +45,23 @@ static const audio_driver_t *audio_drivers[] = { #ifdef HAVE_JACK &audio_jack, #endif +#ifdef HAVE_SDL + &audio_sdl, +#endif }; static const video_driver_t *video_drivers[] = { -#ifdef HAVE_GLFW +#ifdef HAVE_SDL &video_gl, #endif }; +static const input_driver_t *input_drivers[] = { +#ifdef HAVE_SDL + &input_sdl, +#endif +}; + static void find_audio_driver(void) { for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++) @@ -86,6 +98,24 @@ static void find_video_driver(void) exit(1); } +static void find_input_driver(void) +{ + for (int i = 0; i < sizeof(input_drivers) / sizeof(input_driver_t*); i++) + { + if (strcasecmp(g_settings.input.driver, input_drivers[i]->ident) == 0) + { + driver.input = input_drivers[i]; + return; + } + } + SSNES_ERR("Couldn't find any input driver named \"%s\"\n", g_settings.input.driver); + fprintf(stderr, "Available input drivers are:\n"); + for (int i = 0; i < sizeof(input_drivers) / sizeof(input_driver_t*); i++) + fprintf(stderr, "\t%s\n", video_drivers[i]->ident); + + exit(1); +} + void init_drivers(void) { init_video_input(); @@ -141,6 +171,7 @@ void init_video_input(void) int scale = 2; find_video_driver(); + find_input_driver(); // We multiply scales with 2 to allow for hi-res games. #if HAVE_FILTER @@ -169,7 +200,7 @@ void init_video_input(void) }; const input_driver_t *tmp = driver.input; - driver.video_data = driver.video->init(&video, &driver.input); + driver.video_data = driver.video->init(&video, &driver.input, &driver.input_data); if ( driver.video_data == NULL ) { @@ -177,18 +208,18 @@ void init_video_input(void) exit(1); } - if ( driver.input != NULL ) - { - driver.input_data = driver.video_data; - } - else + // Video driver didn't provide an input driver so we use configured one. + if (driver.input == NULL) { driver.input = tmp; if (driver.input != NULL) { driver.input_data = driver.input->init(); if ( driver.input_data == NULL ) + { + SSNES_ERR("Cannot init input driver. Exiting ...\n"); exit(1); + } } else { diff --git a/driver.h b/driver.h index f695746cd3..ec5f3dcca8 100644 --- a/driver.h +++ b/driver.h @@ -19,12 +19,13 @@ #ifndef __DRIVER__H #define __DRIVER__H +#include #include #include #include #include -#define SNES_FAST_FORWARD_KEY 0x666 // Hurr, durr +#define SSNES_FAST_FORWARD_KEY 0x666 // Hurr, durr void set_fast_forward_button(bool state); struct snes_keybind @@ -65,16 +66,19 @@ typedef struct input_driver void* (*init)(void); void (*poll)(void* data); int16_t (*input_state)(void* data, const struct snes_keybind **snes_keybinds, bool port, unsigned device, unsigned index, unsigned id); + bool (*key_pressed)(void* data, int key); void (*free)(void* data); const char *ident; } input_driver_t; typedef struct video_driver { - void* (*init)(video_info_t *video, const input_driver_t **input); - // Should the video driver act as an input driver as well? :) + void* (*init)(video_info_t *video, const input_driver_t **input, void **input_data); + // Should the video driver act as an input driver as well? :) The video init might preinitialize an input driver to override the settings in case the video driver relies on input driver for event handling, e.g. bool (*frame)(void* data, const uint16_t* frame, int width, int height, int pitch); void (*set_nonblock_state)(void* data, bool toggle); // Should we care about syncing to vblank? Fast forwarding. + // Is the window still active? + bool (*alive)(void *data); void (*free)(void* data); const char *ident; } video_driver_t; @@ -107,7 +111,9 @@ extern const audio_driver_t audio_alsa; extern const audio_driver_t audio_roar; extern const audio_driver_t audio_openal; extern const audio_driver_t audio_jack; +extern const audio_driver_t audio_sdl; extern const video_driver_t video_gl; +extern const input_driver_t input_sdl; //////////////////////////////////////////////// #endif diff --git a/dynamic.c b/dynamic.c index 5c8ff14ceb..54bf5a1177 100644 --- a/dynamic.c +++ b/dynamic.c @@ -18,7 +18,12 @@ #include "dynamic.h" #include "general.h" #include + +#ifdef HAVE_CONFIG_H #include "config.h" +#endif + +#include #ifdef HAVE_DYNAMIC #include diff --git a/file.h b/file.h index d4a2e19d3a..5d4add33a1 100644 --- a/file.h +++ b/file.h @@ -22,6 +22,7 @@ #include #include #include +#include ssize_t read_file(FILE *file, void **buf); diff --git a/general.h b/general.h index af97152d12..ef1bfffbb3 100644 --- a/general.h +++ b/general.h @@ -20,9 +20,17 @@ #define __SSNES_GENERAL_H #include -#include #include "driver.h" #include +#include "record/ffemu.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SRC +#include +#endif #define MAX_PLAYERS 2 @@ -40,7 +48,9 @@ struct settings bool vsync; bool smooth; bool force_aspect; + float aspect_ratio; char cg_shader_path[256]; + char bsnes_shader_path[256]; unsigned filter; } video; @@ -81,6 +91,12 @@ struct global char savefile_name_srm[256]; char config_path[256]; char basename[256]; + +#ifdef HAVE_FFMPEG + ffemu_t *rec; + char record_path[256]; + bool recording; +#endif }; void parse_config(void); @@ -91,10 +107,17 @@ extern struct global g_extern; #define SSNES_LOG(msg, args...) do { \ if (g_extern.verbose) \ fprintf(stderr, "SSNES: " msg, ##args); \ + fflush(stderr); \ } while(0) #define SSNES_ERR(msg, args...) do { \ - fprintf(stderr, "SSNES [ERROR] :: " msg, ##args); \ + fprintf(stderr, "SSNES [ERROR] :: " msg, ##args); \ + fflush(stderr); \ + } while(0) + +#define SSNES_WARN(msg, args...) do { \ + fprintf(stderr, "SSNES [WARN] :: " msg, ##args); \ + fflush(stderr); \ } while(0) #endif diff --git a/gfx/gl.c b/gfx/gl.c index 3e9b38c351..fd77397af9 100644 --- a/gfx/gl.c +++ b/gfx/gl.c @@ -15,23 +15,38 @@ * If not, see . */ -#define GL_GLEXT_PROTOTYPES #include "driver.h" -#include -#include + #include #include "libsnes.hpp" #include #include #include #include "general.h" -#include "config.h" +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define NO_SDL_GLEXT +#include "SDL.h" +#include "SDL_opengl.h" +#include "input/ssnes_sdl_input.h" + +#define GL_GLEXT_PROTOTYPES +#include + +#ifndef _WIN32 +#include +#endif #ifdef HAVE_CG -#include -#include +#include "shader_cg.h" +#endif + +#ifdef HAVE_XML +#include "shader_glsl.h" #endif static const GLfloat vertexes[] = { @@ -49,26 +64,20 @@ static const GLfloat tex_coords[] = { }; static bool keep_aspect = true; -#ifdef HAVE_CG -static CGparameter cg_mvp_matrix; -static bool cg_active = false; -#endif static GLuint gl_width = 0, gl_height = 0; typedef struct gl { bool vsync; -#ifdef HAVE_CG - CGcontext cgCtx; - CGprogram cgFPrg; - CGprogram cgVPrg; - CGprofile cgFProf; - CGprofile cgVProf; - CGparameter cg_video_size, cg_texture_size, cg_output_size; - CGparameter cg_Vvideo_size, cg_Vtexture_size, cg_Voutput_size; // Vertexes -#endif GLuint texture; GLuint tex_filter; + bool should_resize; + bool quitting; + + unsigned win_width; + unsigned win_height; + unsigned vp_width; + unsigned vp_height; unsigned last_width; unsigned last_height; unsigned tex_w, tex_h; @@ -76,151 +85,99 @@ typedef struct gl } gl_t; -static void glfw_input_poll(void *data) +static inline bool gl_shader_init(void) { - (void)data; - glfwPollEvents(); + if (strlen(g_settings.video.cg_shader_path) > 0 && strlen(g_settings.video.bsnes_shader_path) > 0) + SSNES_WARN("Both Cg and bSNES XML shader are defined in config file. Cg shader will be selected by default.\n"); + +#ifdef HAVE_CG + if (strlen(g_settings.video.cg_shader_path) > 0) + return gl_cg_init(g_settings.video.cg_shader_path); +#endif + +#ifdef HAVE_XML + if (strlen(g_settings.video.bsnes_shader_path) > 0) + return gl_glsl_init(g_settings.video.bsnes_shader_path); +#endif + + return true; } -#define BUTTONS_MAX 128 -#define AXES_MAX 128 - -static unsigned joypad_id[2]; -static unsigned joypad_buttons[2]; -static unsigned joypad_axes[2]; -static bool joypad_inited = false; -static unsigned joypad_count = 0; - -static int init_joypads(int max_pads) +static inline void gl_shader_deinit(void) { - // Finds the first (two) joypads that are alive - int count = 0; - for ( int i = GLFW_JOYSTICK_1; (i <= GLFW_JOYSTICK_LAST) && (count < max_pads); i++ ) - { - if ( glfwGetJoystickParam(i, GLFW_PRESENT) == GL_TRUE ) - { - joypad_id[count] = i; - joypad_buttons[count] = glfwGetJoystickParam(i, GLFW_BUTTONS); - if (joypad_buttons[count] > BUTTONS_MAX) - joypad_buttons[count] = BUTTONS_MAX; - joypad_axes[count] = glfwGetJoystickParam(i, GLFW_AXES); - if (joypad_axes[count] > AXES_MAX) - joypad_axes[count] = AXES_MAX; - count++; - } - } - joypad_inited = true; - return count; +#ifdef HAVE_CG + gl_cg_deinit(); +#endif + +#ifdef HAVE_XML + gl_glsl_deinit(); +#endif } -static bool glfw_is_pressed(int port_num, const struct snes_keybind *key, unsigned char *buttons, float *axes) +static inline void gl_shader_set_proj_matrix(void) { - if (glfwGetKey(key->key)) - return true; - if (port_num >= joypad_count) - return false; - if (key->joykey < joypad_buttons[port_num] && buttons[key->joykey] == GLFW_PRESS) - return true; +#ifdef HAVE_CG + gl_cg_set_proj_matrix(); +#endif - if (key->joyaxis != AXIS_NONE) - { - if (AXIS_NEG_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_NEG_GET(key->joyaxis)] <= -g_settings.input.axis_threshold) - return true; - if (AXIS_POS_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_POS_GET(key->joyaxis)] >= g_settings.input.axis_threshold) - return true; - } - return false; +#ifdef HAVE_XML + gl_glsl_set_proj_matrix(); +#endif } -static int16_t glfw_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id) +static inline void gl_shader_set_params(unsigned width, unsigned height, + unsigned tex_width, unsigned tex_height, + unsigned out_width, unsigned out_height) { - if ( device != SNES_DEVICE_JOYPAD ) - return 0; +#ifdef HAVE_CG + gl_cg_set_params(width, height, tex_width, tex_height, out_width, out_height); +#endif - if ( !joypad_inited ) - joypad_count = init_joypads(2); - - int port_num = port ? 1 : 0; - unsigned char buttons[BUTTONS_MAX]; - float axes[AXES_MAX]; - - if ( joypad_count > port_num ) - { - glfwGetJoystickButtons(joypad_id[port_num], buttons, joypad_buttons[port_num]); - glfwGetJoystickPos(joypad_id[port_num], axes, joypad_axes[port_num]); - } - - - const struct snes_keybind *snes_keybinds; - if (port == SNES_PORT_1) - snes_keybinds = binds[0]; - else - snes_keybinds = binds[1]; - - // Checks if button is pressed, and sets fast-forwarding state - bool pressed = false; - for ( int i = 0; snes_keybinds[i].id != -1; i++ ) - if ( snes_keybinds[i].id == SNES_FAST_FORWARD_KEY ) - set_fast_forward_button(glfw_is_pressed(port_num, &snes_keybinds[i], buttons, axes)); - else if ( !pressed && snes_keybinds[i].id == (int)id ) - pressed = glfw_is_pressed(port_num, &snes_keybinds[i], buttons, axes); - - return pressed; +#ifdef HAVE_XML + gl_glsl_set_params(width, height, tex_width, tex_height, out_width, out_height); +#endif } -static void glfw_free_input(void *data) -{ - free(data); -} - -static const input_driver_t input_glfw = { - .poll = glfw_input_poll, - .input_state = glfw_input_state, - .free = glfw_free_input, - .ident = "glfw" -}; - -static void GLFWCALL resize(int width, int height) +static void set_viewport(gl_t *gl) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); - GLuint out_width = width, out_height = height; + GLuint out_width = gl->win_width, out_height = gl->win_height; if ( keep_aspect ) { - float desired_aspect = 4.0/3; - float device_aspect = (float)width / height; + float desired_aspect = g_settings.video.aspect_ratio; + float device_aspect = (float)gl->win_width / gl->win_height; // If the aspect ratios of screen and desired aspect ratio are sufficiently equal (floating point stuff), // assume they are actually equal. if ( (int)(device_aspect*1000) > (int)(desired_aspect*1000) ) { float delta = (desired_aspect / device_aspect - 1.0) / 2.0 + 0.5; - glViewport(width * (0.5 - delta), 0, 2.0 * width * delta, height); - out_width = (int)(2.0 * width * delta); + glViewport(gl->win_width * (0.5 - delta), 0, 2.0 * gl->win_width * delta, gl->win_height); + out_width = (int)(2.0 * gl->win_width * delta); } else if ( (int)(device_aspect*1000) < (int)(desired_aspect*1000) ) { float delta = (device_aspect / desired_aspect - 1.0) / 2.0 + 0.5; - glViewport(0, height * (0.5 - delta), width, 2.0 * height * delta); - out_height = (int)(2.0 * height * delta); + glViewport(0, gl->win_height * (0.5 - delta), gl->win_width, 2.0 * gl->win_height * delta); + out_height = (int)(2.0 * gl->win_height * delta); } else - glViewport(0, 0, width, height); + glViewport(0, 0, gl->win_width, gl->win_height); } else - glViewport(0, 0, width, height); + glViewport(0, 0, gl->win_width, gl->win_height); glOrtho(0, 1, 0, 1, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); -#ifdef HAVE_CG - if (cg_active) - cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY); -#endif - gl_width = out_width; - gl_height = out_height; + + gl_shader_set_proj_matrix(); + + gl->vp_width = out_width; + gl->vp_height = out_height; } static float tv_to_fps(const struct timeval *tv, const struct timeval *new_tv, int frames) @@ -229,7 +186,7 @@ static float tv_to_fps(const struct timeval *tv, const struct timeval *new_tv, i return frames/time; } -static inline void show_fps(void) +static void show_fps(void) { // Shows FPS in taskbar. static int frames = 0; @@ -248,8 +205,8 @@ static inline void show_fps(void) float fps = tv_to_fps(&tmp_tv, &new_tv, 180); - snprintf(tmpstr, sizeof(tmpstr) - 1, "SSNES || FPS: %6.1f || Frames: %d", fps, frames); - glfwSetWindowTitle(tmpstr); + snprintf(tmpstr, sizeof(tmpstr), "SSNES || FPS: %6.1f || Frames: %d", fps, frames); + SDL_WM_SetCaption(tmpstr, NULL); } frames++; } @@ -258,20 +215,16 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i { gl_t *gl = data; + if (gl->should_resize) + { + gl->should_resize = false; + SDL_SetVideoMode(gl->win_width, gl->win_height, 32, SDL_OPENGL | SDL_RESIZABLE | (g_settings.video.fullscreen ? SDL_FULLSCREEN : 0)); + set_viewport(gl); + } + glClear(GL_COLOR_BUFFER_BIT); -#if HAVE_CG - if (cg_active) - { - cgGLSetParameter2f(gl->cg_video_size, width, height); - cgGLSetParameter2f(gl->cg_texture_size, gl->tex_w, gl->tex_h); - cgGLSetParameter2f(gl->cg_output_size, gl_width, gl_height); - - cgGLSetParameter2f(gl->cg_Vvideo_size, width, height); - cgGLSetParameter2f(gl->cg_Vtexture_size, gl->tex_w, gl->tex_h); - cgGLSetParameter2f(gl->cg_Voutput_size, gl_width, gl_height); - } -#endif + gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl_width, gl_height); if (width != gl->last_width || height != gl->last_height) // res change. need to clear out texture. { @@ -302,7 +255,8 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i glDrawArrays(GL_QUADS, 0, 4); show_fps(); - glfwSwapBuffers(); + glFlush(); + SDL_GL_SwapBuffers(); return true; } @@ -310,14 +264,12 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i static void gl_free(void *data) { gl_t *gl = data; -#ifdef HAVE_CG - if (cg_active) - cgDestroyContext(gl->cgCtx); -#endif + + gl_shader_deinit(); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDeleteTextures(1, &gl->texture); - glfwTerminate(); + SDL_QuitSubSystem(SDL_INIT_VIDEO); } static void gl_set_nonblock_state(void *data, bool state) @@ -325,43 +277,62 @@ static void gl_set_nonblock_state(void *data, bool state) gl_t *gl = data; if (gl->vsync) { - if (state) - glfwSwapInterval(0); - else - glfwSwapInterval(1); + SSNES_LOG("GL VSync => %s\n", state ? "off" : "on"); +#ifdef _WIN32 + static BOOL (APIENTRY *wgl_swap_interval)(int) = NULL; + if (!wgl_swap_interval) + SSNES_WARN("SDL VSync toggling seems to be broken, attempting to use WGL VSync call directly instead.\n"); + if (!wgl_swap_interval) wgl_swap_interval = (BOOL (APIENTRY*)(int)) wglGetProcAddress("wglSwapIntervalEXT"); + if (wgl_swap_interval) wgl_swap_interval(state ? 0 : 1); +#else + static int (*glx_swap_interval)(int) = NULL; + if (!glx_swap_interval) + SSNES_WARN("SDL VSync toggling seems to be broken, attempting to use GLX VSync call directly instead.\n"); + if (!glx_swap_interval) glx_swap_interval = (int (*)(int))glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalSGI"); + if (!glx_swap_interval) glx_swap_interval = (int (*)(int))glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalMESA"); + if (glx_swap_interval) glx_swap_interval(state ? 0 : 1); +#endif } } -static void* gl_init(video_info_t *video, const input_driver_t **input) +static void* gl_init(video_info_t *video, const input_driver_t **input, void **input_data) { - gl_t *gl = calloc(1, sizeof(gl_t)); - if ( gl == NULL ) + if (SDL_Init(SDL_INIT_VIDEO) < 0) return NULL; + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, video->vsync ? 1 : 0); + + if (!SDL_SetVideoMode(video->width, video->height, 32, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0))) + return NULL; + + int attr = 0; + SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &attr); + if (attr <= 0 && video->vsync) + SSNES_WARN("GL VSync has not been enabled!\n"); + attr = 0; + SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &attr); + if (attr <= 0) + SSNES_WARN("GL double buffer has not been enabled!\n"); + + // Remove that ugly mouse :D + SDL_ShowCursor(SDL_DISABLE); + + gl_t *gl = calloc(1, sizeof(gl_t)); + if (!gl) + return NULL; + + gl->win_width = video->width; + gl->win_height = video->height; + gl->vsync = video->vsync; + keep_aspect = video->force_aspect; if ( video->smooth ) gl->tex_filter = GL_LINEAR; else gl->tex_filter = GL_NEAREST; - glfwInit(); - - int res; - res = glfwOpenWindow(video->width, video->height, 0, 0, 0, 0, 0, 0, (video->fullscreen) ? GLFW_FULLSCREEN : GLFW_WINDOW); - - if (!res) - { - glfwTerminate(); - return NULL; - } - - glfwSetWindowSizeCallback(resize); - - if ( video->vsync ) - glfwSwapInterval(1); // Force vsync - else - glfwSwapInterval(0); - gl->vsync = video->vsync; + set_viewport(gl); glEnable(GL_TEXTURE_2D); glDisable(GL_DITHER); @@ -369,7 +340,7 @@ static void* gl_init(video_info_t *video, const input_driver_t **input) glColor3f(1, 1, 1); glClearColor(0, 0, 0, 0); - glfwSetWindowTitle("SSNES"); + SDL_WM_SetCaption("SSNES", NULL); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); @@ -378,8 +349,8 @@ static void* gl_init(video_info_t *video, const input_driver_t **input) glBindTexture(GL_TEXTURE_2D, gl->texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl->tex_filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl->tex_filter); @@ -400,68 +371,38 @@ static void* gl_init(video_info_t *video, const input_driver_t **input) gl->last_width = gl->tex_w; gl->last_height = gl->tex_h; -#ifdef HAVE_CG - cg_active = false; - if (strlen(g_settings.video.cg_shader_path) > 0) + gl_shader_init(); + + // Hook up SDL input driver to get SDL_QUIT events and RESIZE. + sdl_input_t *sdl_input = input_sdl.init(); + if (sdl_input) { - SSNES_LOG("Loading Cg file: %s\n", g_settings.video.cg_shader_path); - gl->cgCtx = cgCreateContext(); - if (gl->cgCtx == NULL) - { - fprintf(stderr, "Failed to create Cg context\n"); - goto error; - } - gl->cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT); - gl->cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX); - if (gl->cgFProf == CG_PROFILE_UNKNOWN || gl->cgVProf == CG_PROFILE_UNKNOWN) - { - fprintf(stderr, "Invalid profile type\n"); - goto error; - } - cgGLSetOptimalOptions(gl->cgFProf); - cgGLSetOptimalOptions(gl->cgVProf); - gl->cgFPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, g_settings.video.cg_shader_path, gl->cgFProf, "main_fragment", 0); - gl->cgVPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, g_settings.video.cg_shader_path, gl->cgVProf, "main_vertex", 0); - if (gl->cgFPrg == NULL || gl->cgVPrg == NULL) - { - CGerror err = cgGetError(); - fprintf(stderr, "CG error: %s\n", cgGetErrorString(err)); - goto error; - } - cgGLLoadProgram(gl->cgFPrg); - cgGLLoadProgram(gl->cgVPrg); - cgGLEnableProfile(gl->cgFProf); - cgGLEnableProfile(gl->cgVProf); - cgGLBindProgram(gl->cgFPrg); - cgGLBindProgram(gl->cgVPrg); - - gl->cg_video_size = cgGetNamedParameter(gl->cgFPrg, "IN.video_size"); - gl->cg_texture_size = cgGetNamedParameter(gl->cgFPrg, "IN.texture_size"); - gl->cg_output_size = cgGetNamedParameter(gl->cgFPrg, "IN.output_size"); - gl->cg_Vvideo_size = cgGetNamedParameter(gl->cgVPrg, "IN.video_size"); - gl->cg_Vtexture_size = cgGetNamedParameter(gl->cgVPrg, "IN.texture_size"); - gl->cg_Voutput_size = cgGetNamedParameter(gl->cgVPrg, "IN.output_size"); - cg_mvp_matrix = cgGetNamedParameter(gl->cgVPrg, "modelViewProj"); - cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY); - cg_active = true; + sdl_input->quitting = &gl->quitting; + sdl_input->should_resize = &gl->should_resize; + sdl_input->new_width = &gl->win_width; + sdl_input->new_height = &gl->win_height; + *input = &input_sdl; + *input_data = sdl_input; } -#endif + else + *input = NULL; - *input = &input_glfw; return gl; -#ifdef HAVE_CG -error: - free(gl); - return NULL; -#endif +} + +static bool gl_alive(void *data) +{ + gl_t *gl = data; + return !gl->quitting; } const video_driver_t video_gl = { .init = gl_init, .frame = gl_frame, + .alive = gl_alive, .set_nonblock_state = gl_set_nonblock_state, .free = gl_free, - .ident = "glfw" + .ident = "gl" }; diff --git a/gfx/shader_cg.c b/gfx/shader_cg.c new file mode 100644 index 0000000000..1c1005a794 --- /dev/null +++ b/gfx/shader_cg.c @@ -0,0 +1,104 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#include "shader_cg.h" +#include +#include +#include "general.h" + +static CGcontext cgCtx; +static CGprogram cgFPrg; +static CGprogram cgVPrg; +static CGprofile cgFProf; +static CGprofile cgVProf; +static CGparameter cg_video_size, cg_texture_size, cg_output_size; +static CGparameter cg_Vvideo_size, cg_Vtexture_size, cg_Voutput_size; // Vertexes +static CGparameter cg_mvp_matrix; +static bool cg_active = false; + +void gl_cg_set_proj_matrix(void) +{ + if (cg_active) + cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY); +} + +void gl_cg_set_params(unsigned width, unsigned height, + unsigned tex_width, unsigned tex_height, + unsigned out_width, unsigned out_height) +{ + if (cg_active) + { + cgGLSetParameter2f(cg_video_size, width, height); + cgGLSetParameter2f(cg_texture_size, tex_width, tex_height); + cgGLSetParameter2f(cg_output_size, out_width, out_height); + + cgGLSetParameter2f(cg_Vvideo_size, width, height); + cgGLSetParameter2f(cg_Vtexture_size, tex_width, tex_height); + cgGLSetParameter2f(cg_Voutput_size, out_width, out_height); + } +} + +void gl_cg_deinit(void) +{ + if (cg_active) + cgDestroyContext(cgCtx); +} + +bool gl_cg_init(const char *path) +{ + SSNES_LOG("Loading Cg file: %s\n", path); + cgCtx = cgCreateContext(); + if (cgCtx == NULL) + { + SSNES_ERR("Failed to create Cg context\n"); + return false; + } + cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT); + cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX); + if (cgFProf == CG_PROFILE_UNKNOWN || cgVProf == CG_PROFILE_UNKNOWN) + { + SSNES_ERR("Invalid profile type\n"); + return false; + } + cgGLSetOptimalOptions(cgFProf); + cgGLSetOptimalOptions(cgVProf); + cgFPrg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, path, cgFProf, "main_fragment", 0); + cgVPrg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, path, cgVProf, "main_vertex", 0); + if (cgFPrg == NULL || cgVPrg == NULL) + { + CGerror err = cgGetError(); + SSNES_ERR("CG error: %s\n", cgGetErrorString(err)); + return false; + } + cgGLLoadProgram(cgFPrg); + cgGLLoadProgram(cgVPrg); + cgGLEnableProfile(cgFProf); + cgGLEnableProfile(cgVProf); + cgGLBindProgram(cgFPrg); + cgGLBindProgram(cgVPrg); + + cg_video_size = cgGetNamedParameter(cgFPrg, "IN.video_size"); + cg_texture_size = cgGetNamedParameter(cgFPrg, "IN.texture_size"); + cg_output_size = cgGetNamedParameter(cgFPrg, "IN.output_size"); + cg_Vvideo_size = cgGetNamedParameter(cgVPrg, "IN.video_size"); + cg_Vtexture_size = cgGetNamedParameter(cgVPrg, "IN.texture_size"); + cg_Voutput_size = cgGetNamedParameter(cgVPrg, "IN.output_size"); + cg_mvp_matrix = cgGetNamedParameter(cgVPrg, "modelViewProj"); + cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY); + cg_active = true; + return true; +} diff --git a/gfx/shader_cg.h b/gfx/shader_cg.h new file mode 100644 index 0000000000..f788466c0a --- /dev/null +++ b/gfx/shader_cg.h @@ -0,0 +1,34 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + + +#ifndef __SSNES_CG_H +#define __SSNES_CG_H + +#include + +bool gl_cg_init(const char *path); + +void gl_cg_deinit(void); + +void gl_cg_set_proj_matrix(void); + +void gl_cg_set_params(unsigned width, unsigned height, + unsigned tex_width, unsigned tex_height, + unsigned out_width, unsigned out_height); + +#endif diff --git a/gfx/shader_glsl.c b/gfx/shader_glsl.c new file mode 100644 index 0000000000..a9e93a47e6 --- /dev/null +++ b/gfx/shader_glsl.c @@ -0,0 +1,253 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +// +// GLSL code here is mostly copypasted from bSNES. +// + +#include +#include +#include "general.h" + +#define NO_SDL_GLEXT +#include +#include "SDL.h" +#include "SDL_opengl.h" +#include +#include +#include + +#define GL_GLEXT_PROTOTYPES +#include + +static PFNGLCREATEPROGRAMPROC pglCreateProgram = NULL; +static PFNGLUSEPROGRAMPROC pglUseProgram = NULL; +static PFNGLCREATESHADERPROC pglCreateShader = NULL; +static PFNGLDELETESHADERPROC pglDeleteShader = NULL; +static PFNGLSHADERSOURCEPROC pglShaderSource = NULL; +static PFNGLCOMPILESHADERPROC pglCompileShader = NULL; +static PFNGLATTACHSHADERPROC pglAttachShader = NULL; +static PFNGLDETACHSHADERPROC pglDetachShader = NULL; +static PFNGLLINKPROGRAMPROC pglLinkProgram = NULL; +static PFNGLGETUNIFORMLOCATIONPROC pglGetUniformLocation = NULL; +static PFNGLUNIFORM1IPROC pglUniform1i = NULL; +static PFNGLUNIFORM2FVPROC pglUniform2fv = NULL; +static PFNGLUNIFORM4FVPROC pglUniform4fv = NULL; +static PFNGLGETSHADERIVPROC pglGetShaderiv = NULL; +static PFNGLGETSHADERINFOLOGPROC pglGetShaderInfoLog = NULL; + +static bool glsl_enable = false; +static GLuint gl_program; +static GLuint fragment_shader; +static GLuint vertex_shader; + +static bool get_xml_shaders(const char *path, char **vertex_shader, char **fragment_shader) +{ + LIBXML_TEST_VERSION; + + xmlParserCtxtPtr ctx = xmlNewParserCtxt(); + if (!ctx) + { + SSNES_ERR("Failed to load libxml2 context.\n"); + return false; + } + + SSNES_LOG("Loading XML shader: %s\n", path); + xmlDocPtr doc = xmlCtxtReadFile(ctx, path, NULL, 0); + if (!doc) + { + SSNES_ERR("Failed to parse XML file: %s\n", path); + goto error; + } + + if (ctx->valid == 0) + { + SSNES_ERR("Cannot validate XML shader: %s\n", path); + goto error; + } + + xmlNodePtr head = xmlDocGetRootElement(doc); + xmlNodePtr cur = NULL; + for (cur = head; cur; cur = cur->next) + { + if (cur->type == XML_ELEMENT_NODE && strcmp((const char*)cur->name, "shader") == 0) + { + xmlChar *attr; + if ((attr = xmlGetProp(cur, (const xmlChar*)"language")) && strcmp((const char*)attr, "GLSL") == 0) + break; + } + } + + if (!cur) // We couldn't find any GLSL shader :( + goto error; + + bool vertex_found = false; + bool fragment_found = false; + // Iterate to check if we find fragment and/or vertex shaders. + for (cur = cur->children; cur; cur = cur->next) + { + if (cur->type != XML_ELEMENT_NODE) + continue; + + xmlChar *content = xmlNodeGetContent(cur); + if (!content) + continue; + + if (strcmp((const char*)cur->name, "vertex") == 0 && !vertex_found) + { + *vertex_shader = malloc(xmlStrlen(content) + 1); + strcpy(*vertex_shader, (const char*)content); + vertex_found = true; + } + else if (strcmp((const char*)cur->name, "fragment") == 0 && !fragment_found) + { + *fragment_shader = malloc(xmlStrlen(content) + 1); + strcpy(*fragment_shader, (const char*)content); + fragment_found = true; + } + } + + if (!vertex_found && !fragment_found) + { + SSNES_ERR("Couldn't find vertex shader nor fragment shader in XML file.\n"); + goto error; + } + + + xmlFreeDoc(doc); + xmlFreeParserCtxt(ctx); + return true; + +error: + SSNES_ERR("Failed to load XML shader ...\n"); + if (doc) + xmlFreeDoc(doc); + xmlFreeParserCtxt(ctx); + return false; +} + +static void print_shader_log(GLuint obj) +{ + int info_len = 0; + int max_len; + + pglGetShaderiv(obj, GL_INFO_LOG_LENGTH, &max_len); + + char info_log[max_len]; + pglGetShaderInfoLog(obj, max_len, &info_len, info_log); + + if (info_len > 0) + SSNES_LOG("Shader log: %s\n", info_log); +} + +bool gl_glsl_init(const char *path) +{ + // Load shader functions. + pglCreateProgram = SDL_GL_GetProcAddress("glCreateProgram"); + pglUseProgram = SDL_GL_GetProcAddress("glUseProgram"); + pglCreateShader = SDL_GL_GetProcAddress("glCreateShader"); + pglDeleteShader = SDL_GL_GetProcAddress("glDeleteShader"); + pglShaderSource = SDL_GL_GetProcAddress("glShaderSource"); + pglCompileShader = SDL_GL_GetProcAddress("glCompileShader"); + pglAttachShader = SDL_GL_GetProcAddress("glAttachShader"); + pglDetachShader = SDL_GL_GetProcAddress("glDetachShader"); + pglLinkProgram = SDL_GL_GetProcAddress("glLinkProgram"); + pglGetUniformLocation = SDL_GL_GetProcAddress("glGetUniformLocation"); + pglUniform1i = SDL_GL_GetProcAddress("glUniform1i"); + pglUniform2fv = SDL_GL_GetProcAddress("glUniform2fv"); + pglUniform4fv = SDL_GL_GetProcAddress("glUniform4fv"); + pglGetShaderiv = SDL_GL_GetProcAddress("glGetShaderiv"); + pglGetShaderInfoLog = SDL_GL_GetProcAddress("glGetShaderInfoLog"); + + SSNES_LOG("Checking GLSL shader support ...\n"); + bool shader_support = pglCreateProgram && pglUseProgram && pglCreateShader + && pglDeleteShader && pglShaderSource && pglCompileShader && pglAttachShader + && pglDetachShader && pglLinkProgram && pglGetUniformLocation + && pglUniform1i && pglUniform2fv && pglUniform4fv + && pglGetShaderiv && pglGetShaderInfoLog; + + if (!shader_support) + { + SSNES_ERR("GLSL shaders aren't supported by your GL driver.\n"); + return false; + } + + gl_program = pglCreateProgram(); + + char *vertex_prog = NULL; + char *fragment_prog = NULL; + if (!get_xml_shaders(path, &vertex_prog, &fragment_prog)) + return false; + + if (vertex_prog) + { + vertex_shader = pglCreateShader(GL_VERTEX_SHADER); + pglShaderSource(vertex_shader, 1, (const char**)&vertex_prog, 0); + pglCompileShader(vertex_shader); + print_shader_log(vertex_shader); + + pglAttachShader(gl_program, vertex_shader); + free(vertex_prog); + } + if (fragment_prog) + { + fragment_shader = pglCreateShader(GL_FRAGMENT_SHADER); + pglShaderSource(fragment_shader, 1, (const char**)&fragment_prog, 0); + pglCompileShader(fragment_shader); + print_shader_log(fragment_shader); + + pglAttachShader(gl_program, fragment_shader); + free(fragment_prog); + } + + if (vertex_prog || fragment_prog) + { + pglLinkProgram(gl_program); + pglUseProgram(gl_program); + } + + glsl_enable = true; + return true; +} + +void gl_glsl_deinit(void) +{} + +void gl_glsl_set_params(unsigned width, unsigned height, + unsigned tex_width, unsigned tex_height, + unsigned out_width, unsigned out_height) +{ + if (glsl_enable) + { + GLint location; + + float inputSize[2] = {width, height}; + location = pglGetUniformLocation(gl_program, "rubyInputSize"); + pglUniform2fv(location, 1, inputSize); + + float outputSize[2] = {out_width, out_height}; + location = pglGetUniformLocation(gl_program, "rubyOutputSize"); + pglUniform2fv(location, 1, outputSize); + + float textureSize[2] = {tex_width, tex_height}; + location = pglGetUniformLocation(gl_program, "rubyTextureSize"); + pglUniform2fv(location, 1, textureSize); + } +} + +void gl_glsl_set_proj_matrix(void) +{} diff --git a/gfx/shader_glsl.h b/gfx/shader_glsl.h new file mode 100644 index 0000000000..4b0ad8761e --- /dev/null +++ b/gfx/shader_glsl.h @@ -0,0 +1,34 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + + +#ifndef __SSNES_GLSL_H +#define __SSNES_GLSL_H + +#include + +bool gl_glsl_init(const char *path); + +void gl_glsl_deinit(void); + +void gl_glsl_set_proj_matrix(void); + +void gl_glsl_set_params(unsigned width, unsigned height, + unsigned tex_width, unsigned tex_height, + unsigned out_width, unsigned out_height); + +#endif diff --git a/hqflt/filters.h b/hqflt/filters.h index 439b3f3727..0e73b294f2 100644 --- a/hqflt/filters.h +++ b/hqflt/filters.h @@ -19,7 +19,9 @@ #ifndef __FILTERS_H #define __FILTERS_H +#ifdef HAVE_CONFIG_H #include "config.h" +#endif #ifdef HAVE_FILTER diff --git a/input/sdl.c b/input/sdl.c new file mode 100644 index 0000000000..a2260ebe2e --- /dev/null +++ b/input/sdl.c @@ -0,0 +1,177 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#include "driver.h" + +#include "SDL.h" +#include +#include "general.h" +#include +#include +#include +#include "ssnes_sdl_input.h" + +static void* sdl_input_init(void) +{ + sdl_input_t *sdl = calloc(1, sizeof(*sdl)); + if (!sdl) + return NULL; + + if (SDL_Init(SDL_INIT_JOYSTICK) < 0) + return NULL; + + sdl->num_joysticks = SDL_NumJoysticks(); + if (sdl->num_joysticks > 2) + sdl->num_joysticks = 2; + for (unsigned i = 0; i < sdl->num_joysticks; i++) + { + sdl->joysticks[i] = SDL_JoystickOpen(i); + if (!sdl->joysticks[i]) + { + SSNES_ERR("Couldn't open SDL joystick %d\n", i); + free(sdl); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + return NULL; + } + + SSNES_LOG("Opened Joystick: %s\n", SDL_JoystickName(i)); + sdl->num_axes[i] = SDL_JoystickNumAxes(sdl->joysticks[i]); + sdl->num_buttons[i] = SDL_JoystickNumButtons(sdl->joysticks[i]); + } + + return sdl; +} + +static bool sdl_key_pressed(void *data, int key) +{ + int num_keys; + Uint8 *keymap = SDL_GetKeyState(&num_keys); + + if (key >= num_keys) + return false; + + return keymap[key]; +} + +static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_keybind *key) +{ + if (sdl_key_pressed(sdl, key->key)) + return true; + if (port_num >= sdl->num_joysticks) + return false; + if (key->joykey < sdl->num_buttons[port_num] && SDL_JoystickGetButton(sdl->joysticks[port_num], key->joykey)) + return true; + + if (key->joyaxis != AXIS_NONE) + { + if (AXIS_NEG_GET(key->joyaxis) < sdl->num_axes[port_num]) + { + Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(key->joyaxis)); + float scaled = (float)val / 0x8000; + if (scaled < -g_settings.input.axis_threshold) + return true; + } + if (AXIS_POS_GET(key->joyaxis) < sdl->num_axes[port_num]) + { + Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(key->joyaxis)); + float scaled = (float)val / 0x8000; + if (scaled > g_settings.input.axis_threshold) + return true; + } + } + + return false; +} + +static int16_t sdl_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id) +{ + sdl_input_t *sdl = data; + if (device != SNES_DEVICE_JOYPAD) + return 0; + + const struct snes_keybind *snes_keybinds = binds[port == SNES_PORT_1 ? 0 : 1]; + + // Checks if button is pressed, and sets fast-forwarding state + bool pressed = false; + int port_num = port == SNES_PORT_1 ? 0 : 1; + for (int i = 0; snes_keybinds[i].id != -1; i++) + { + if (snes_keybinds[i].id == SSNES_FAST_FORWARD_KEY) + set_fast_forward_button(sdl_is_pressed(sdl, port_num, &snes_keybinds[i])); + else if (!pressed && snes_keybinds[i].id == (int)id) + pressed = sdl_is_pressed(sdl, port_num, &snes_keybinds[i]); + } + + return pressed; +} + +static void sdl_input_free(void *data) +{ + if (data) + { + sdl_input_t *sdl = data; + for (int i = 0; i < sdl->num_joysticks; i++) + SDL_JoystickClose(sdl->joysticks[i]); + + free(data); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } +} + +static void sdl_input_poll(void *data) +{ + SDL_PumpEvents(); + SDL_Event event; + + sdl_input_t *sdl = data; + // Search for events... + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + if (sdl->quitting) + { + *sdl->quitting = true; + return; + } + break; + + case SDL_VIDEORESIZE: + if (sdl->should_resize) + { + *sdl->new_width = event.resize.w; + *sdl->new_height = event.resize.h; + *sdl->should_resize = true; + } + break; + + default: + break; + } + } +} + +const input_driver_t input_sdl = { + .init = sdl_input_init, + .poll = sdl_input_poll, + .input_state = sdl_input_state, + .key_pressed = sdl_key_pressed, + .free = sdl_input_free, + .ident = "sdl" +}; + diff --git a/input/ssnes_sdl_input.h b/input/ssnes_sdl_input.h new file mode 100644 index 0000000000..5833396e3f --- /dev/null +++ b/input/ssnes_sdl_input.h @@ -0,0 +1,36 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#ifndef __SSNES_SDL_INPUT_H +#define __SSNES_SDL_INPUT_H + +#include "SDL.h" +typedef struct sdl_input +{ + SDL_Joystick *joysticks[2]; + unsigned num_axes[2]; + unsigned num_buttons[2]; + unsigned num_joysticks; + + // A video driver could pre-init with the SDL driver and have it handle resizing events... + bool *quitting; + bool *should_resize; + unsigned *new_width; + unsigned *new_height; +} sdl_input_t; + +#endif diff --git a/qb/config.libs.sh b/qb/config.libs.sh index a01eaf03d9..9e910d9cc2 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -16,17 +16,29 @@ check_lib RSOUND -lrsound rsd_init check_lib ROAR -lroar roar_vs_new check_lib JACK -ljack jack_client_open -check_lib GLFW -lglfw glfwInit -check_critical GLFW "Cannot find GLFW library." +check_pkgconf SDL sdl 1.2.10 +check_critical SDL "Cannot find SDL library." check_lib CG -lCg cgCreateContext +check_pkgconf XML libxml-2.0 -check_lib SRC -lsamplerate src_callback_new +if [ $HAVE_FFMPEG != no ]; then + check_pkgconf AVCODEC libavcodec + check_pkgconf AVFORMAT libavformat + check_pkgconf AVCORE libavcore + check_pkgconf AVUTIL libavutil + check_pkgconf SWSCALE libswscale + + ( [ $HAVE_FFMPEG = auto ] && ( [ $HAVE_AVCODEC = no ] || [ $HAVE_AVFORMAT = no ] || [ $HAVE_AVCORE = no ] || [ $HAVE_AVUTIL = no ] || [ $HAVE_SWSCALE = no ] ) && HAVE_FFMPEG=no ) || HAVE_FFMPEG=yes +fi + +check_pkgconf SRC samplerate +check_critical SRC "Cannot find libsamplerate." check_lib DYNAMIC -ldl dlopen -# Creates config.mk. -VARS="ALSA OSS AL RSOUND ROAR JACK GLFW FILTER CG DYNAMIC" +# Creates config.mk and config.h. +VARS="ALSA OSS AL RSOUND ROAR JACK SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC" create_config_make config.mk $VARS create_config_header config.h $VARS diff --git a/qb/config.params.sh b/qb/config.params.sh index 34dc9a88d2..20d930236f 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -9,8 +9,10 @@ PACKAGE_VERSION=0.1 # $3: Default arg. auto implies that HAVE_ALSA will be set according to library checks later on. add_command_line_enable DYNAMIC "Enable dynamic loading of libsnes library." no add_command_line_string LIBSNES "libsnes library used" "-lsnes" +add_command_line_enable FFMPEG "Enable FFmpeg recording support" auto add_command_line_enable FILTER "Disable CPU filter support" yes -add_command_line_enable CG "Enable CG shader support" auto +add_command_line_enable CG "Enable Cg shader support" auto +add_command_line_enable XML "Enable bSNES-style XML shader support" auto add_command_line_enable ALSA "Enable ALSA support" auto add_command_line_enable OSS "Enable OSS support" auto add_command_line_enable RSOUND "Enable RSound support" auto diff --git a/qb/qb.libs.sh b/qb/qb.libs.sh index ab8585fc83..579c91a44d 100644 --- a/qb/qb.libs.sh +++ b/qb/qb.libs.sh @@ -106,14 +106,15 @@ check_pkgconf() eval tmpval=\$$tmpval [ "$tmpval" = "no" ] && return 0 - echo -n "Checking presence of package $2 ... " + echo -n "Checking presence of package $2" eval HAVE_$1=no eval $1_CFLAGS="" eval $1_LIBS="" answer=no minver=0.0 - [ ! -z $3 ] && minver=$3 - pkg-config --atleast-version=$minver --exists "$2" && eval HAVE_$1=yes && eval $1_CFLAGS='"`pkg-config $2 --cflags`"' && eval $1_LIBS='"`pkg-config $2 --libs`"' && answer=yes + [ ! -z $3 ] && minver=$3 && echo -n " with minimum version $minver" + echo -n " ... " + pkg-config --atleast-version=$minver "$2" && eval HAVE_$1=yes && eval $1_CFLAGS='"`pkg-config $2 --cflags`"' && eval $1_LIBS='"`pkg-config $2 --libs`"' && answer=yes echo $answer PKG_CONF_USED="$PKG_CONF_USED $1" diff --git a/record/ffemu.c b/record/ffemu.c new file mode 100644 index 0000000000..b5a632d16f --- /dev/null +++ b/record/ffemu.c @@ -0,0 +1,412 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ffemu.h" + +struct video_info +{ + bool enabled; + AVCodecContext *codec; + + AVFrame *conv_frame; + uint8_t *conv_frame_buf; + int64_t frame_cnt; + + uint8_t *outbuf; + size_t outbuf_size; + + AVFormatContext *format; + +} video; + +struct audio_info +{ + bool enabled; + AVCodecContext *codec; + + int16_t *buffer; + size_t frames_in_buffer; + + int64_t frame_cnt; + + void *outbuf; + size_t outbuf_size; +} audio; + +struct muxer_info +{ + AVFormatContext *ctx; + AVStream *astream; + AVStream *vstream; +}; + +struct ffemu +{ + struct video_info video; + struct audio_info audio; + struct muxer_info muxer; + + struct ffemu_params params; +}; + +// Currently hardcoded atm. :) +static int map_audio_codec(ffemu_audio_codec codec) +{ + (void)codec; + return CODEC_ID_VORBIS; +} + +// Currently hardcoded atm. :) +static int map_video_codec(ffemu_video_codec codec) +{ + (void)codec; + return CODEC_ID_H264; +} + +static int init_audio(struct audio_info *audio, struct ffemu_params *param) +{ + AVCodec *codec = avcodec_find_encoder(map_audio_codec(param->acodec)); + if (!codec) + return -1; + + audio->codec = avcodec_alloc_context(); + + avcodec_get_context_defaults(audio->codec); + + // Hardcode this atm. + audio->codec->global_quality = 100000; + audio->codec->flags |= CODEC_FLAG_QSCALE; + audio->codec->sample_rate = param->samplerate; + audio->codec->time_base = (AVRational) { 1, param->samplerate }; + audio->codec->channels = param->channels; + audio->codec->sample_fmt = AV_SAMPLE_FMT_S16; + if (avcodec_open(audio->codec, codec) != 0) + return -1; + + + audio->buffer = av_malloc(audio->codec->frame_size * param->channels * sizeof(int16_t)); + if (!audio->buffer) + return -1; + + audio->outbuf_size = 50000; + audio->outbuf = av_malloc(audio->outbuf_size); + if (!audio->outbuf) + return -1; + + return 0; +} + +// Hardcode. +static void init_x264_param(AVCodecContext *c) +{ + c->coder_type = 1; // coder = 1 + c->flags|=CODEC_FLAG_LOOP_FILTER; // flags=+loop + c->me_cmp|= 1; // cmp=+chroma, where CHROMA = 1 + c->partitions|=X264_PART_I8X8+X264_PART_I4X4+X264_PART_P8X8+X264_PART_B8X8; // partitions=+parti8x8+parti4x4+partp8x8+partb8x8 + c->me_method=ME_HEX; // me_method=hex + c->me_subpel_quality = 7; // subq=7 + c->me_range = 16; // me_range=16 + c->gop_size = 250; // g=250 + c->keyint_min = 25; // keyint_min=25 + c->scenechange_threshold = 40; // sc_threshold=40 + c->i_quant_factor = 0.71; // i_qfactor=0.71 + c->b_frame_strategy = 1; // b_strategy=1 + c->qcompress = 0.6; // qcomp=0.6 + c->qmin = 10; // qmin=10 + c->qmax = 51; // qmax=51 + c->max_qdiff = 4; // qdiff=4 + c->max_b_frames = 3; // bf=3 + c->refs = 3; // refs=3 + c->directpred = 1; // directpred=1 + c->trellis = 1; // trellis=1 + c->flags2|=CODEC_FLAG2_BPYRAMID+CODEC_FLAG2_MIXED_REFS+CODEC_FLAG2_WPRED+CODEC_FLAG2_8X8DCT+CODEC_FLAG2_FASTPSKIP; // flags2=+bpyramid+mixed_refs+wpred+dct8x8+fastpskip + c->weighted_p_pred = 2; // wpredp=2 + + // libx264-main.ffpreset preset + c->flags2|=CODEC_FLAG2_8X8DCT; + c->flags2^=CODEC_FLAG2_8X8DCT; +} + +static int init_video(struct video_info *video, struct ffemu_params *param) +{ + AVCodec *codec = avcodec_find_encoder(map_video_codec(param->vcodec)); + if (!codec) + return -1; + + video->codec = avcodec_alloc_context(); + video->codec->width = param->out_width; + video->codec->height = param->out_height; + video->codec->time_base = (AVRational) {param->fps.den, param->fps.num}; + video->codec->crf = 25; + // Might have to change this later to account for RGB codecs. + video->codec->pix_fmt = PIX_FMT_YUV420P; + ///// Is this element in all recent ffmpeg versions? + video->codec->thread_count = 4; + ///// + video->codec->sample_aspect_ratio = av_d2q(param->aspect_ratio * param->out_height / param->out_width, 255); + init_x264_param(video->codec); + + if (avcodec_open(video->codec, codec) != 0) + return -1; + + // Allocate a big buffer :p ffmpeg API doesn't seem to give us some clues how big this buffer should be. + video->outbuf_size = 1000000; + video->outbuf = av_malloc(video->outbuf_size); + + int size = avpicture_get_size(PIX_FMT_YUV420P, param->out_width, param->out_height); + video->conv_frame_buf = av_malloc(size); + video->conv_frame = avcodec_alloc_frame(); + avpicture_fill((AVPicture*)video->conv_frame, video->conv_frame_buf, PIX_FMT_YUV420P, param->out_width, param->out_height); + + return 0; +} + +static int init_muxer(ffemu_t *handle) +{ + AVFormatContext *ctx = avformat_alloc_context(); + av_strlcpy(ctx->filename, handle->params.filename, sizeof(ctx->filename)); + ctx->oformat = av_guess_format(NULL, ctx->filename, NULL); + if (url_fopen(&ctx->pb, ctx->filename, URL_WRONLY) < 0) + { + av_free(ctx); + return -1; + } + + int stream_cnt = 0; + if (handle->video.enabled) + { + AVStream *stream = av_new_stream(ctx, stream_cnt++); + stream->codec = handle->video.codec; + + if (ctx->oformat->flags & AVFMT_GLOBALHEADER) + handle->video.codec->flags |= CODEC_FLAG_GLOBAL_HEADER; + handle->muxer.vstream = stream; + handle->muxer.vstream->sample_aspect_ratio = handle->video.codec->sample_aspect_ratio; + } + + if (handle->audio.enabled) + { + AVStream *stream = av_new_stream(ctx, stream_cnt++); + stream->codec = handle->audio.codec; + + if (ctx->oformat->flags & AVFMT_GLOBALHEADER) + handle->audio.codec->flags |= CODEC_FLAG_GLOBAL_HEADER; + handle->muxer.astream = stream; + } + + if (av_write_header(ctx) < 0) + return -1; + + handle->muxer.ctx = ctx; + return 0; +} + +ffemu_t *ffemu_new(const struct ffemu_params *params) +{ + avcodec_init(); + av_register_all(); + + ffemu_t *handle = calloc(1, sizeof(*handle)); + if (!handle) + goto error; + + handle->params = *params; + if (handle->params.vcodec != FFEMU_VIDEO_NONE) + handle->video.enabled = true; + if (handle->params.acodec != FFEMU_AUDIO_NONE) + handle->audio.enabled = true; + + if (handle->video.enabled) + if (init_video(&handle->video, &handle->params) < 0) + goto error; + + if (handle->audio.enabled) + if (init_audio(&handle->audio, &handle->params) < 0) + goto error; + + if (init_muxer(handle) < 0) + goto error; + + return handle; + +error: + ffemu_free(handle); + return NULL; +} + +void ffemu_free(ffemu_t *handle) +{ + if (handle) + { + if (handle->audio.codec) + { + avcodec_close(handle->audio.codec); + av_free(handle->audio.codec); + } + + if (handle->audio.buffer) + av_free(handle->audio.buffer); + + if (handle->video.codec) + { + avcodec_close(handle->video.codec); + av_free(handle->video.codec); + } + + if (handle->video.conv_frame) + av_free(handle->video.conv_frame); + + if (handle->video.conv_frame_buf) + av_free(handle->video.conv_frame_buf); + + free(handle); + } +} + +// Need to make this thread based, but hey. +int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data) +{ + if (!handle->video.enabled) + return -1; + + // This is deprecated, can't find a replacement... :( + // Hardcode pixel format for now (SNES) + struct SwsContext *conv_ctx = sws_getContext(data->width, data->height, PIX_FMT_RGB555LE, + handle->params.out_width, handle->params.out_height, PIX_FMT_YUV420P, handle->params.rescaler == FFEMU_RESCALER_LANCZOS ? SWS_LANCZOS : SWS_POINT, + NULL, NULL, NULL); + + int linesize = data->pitch; + + sws_scale(conv_ctx, (const uint8_t* const*)&data->data, &linesize, 0, handle->params.out_width, handle->video.conv_frame->data, handle->video.conv_frame->linesize); + + handle->video.conv_frame->pts = handle->video.frame_cnt; + handle->video.conv_frame->display_picture_number = handle->video.frame_cnt; + + int outsize = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, handle->video.conv_frame); + + if (outsize < 0) + return -1; + + sws_freeContext(conv_ctx); + + AVPacket pkt; + av_init_packet(&pkt); + pkt.stream_index = handle->muxer.vstream->index; + pkt.data = handle->video.outbuf; + pkt.size = outsize; + + pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base, handle->muxer.vstream->time_base); + + if (handle->video.codec->coded_frame->key_frame) + pkt.flags |= AV_PKT_FLAG_KEY; + + if (pkt.size > 0) + { + if (av_interleaved_write_frame(handle->muxer.ctx, &pkt) < 0) + return -1; + } + + handle->video.frame_cnt++; + + return 0; +} + +int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data) +{ + if (!handle->audio.enabled) + return -1; + + AVPacket pkt; + av_init_packet(&pkt); + pkt.stream_index = handle->muxer.astream->index; + pkt.data = handle->audio.outbuf; + + size_t written_frames = 0; + while (written_frames < data->frames) + { + size_t can_write = handle->audio.codec->frame_size - handle->audio.frames_in_buffer; + size_t write_frames = data->frames > can_write ? can_write : data->frames; + + memcpy(handle->audio.buffer + handle->audio.frames_in_buffer * handle->params.channels, + data->data + written_frames * handle->params.channels, + write_frames * handle->params.channels * sizeof(int16_t)); + + written_frames += write_frames; + handle->audio.frames_in_buffer += write_frames; + + if (handle->audio.frames_in_buffer == (size_t)handle->audio.codec->frame_size) + { + + int out_size = avcodec_encode_audio(handle->audio.codec, handle->audio.outbuf, handle->audio.outbuf_size, handle->audio.buffer); + if (out_size < 0) + return -1; + + pkt.size = out_size; + if (handle->audio.codec->coded_frame && handle->audio.codec->coded_frame->pts != AV_NOPTS_VALUE) + { + pkt.pts = av_rescale_q(handle->audio.codec->coded_frame->pts, handle->audio.codec->time_base, handle->muxer.astream->time_base); + } + else + { + pkt.pts = av_rescale_q(handle->audio.frame_cnt, handle->audio.codec->time_base, handle->muxer.astream->time_base); + } + + pkt.flags |= AV_PKT_FLAG_KEY; + handle->audio.frames_in_buffer = 0; + handle->audio.frame_cnt += handle->audio.codec->frame_size; + + if (pkt.size > 0) + { + if (av_interleaved_write_frame(handle->muxer.ctx, &pkt) < 0) + return -1; + } + } + } + return 0; +} + +int ffemu_finalize(ffemu_t *handle) +{ + // Push out delayed frames. (MPEG codecs) + if (handle->video.enabled) + { + AVPacket pkt; + av_init_packet(&pkt); + pkt.stream_index = handle->muxer.vstream->index; + pkt.data = handle->video.outbuf; + + int out_size = 0; + do + { + out_size = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, NULL); + pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base, handle->muxer.vstream->time_base); + + if (handle->video.codec->coded_frame->key_frame) + pkt.flags |= AV_PKT_FLAG_KEY; + + pkt.size = out_size; + + if (pkt.size > 0) + { + int err = av_interleaved_write_frame(handle->muxer.ctx, &pkt); + if (err < 0) + break; + } + + } while (out_size > 0); + } + + // Write final data. + av_write_trailer(handle->muxer.ctx); + + return 0; +} + diff --git a/record/ffemu.h b/record/ffemu.h new file mode 100644 index 0000000000..f00a1c131d --- /dev/null +++ b/record/ffemu.h @@ -0,0 +1,127 @@ +#ifndef __FFEMU_H +#define __FFEMU_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Available video codecs +typedef enum ffemu_video_codec +{ + FFEMU_VIDEO_NONE, + FFEMU_VIDEO_H264, + FFEMU_VIDEO_MPEG4, +} ffemu_video_codec; + +// Available audio codecs +typedef enum ffemu_audio_codec +{ + FFEMU_AUDIO_NONE, + FFEMU_AUDIO_VORBIS, + FFEMU_AUDIO_MP3, + FFEMU_AUDIO_AAC, +} ffemu_audio_codec; + +// Available pixel formats +typedef enum ffemu_pixel_format +{ + FFEMU_FMT_XBGR1555, + FFEMU_FMT_RGB888, +} ffemu_pixel_format; + +typedef enum ffemu_rescaler +{ + FFEMU_RESCALER_LANCZOS, + FFEMU_RESCALER_POINT +} ffemu_rescaler; + +struct ffemu_rational +{ + unsigned num; + unsigned den; +}; + +// Parameters passed to ffemu_new() +struct ffemu_params +{ + // Video codec to use. If not recording video, select FFEMU_VIDEO_NONE. + ffemu_video_codec vcodec; + + // Desired output resolution. + unsigned out_width; + unsigned out_height; + float aspect_ratio; + + // Rescaler for video. + ffemu_rescaler rescaler; + + // Pixel format for video input. + ffemu_pixel_format format; + + // FPS of video input. + struct ffemu_rational fps; + + // Relative video quality. 0 is lossless (if available), 10 is very low quality. + // A value over 10 is codec defined if it will give even worse quality. + unsigned videoq; + + // Define some video codec dependent option. (E.g. h264 profiles) + uint64_t video_opt; + + // Audio codec. If not recording audio, select FFEMU_AUDIO_NONE. + ffemu_audio_codec acodec; + + // Audio sample rate. + unsigned samplerate; + + // Audio channels. + unsigned channels; + + // Audio bits. Sample format is always signed PCM in native byte order. + //unsigned bits; + + // Relative audio quality. 0 is lossless (if available), 10 is very low quality. + // A value over 10 is codec defined if it will give even worse quality. + // Some codecs might ignore this (lossless codecs such as FLAC). + unsigned audioq; + + // Define some audio codec dependent option. + uint64_t audio_opt; + + // Filename to dump to. + const char *filename; +}; + +struct ffemu_video_data +{ + const void *data; + unsigned width; + unsigned height; + unsigned pitch; +}; + +struct ffemu_audio_data +{ + const int16_t *data; + size_t frames; +}; + +typedef struct ffemu ffemu_t; + +ffemu_t *ffemu_new(const struct ffemu_params *params); +void ffemu_free(ffemu_t* handle); + +int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data); +int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data); +int ffemu_finalize(ffemu_t *handle); + + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/settings.c b/settings.c index 0288dd3dd6..5337f042c7 100644 --- a/settings.c +++ b/settings.c @@ -21,7 +21,11 @@ #include #include #include "hqflt/filters.h" + +#ifdef HAVE_CONFIG_H #include "config.h" +#endif + #include struct settings g_settings; @@ -32,11 +36,12 @@ static void set_defaults(void) { const char *def_video = NULL; const char *def_audio = NULL; + const char *def_input = NULL; switch (VIDEO_DEFAULT_DRIVER) { case VIDEO_GL: - def_video = "glfw"; + def_video = "gl"; break; default: break; @@ -59,16 +64,28 @@ static void set_defaults(void) case AUDIO_AL: def_audio = "openal"; break; + case AUDIO_SDL: + def_audio = "sdl"; + break; default: break; } - // No input atm ... It is in the GLFW driver. + switch (INPUT_DEFAULT_DRIVER) + { + case INPUT_SDL: + def_input = "sdl"; + break; + default: + break; + } if (def_video) strncpy(g_settings.video.driver, def_video, sizeof(g_settings.video.driver) - 1); if (def_audio) strncpy(g_settings.audio.driver, def_audio, sizeof(g_settings.audio.driver) - 1); + if (def_input) + strncpy(g_settings.input.driver, def_input, sizeof(g_settings.input.driver) - 1); g_settings.video.xscale = xscale; g_settings.video.yscale = yscale; @@ -78,6 +95,7 @@ static void set_defaults(void) g_settings.video.vsync = vsync; g_settings.video.smooth = video_smooth; g_settings.video.force_aspect = force_aspect; + g_settings.video.aspect_ratio = SNES_ASPECT_RATIO; g_settings.audio.enable = audio_enable; g_settings.audio.out_rate = out_rate; @@ -97,7 +115,7 @@ static void set_defaults(void) g_settings.input.load_state_key = LOAD_STATE_KEY; g_settings.input.toggle_fullscreen_key = TOGGLE_FULLSCREEN; g_settings.input.axis_threshold = AXIS_THRESHOLD; - g_settings.input.exit_emulator_key = GLFW_KEY_ESC; + g_settings.input.exit_emulator_key = SDLK_ESCAPE; } void parse_config(void) @@ -116,6 +134,10 @@ void parse_config(void) } else { +#ifdef _WIN32 + // Just do something for now. + conf = config_file_new("ssnes.cfg"); +#else const char *xdg = getenv("XDG_CONFIG_HOME"); const char *home = getenv("HOME"); if (xdg) @@ -128,13 +150,14 @@ void parse_config(void) else if (home) { char conf_path[strlen(home) + strlen("/.ssnesrc ")]; - strcpy(conf_path, xdg); + strcpy(conf_path, home); strcat(conf_path, "/.ssnesrc"); conf = config_file_new(conf_path); } // Try this as a last chance... if (!conf) conf = config_file_new("/etc/ssnes.cfg"); +#endif } set_defaults(); @@ -171,12 +194,21 @@ void parse_config(void) if (config_get_bool(conf, "video_force_aspect", &tmp_bool)) g_settings.video.force_aspect = tmp_bool; + if (config_get_double(conf, "video_aspect_ratio", &tmp_double)) + g_settings.video.aspect_ratio = tmp_double; + if (config_get_string(conf, "video_cg_shader", &tmp_str)) { strncpy(g_settings.video.cg_shader_path, tmp_str, sizeof(g_settings.video.cg_shader_path) - 1); free(tmp_str); } + if (config_get_string(conf, "video_bsnes_shader", &tmp_str)) + { + strncpy(g_settings.video.bsnes_shader_path, tmp_str, sizeof(g_settings.video.bsnes_shader_path)); + free(tmp_str); + } + #ifdef HAVE_FILTER if (config_get_string(conf, "video_filter", &tmp_str)) { @@ -255,6 +287,11 @@ void parse_config(void) strncpy(g_settings.audio.driver, tmp_str, sizeof(g_settings.audio.driver) - 1); free(tmp_str); } + if (config_get_string(conf, "input_driver", &tmp_str)) + { + strncpy(g_settings.input.driver, tmp_str, sizeof(g_settings.input.driver) - 1); + free(tmp_str); + } if (config_get_string(conf, "libsnes_path", &tmp_str)) { strncpy(g_settings.libsnes, tmp_str, sizeof(g_settings.libsnes) - 1); @@ -263,8 +300,6 @@ void parse_config(void) read_keybinds(conf); - // TODO: Keybinds. - config_file_free(conf); } @@ -291,7 +326,7 @@ static const struct bind_map bind_maps[2][13] = { { "input_player1_right", "input_player1_right_btn", "input_player1_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT }, { "input_player1_up", "input_player1_up_btn", "input_player1_up_axis", SNES_DEVICE_ID_JOYPAD_UP }, { "input_player1_down", "input_player1_down_btn", "input_player1_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN }, - { "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SNES_FAST_FORWARD_KEY } + { "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SSNES_FAST_FORWARD_KEY } }, { { "input_player2_a", "input_player2_a_btn", NULL, SNES_DEVICE_ID_JOYPAD_A }, @@ -306,44 +341,44 @@ static const struct bind_map bind_maps[2][13] = { { "input_player2_right", "input_player2_right_btn", "input_player2_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT }, { "input_player2_up", "input_player2_up_btn", "input_player2_up_axis", SNES_DEVICE_ID_JOYPAD_UP }, { "input_player2_down", "input_player2_down_btn", "input_player2_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN }, - { "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SNES_FAST_FORWARD_KEY } + { "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SSNES_FAST_FORWARD_KEY } } }; -struct glfw_map +struct key_map { const char *str; int key; }; // Edit: Not portable to different input systems atm. Might move this map into the driver itself or something. -static const struct glfw_map glfw_map[] = { - { "left", GLFW_KEY_LEFT }, - { "right", GLFW_KEY_RIGHT }, - { "up", GLFW_KEY_UP }, - { "down", GLFW_KEY_DOWN }, - { "enter", GLFW_KEY_ENTER }, - { "tab", GLFW_KEY_TAB }, - { "insert", GLFW_KEY_INSERT }, - { "del", GLFW_KEY_DEL }, - { "rshift", GLFW_KEY_RSHIFT }, - { "shift", GLFW_KEY_LSHIFT }, - { "ctrl", GLFW_KEY_LCTRL }, - { "alt", GLFW_KEY_LALT }, - { "space", GLFW_KEY_SPACE }, - { "escape", GLFW_KEY_ESC }, - { "f1", GLFW_KEY_F1 }, - { "f2", GLFW_KEY_F2 }, - { "f3", GLFW_KEY_F3 }, - { "f4", GLFW_KEY_F4 }, - { "f5", GLFW_KEY_F5 }, - { "f6", GLFW_KEY_F6 }, - { "f7", GLFW_KEY_F7 }, - { "f8", GLFW_KEY_F8 }, - { "f9", GLFW_KEY_F9 }, - { "f10", GLFW_KEY_F10 }, - { "f11", GLFW_KEY_F11 }, - { "f12", GLFW_KEY_F12 }, +static const struct key_map sdlk_map[] = { + { "left", SDLK_LEFT }, + { "right", SDLK_RIGHT }, + { "up", SDLK_UP }, + { "down", SDLK_DOWN }, + { "enter", SDLK_RETURN }, + { "tab", SDLK_TAB }, + { "insert", SDLK_INSERT }, + { "del", SDLK_DELETE }, + { "rshift", SDLK_RSHIFT }, + { "shift", SDLK_LSHIFT }, + { "ctrl", SDLK_LCTRL }, + { "alt", SDLK_LALT }, + { "space", SDLK_SPACE }, + { "escape", SDLK_ESCAPE }, + { "f1", SDLK_F1 }, + { "f2", SDLK_F2 }, + { "f3", SDLK_F3 }, + { "f4", SDLK_F4 }, + { "f5", SDLK_F5 }, + { "f6", SDLK_F6 }, + { "f7", SDLK_F7 }, + { "f8", SDLK_F8 }, + { "f9", SDLK_F9 }, + { "f10", SDLK_F10 }, + { "f11", SDLK_F11 }, + { "f12", SDLK_F12 }, }; static struct snes_keybind *find_snes_bind(unsigned port, int id) @@ -358,23 +393,23 @@ static struct snes_keybind *find_snes_bind(unsigned port, int id) return NULL; } -static int find_glfw_bind(const char *str) +static int find_sdlk_bind(const char *str) { - for (int i = 0; i < sizeof(glfw_map)/sizeof(struct glfw_map); i++) + for (int i = 0; i < sizeof(sdlk_map)/sizeof(struct key_map); i++) { - if (strcasecmp(glfw_map[i].str, str) == 0) - return glfw_map[i].key; + if (strcasecmp(sdlk_map[i].str, str) == 0) + return sdlk_map[i].key; } return -1; } -static int find_glfw_key(const char *str) +static int find_sdlk_key(const char *str) { // If the bind is a normal key-press ... if (strlen(str) == 1 && isalpha(*str)) - return toupper(*str); + return (int)SDLK_a + (tolower(*str) - (int)'a'); else // Check if we have a special mapping for it. - return find_glfw_bind(str); + return find_sdlk_bind(str); } static void read_keybinds(config_file_t *conf) @@ -393,7 +428,7 @@ static void read_keybinds(config_file_t *conf) if (bind_maps[j][i].key && config_get_string(conf, bind_maps[j][i].key, &tmp_key)) { - int key = find_glfw_key(tmp_key); + int key = find_sdlk_key(tmp_key); if (key >= 0) bind->key = key; @@ -428,28 +463,28 @@ static void read_keybinds(config_file_t *conf) char *tmp_str; if (config_get_string(conf, "input_toggle_fullscreen", &tmp_str)) { - int key = find_glfw_key(tmp_str); + int key = find_sdlk_key(tmp_str); if (key >= 0) g_settings.input.toggle_fullscreen_key = key; free(tmp_str); } if (config_get_string(conf, "input_save_state", &tmp_str)) { - int key = find_glfw_key(tmp_str); + int key = find_sdlk_key(tmp_str); if (key >= 0) g_settings.input.save_state_key = key; free(tmp_str); } if (config_get_string(conf, "input_load_state", &tmp_str)) { - int key = find_glfw_key(tmp_str); + int key = find_sdlk_key(tmp_str); if (key >= 0) g_settings.input.load_state_key = key; free(tmp_str); } if (config_get_string(conf, "input_exit_emulator", &tmp_str)) { - int key = find_glfw_key(tmp_str); + int key = find_sdlk_key(tmp_str); if (key >= 0) g_settings.input.exit_emulator_key = key; free(tmp_str); diff --git a/ssnes.c b/ssnes.c index cc0c518b10..af3a1b724c 100644 --- a/ssnes.c +++ b/ssnes.c @@ -17,8 +17,6 @@ #include -#include -#include #include #include #include @@ -29,6 +27,11 @@ #include "hqflt/filters.h" #include "general.h" #include "dynamic.h" +#include "record/ffemu.h" +#include +#ifdef HAVE_SRC +#include +#endif struct global g_extern = { .video_active = true, @@ -83,6 +86,19 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height) if ( !g_extern.video_active ) return; +#ifdef HAVE_FFMPEG + if (g_extern.recording) + { + struct ffemu_video_data ffemu_data = { + .data = data, + .pitch = height == 448 || height == 478 ? 1024 : 2048, + .width = width, + .height = height + }; + ffemu_push_video(g_extern.rec, &ffemu_data); + } +#endif + #ifdef HAVE_FILTER uint16_t output_filter[width * height * 4 * 4]; uint16_t output[width * height]; @@ -130,6 +146,20 @@ static void audio_sample(uint16_t left, uint16_t right) if ( !g_extern.audio_active ) return; +#ifdef HAVE_FFMPEG + if (g_extern.recording) + { + static int16_t static_data[2]; + static_data[0] = left; + static_data[1] = right; + struct ffemu_audio_data ffemu_data = { + .data = static_data, + .frames = 1 + }; + ffemu_push_audio(g_extern.rec, &ffemu_data); + } +#endif + static float data[AUDIO_CHUNK_SIZE_NONBLOCKING]; static int data_ptr = 0; @@ -187,15 +217,31 @@ static void fill_pathname(char *out_path, char *in_path, const char *replace) strcat(out_path, replace); } +#ifdef HAVE_FFMPEG +#define FFMPEG_HELP_QUARK " | -r/--record " +#else +#define FFMPEG_HELP_QUARK +#endif + +#ifdef _WIN32 +#define SSNES_DEFAULT_CONF_PATH_STR "\n\tDefaults to ssnes.cfg in same directory as ssnes.exe" +#else +#define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg" +#endif + static void print_help(void) { puts("================================================="); puts("ssnes: Simple Super Nintendo Emulator (libsnes)"); puts("================================================="); - puts("Usage: ssnes [rom file] [-h/--help | -s/--save]"); + puts("Usage: ssnes [rom file] [-h/--help | -s/--save" FFMPEG_HELP_QUARK "]"); puts("\t-h/--help: Show this help message"); puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin"); - puts("\t-c/--config: Path for config file. Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg"); + puts("\t-c/--config: Path for config file." SSNES_DEFAULT_CONF_PATH_STR); + +#ifdef HAVE_FFMPEG + puts("\t-r/--record: Path to record video file. Settings for video/audio codecs are found in config file."); +#endif puts("\t-v/--verbose: Verbose logging"); } @@ -210,13 +256,23 @@ static void parse_input(int argc, char *argv[]) struct option opts[] = { { "help", 0, NULL, 'h' }, { "save", 1, NULL, 's' }, +#ifdef HAVE_FFMPEG + { "record", 1, NULL, 'r' }, +#endif { "verbose", 0, NULL, 'v' }, { "config", 0, NULL, 'c' }, { NULL, 0, NULL, 0 } }; int option_index = 0; - char optstring[] = "hs:vc:"; + +#ifdef HAVE_FFMPEG +#define FFMPEG_RECORD_ARG "r:" +#else +#define FFMPEG_RECORD_ARG +#endif + + char optstring[] = "hs:vc:" FFMPEG_RECORD_ARG; for(;;) { int c = getopt_long(argc, argv, optstring, opts, &option_index); @@ -243,6 +299,13 @@ static void parse_input(int argc, char *argv[]) strncpy(g_extern.config_path, optarg, sizeof(g_extern.config_path) - 1); break; +#ifdef HAVE_FFMPEG + case 'r': + strncpy(g_extern.record_path, optarg, sizeof(g_extern.record_path) - 1); + g_extern.recording = true; + break; +#endif + case '?': print_help(); exit(1); @@ -298,7 +361,7 @@ int main(int argc, char *argv[]) SSNES_ERR("Could not read ROM file.\n"); exit(1); } - SSNES_LOG("ROM size: %zi bytes\n", rom_len); + SSNES_LOG("ROM size: %d bytes\n", (int)rom_len); if (g_extern.rom_file != NULL) fclose(g_extern.rom_file); @@ -335,23 +398,49 @@ int main(int argc, char *argv[]) load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM); load_save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC); - ///// TODO: Modular friendly!!! +#ifdef HAVE_FFMPEG + // Hardcode these options at the moment. Should be specificed in the config file later on. + if (g_extern.recording) + { + struct ffemu_rational ntsc_fps = {60000, 1001}; + struct ffemu_rational pal_fps = {50000, 1001}; + struct ffemu_params params = { + .vcodec = FFEMU_VIDEO_H264, + .acodec = FFEMU_AUDIO_VORBIS, + .rescaler = FFEMU_RESCALER_POINT, + .out_width = 512, + .out_height = 448, + .channels = 2, + .samplerate = 32040, + .filename = g_extern.record_path, + .fps = snes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps, + .aspect_ratio = 4.0/3 + }; + SSNES_LOG("Recording with FFmpeg to %s.\n", g_extern.record_path); + g_extern.rec = ffemu_new(¶ms); + if (!g_extern.rec) + { + SSNES_ERR("Failed to start FFmpeg recording.\n"); + g_extern.recording = false; + } + } +#endif + for(;;) { - bool quitting = glfwGetKey(g_settings.input.exit_emulator_key) || !glfwGetWindowParam(GLFW_OPENED); - - if ( quitting ) + if (driver.input->key_pressed(driver.input_data, g_settings.input.exit_emulator_key) || + !driver.video->alive(driver.video_data)) break; - if ( glfwGetKey( g_settings.input.save_state_key )) + if (driver.input->key_pressed(driver.input_data, g_settings.input.save_state_key)) { write_file(statefile_name, serial_data, serial_size); } - else if ( glfwGetKey( g_settings.input.load_state_key ) ) + else if (driver.input->key_pressed(driver.input_data, g_settings.input.load_state_key)) load_state(statefile_name, serial_data, serial_size); - else if ( glfwGetKey( g_settings.input.toggle_fullscreen_key ) ) + else if (driver.input->key_pressed(driver.input_data, g_settings.input.toggle_fullscreen_key)) { g_settings.video.fullscreen = !g_settings.video.fullscreen; uninit_drivers(); @@ -361,6 +450,14 @@ int main(int argc, char *argv[]) psnes_run(); } +#ifdef HAVE_FFMPEG + if (g_extern.recording) + { + ffemu_finalize(g_extern.rec); + ffemu_free(g_extern.rec); + } +#endif + save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM); save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC); diff --git a/ssnes.cfg b/ssnes.cfg index b9d5f9aaba..7f52b5961f 100644 --- a/ssnes.cfg +++ b/ssnes.cfg @@ -10,8 +10,8 @@ # video_yscale = 3.0 # Fullscreen resolution -# video_fullscreen_x = 1280 -# video_fullscreen_y = 720 +# video_fullscreen_x = 1920 +# video_fullscreen_y = 1200 # Start in fullscreen. Can be changed at runtime. # video_fullscreen = false @@ -22,12 +22,18 @@ # Smoothens picture with bilinear filtering. Should be disabled if using Cg shaders. # video_smooth = true -# Forces rendering area to stay 4:3. +# Forces rendering area to stay equal to SNES aspect ratio 4:3 or as defined in video_aspect_ratio. # video_force_aspect = true +# A floating point value for video aspect ratio (width / height) +# video_aspect_ratio = 1.333 + # Path to Cg shader. If enabled # video_cg_shader = "/path/to/cg/shader.cg" +# Path to bSNES-style XML shader. If both Cg shader path and XML shader path are defined, Cg shader will take priority. +# video_bsnes_shader = "/path/to/bsnes/xml/shader.shader" + # CPU-based filter. Valid ones are: hq2x, hq4x, grayscale, bleed, ntsc. # video_filter = ntsc @@ -43,10 +49,10 @@ # Lower this (slightly) if you are experiencing frequent audio dropouts while vsync is enabled. # Conversely, increase this slightly if you are experiencing good audio, # but lots of dropped frames. Reasonable values for this is 32000 +/- 100 Hz. -# audio_in_rate = 31950 +# audio_in_rate = 31980 -# Audio driver backend. Depending on configuration possible candidates are: alsa, oss, rsound, roar, openal -# audio_driver = alsa +# Audio driver backend. Depending on configuration possible candidates are: alsa, oss, jack, rsound, roar, openal and sdl +# audio_driver = # Override the default audio device the audio_driver uses. # audio_device = @@ -62,8 +68,11 @@ ### Input +# Input driver. Depending on video driver, it might force a different input driver. +# input_driver = sdl + # Defines axis threshold. Possible values are [0.0, 1.0] -# input_axis_threshold = 0.6 +# input_axis_threshold = 0.5 # Keyboard input. Will recognize normal keypresses and special keys like "left", "right", and so on. # input_player1_a = x