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