Merge git://github.com/Themaister/SSNES

This commit is contained in:
Themaister 2011-01-08 12:08:19 +01:00
commit b971957f41
29 changed files with 2195 additions and 361 deletions

View File

@ -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)

59
Makefile.win32 Normal file
View File

@ -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

View File

@ -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.

121
audio/buffer.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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;
}

36
audio/buffer.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __BUFFER_H
#define __BUFFER_H
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#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

217
audio/sdl.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "driver.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#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"
};

View File

@ -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;
}
}

View File

@ -19,14 +19,28 @@
//
//
#ifndef __CONFIG_H
#define __CONFIG_H
#ifndef __CONFIG_DEF_H
#define __CONFIG_DEF_H
#include <GL/glfw.h>
#include <stdbool.h>
#include "libsnes.hpp"
#include "driver.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SDL
#include <SDL/SDL.h>
#else
#error HAVE_SDL is not defined!
#endif
#ifdef HAVE_SRC
#include <samplerate.h>
#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

View File

@ -21,7 +21,10 @@
#include <stdio.h>
#include <string.h>
#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
{

View File

@ -19,12 +19,13 @@
#ifndef __DRIVER__H
#define __DRIVER__H
#include <sys/types.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#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

View File

@ -18,7 +18,12 @@
#include "dynamic.h"
#include "general.h"
#include <string.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <libsnes.hpp>
#ifdef HAVE_DYNAMIC
#include <dlfcn.h>

1
file.h
View File

@ -22,6 +22,7 @@
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <sys/types.h>
ssize_t read_file(FILE *file, void **buf);

View File

@ -20,9 +20,17 @@
#define __SSNES_GENERAL_H
#include <stdbool.h>
#include <samplerate.h>
#include "driver.h"
#include <stdio.h>
#include "record/ffemu.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SRC
#include <samplerate.h>
#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

391
gfx/gl.c
View File

@ -15,23 +15,38 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
#define GL_GLEXT_PROTOTYPES
#include "driver.h"
#include <GL/glfw.h>
#include <GL/glext.h>
#include <stdint.h>
#include "libsnes.hpp"
#include <stdio.h>
#include <sys/time.h>
#include <string.h>
#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 <GL/glext.h>
#ifndef _WIN32
#include <GL/glx.h>
#endif
#ifdef HAVE_CG
#include <Cg/cg.h>
#include <Cg/cgGL.h>
#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"
};

104
gfx/shader_cg.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "shader_cg.h"
#include <Cg/cg.h>
#include <Cg/cgGL.h>
#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;
}

34
gfx/shader_cg.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __SSNES_CG_H
#define __SSNES_CG_H
#include <stdbool.h>
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

253
gfx/shader_glsl.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
//
// GLSL code here is mostly copypasted from bSNES.
//
#include <stdbool.h>
#include <string.h>
#include "general.h"
#define NO_SDL_GLEXT
#include <GL/gl.h>
#include "SDL.h"
#include "SDL_opengl.h"
#include <stdlib.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#define GL_GLEXT_PROTOTYPES
#include <GL/glext.h>
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)
{}

34
gfx/shader_glsl.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __SSNES_GLSL_H
#define __SSNES_GLSL_H
#include <stdbool.h>
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

View File

@ -19,7 +19,9 @@
#ifndef __FILTERS_H
#define __FILTERS_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_FILTER

177
input/sdl.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "driver.h"
#include "SDL.h"
#include <stdbool.h>
#include "general.h"
#include <stdint.h>
#include <stdlib.h>
#include <libsnes.hpp>
#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"
};

36
input/ssnes_sdl_input.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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

View File

@ -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

View File

@ -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"

412
record/ffemu.c Normal file
View File

@ -0,0 +1,412 @@
#include <libavcodec/avcodec.h>
#include <libavutil/mathematics.h>
#include <libavutil/avutil.h>
#include <libavutil/avstring.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#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;
}

127
record/ffemu.h Normal file
View File

@ -0,0 +1,127 @@
#ifndef __FFEMU_H
#define __FFEMU_H
#include <stdint.h>
#include <stddef.h>
#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

View File

@ -21,7 +21,11 @@
#include <assert.h>
#include <string.h>
#include "hqflt/filters.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <ctype.h>
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);

123
ssnes.c
View File

@ -17,8 +17,6 @@
#include <stdbool.h>
#include <GL/glfw.h>
#include <samplerate.h>
#include <libsnes.hpp>
#include <stdio.h>
#include <stdlib.h>
@ -29,6 +27,11 @@
#include "hqflt/filters.h"
#include "general.h"
#include "dynamic.h"
#include "record/ffemu.h"
#include <assert.h>
#ifdef HAVE_SRC
#include <samplerate.h>
#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(&params);
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);

View File

@ -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