Compare commits

...
82 Commits
Author SHA1 Message Date
Themaister 70e9f084b7 Merge branch 'master' of github.com:Themaister/SSNES 2011-10-22 20:20:03 +02:00
Themaister 824790a80d Updates for 0.9. 2011-10-22 20:19:40 +02:00
Themaister 23d23983bc Fix broken #define in header. 2011-10-21 22:12:03 +02:00
Themaister c42655ed91 Logging for paused. 2011-10-20 23:33:15 +02:00
Themaister f46ee78c76 Apply a hack and push the frame several times when changing fullscreen. 2011-10-20 23:28:46 +02:00
Themaister 5d1d188231 Hardcode hermite for 2 channels. 2011-10-20 22:26:41 +02:00
Themaister c55ce543c2 Revert "Use C99 stdio in mingw, yay" :\
This reverts commit de8bc62474.
2011-10-19 18:12:30 +02:00
Themaister de8bc62474 Use C99 stdio in mingw, yay 2011-10-19 18:10:05 +02:00
Themaister 397da887a7 Merge branch 'master' of github.com:Themaister/SSNES 2011-10-19 08:16:26 +02:00
Themaister f44733d7f6 Only show paused rewind for 1 frame. 2011-10-26 03:24:39 +02:00
Themaister 5faa28a1a2 Clarify x/yscale. 2011-10-19 08:16:01 +02:00
Themaister efaa85ff9b Cleaner. 2011-10-18 21:59:37 +02:00
Themaister 8f5580be10 Frame advance goes into pause. 2011-10-18 21:57:28 +02:00
Themaister c7acd99548 Same for SDL ... 2011-10-18 19:52:43 +02:00
Themaister 64ee13e6f4 Push window loop in .alive(). 2011-10-18 19:51:38 +02:00
Themaister e134f8afc7 And yet another ... :) 2011-10-18 19:22:20 +02:00
Themaister dd89e6fbe0 Fix build without FFmpeg. 2011-10-18 19:16:38 +02:00
Themaister a79a1b46f7 Updates for 0.9-rc4. 2011-10-18 19:12:28 +02:00
Themaister 6467f51ea8 Frame caching for more "proper" fullscreen switch. 2011-10-18 17:26:15 +02:00
Themaister 141eefb03b Fix fullscreen pause toggle for windows. 2011-10-18 14:39:54 +02:00
Themaister 068c224d83 Include windows.h properly. 2011-10-18 14:05:30 +02:00
Themaister 5bc8090ea7 Allow fullscreen toggle while paused. 2011-10-18 13:29:39 +02:00
Themaister 466ab1ef8d Frame advance. 2011-10-17 21:30:58 +02:00
Themaister a1c19ed1f5 Minor refactor. 2011-10-17 20:46:38 +02:00
Themaister cce4b7d20c Allow #include depth. 2011-10-17 20:09:57 +02:00
Themaister 80925ffc11 Some Windows fixes. 2011-10-17 19:30:52 +02:00
Themaister 9eb14e328d Add "#include" for config files. 2011-10-17 19:11:31 +02:00
Themaister 5ae0cb1821 Base size for windowed mode calculations. 2011-10-16 21:36:17 +02:00
Themaister fc594f7f0f Fix edge case on input poll. 2011-10-16 15:45:04 +02:00
Themaister 0c4e406ee7 Remove unused variable. 2011-10-16 12:51:14 +02:00
Themaister a082ec5251 Fix segfault. 2011-10-16 04:01:01 +02:00
Themaister 953b5daf0a Pause hooked up. 2011-10-16 02:00:55 +02:00
Themaister b4ff622f32 Title buf hints. Purge samplerate opts. 2011-10-15 20:05:34 +02:00
Themaister f26b54c998 Updates for RC3. 2011-10-15 18:51:58 +02:00
Themaister d7b3f39136 SSE2 optimized a bit :D 2011-10-15 18:09:44 +02:00
Themaister 5bcf601a40 Refactor out audio conversion. 2011-10-15 17:47:29 +02:00
Themaister e91ebdac97 Do not recalculate ratio all the time. 2011-10-15 16:16:13 +02:00
Themaister b2dd12d186 Rewind audio! :D 2011-10-15 14:33:41 +02:00
Themaister 9c79bf0ba2 Refactor audio flushing method. 2011-10-15 13:16:32 +02:00
Themaister f48247a18b Style nits ... 2011-10-15 12:56:48 +02:00
Themaister 48e015558e Style nits. 2011-10-15 12:54:47 +02:00
Themaister 0ff3cbc93b Nits. 2011-10-15 12:51:17 +02:00
Themaister 25f0e162a4 Purge support for libsamplerate. Obsolete. 2011-10-15 12:48:15 +02:00
Themaister 0810812cd9 Slight cleanups when pushing audio. 2011-10-15 12:42:39 +02:00
Themaister 15e0b721e6 Updates for RC2. 2011-10-14 14:07:03 +02:00
Themaister 9d12c3ac87 Use Makefile.win for Phoenix. 2011-10-14 13:34:27 +02:00
Themaister a7c3d8d17a Some slight recording tweaks. 2011-10-14 11:27:02 +02:00
Themaister 8fbfdee011 Do not always force 4.0 / 3 aspect ratio for output. 2011-10-14 11:00:31 +02:00
Themaister bc999e72fa Updates for v0.9-rc1. Use --features rather than spamming down --help. 2011-10-11 20:02:46 +02:00
Themaister c7f8110670 Use glorious x264 RGB. 2011-10-10 22:24:26 +02:00
Themaister b0dbc67281 Fix build when only Cg is being compiled. 2011-10-17 15:35:02 +02:00
Themaister 4916f7b08b Add placeholder define for when libx264 RGB support is built in. 2011-10-09 12:37:53 +02:00
Themaister 1058d94ec1 x264 RGB recording. 2011-10-09 12:27:02 +02:00
Themaister 340dca3c6d Download headers (lib/headers split). 2011-10-08 17:01:40 +02:00
Themaister 4ae0f3da22 Remove obsolete readme. 2011-10-08 01:27:21 +02:00
Themaister 7d2d1ce49e Change "hashing" value used for netplay. 2011-10-07 22:33:38 +02:00
Themaister fa26f6a10c Cache font rendering. Major overhead reduction. 2011-10-07 17:58:22 +02:00
Themaister db71a5cdba New font rendering code for OpenGL. 2011-10-07 17:43:39 +02:00
Themaister 5058fdcd1a Update docs. 2011-10-07 01:36:52 +02:00
Themaister ef04f199d5 Slight cleanups. 2011-10-07 01:21:54 +02:00
Themaister d26dd9d9e7 Slight cleanups. 2011-10-07 00:54:47 +02:00
Themaister d2bc4096de Some fixes to FFmpeg. Conserves RAM better. 2011-10-07 00:04:52 +02:00
Themaister 08ca1a54d3 Remove useless asserts. 2011-10-06 23:44:21 +02:00
Themaister 869eddfc1a Custom FFmpeg recording sizes. 2011-10-06 23:43:48 +02:00
Themaister fbced43f11 Update threading for Win32. 2011-10-06 22:40:47 +02:00
Themaister c996f816b8 Default font path OSX. 2011-10-06 21:12:11 +02:00
Themaister 33ecedb462 Fixup build for OSX. 2011-10-06 20:49:00 +02:00
Themaister a58676c9e1 Linux needs -lrt for clock_gettime. 2011-10-06 20:34:01 +02:00
Themaister 1ee9722e5f Start working on custom thread implementation.
Avoids SDL dependency for this to improve portability.
2011-10-06 20:31:39 +02:00
Themaister 81250657f0 Change default font color to yellow. 2011-10-06 19:10:01 +02:00
Themaister 7d33cccc8e Default fonts. 2011-10-06 19:06:38 +02:00
Themaister 3db79ab2f0 Nits. 2011-10-05 22:56:52 +02:00
Themaister ea0f6813d2 Avoid assertions. 2011-10-05 22:39:54 +02:00
Themaister f6d4d32c03 Dynamic loading by default, add OSD message for netplay DC. 2011-10-05 21:44:17 +02:00
Themaister 892e82141f Fix FTBFS. 2011-10-05 19:57:32 +02:00
Themaister 65ec7fae9c Is this it? 2011-10-05 02:09:54 +02:00
Themaister a0acb75a19 Might push lagframes better. 2011-10-05 01:05:34 +02:00
Themaister bcae973d82 Savestate auto indexing support. 2011-09-27 15:31:25 +02:00
Themaister d01a4fe61b Only load second pass shaders when render-to-texture is set. 2011-09-26 22:45:28 +02:00
Themaister 8d4620b680 FFmpeg API seems to be too fragmented. Give in and #ifdef shit. :v 2011-09-23 22:57:41 +02:00
Themaister c2cedb1b6f Resizing windows in 1.2 should not trigger some init code :) 2011-09-22 21:50:57 +02:00
Themaister 157dcad87b Fix newline typo. 2011-09-22 13:15:16 +03:00
51 changed files with 1834 additions and 587 deletions

No files matched your search

+18 -10
View File
@@ -2,11 +2,11 @@ include config.mk
TARGET = ssnes tools/ssnes-joyconfig
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o bps.o strl.o screenshot.o
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o bps.o strl.o screenshot.o thread.o audio/hermite.o
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o strl.o
HEADERS = $(wildcard */*.h) $(wildcard *.h)
LIBS = -lm
LIBS = -lm -lpthread
DEFINES = -DHAVE_CONFIG_H
ifneq ($(findstring Darwin,$(OS)),)
@@ -23,11 +23,8 @@ ifneq ($(findstring BSD,$(OS)),)
DYLIB_LIB = -lc
endif
ifeq ($(HAVE_SRC), 1)
LIBS += $(SRC_LIBS)
DEFINES += $(SRC_CFLAGS)
else
OBJ += audio/hermite.o
ifneq ($(findstring Linux,$(OS)),)
LIBS += -lrt
endif
ifeq ($(HAVE_CONFIGFILE), 1)
@@ -111,11 +108,17 @@ ifeq ($(HAVE_CG), 1)
endif
ifeq ($(HAVE_XML), 1)
OBJ += gfx/shader_glsl.o gfx/image.o gfx/snes_state.o sha256.o cheats.o
OBJ += gfx/shader_glsl.o sha256.o cheats.o
LIBS += $(XML_LIBS)
DEFINES += $(XML_CFLAGS)
endif
ifeq ($(HAVE_XML), 1)
OBJ += gfx/snes_state.o gfx/image.o
else ifeq ($(HAVE_CG), 1)
OBJ += gfx/snes_state.o gfx/image.o
endif
ifeq ($(HAVE_DYLIB), 1)
OBJ += gfx/ext.o audio/ext.o
LIBS += $(DYLIB_LIB)
@@ -158,9 +161,14 @@ ifneq ($(V),1)
Q := @
endif
CFLAGS += -Wall -O3 -g -I. -pedantic
OPTIMIZE_FLAG = -O3
ifeq ($(DEBUG), 1)
OPTIMIZE_FLAG = -O0
endif
CFLAGS += -Wall $(OPTIMIZE_FLAG) -g -I. -pedantic
ifneq ($(findstring icc,$(CC)),)
CFLAGS += -std=c99
CFLAGS += -std=c99 -D_GNU_SOURCE
else
CFLAGS += -std=gnu99
endif
+13 -3
View File
@@ -1,6 +1,6 @@
TARGET = ssnes.exe
JTARGET = ssnes-joyconfig.exe
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o bps.o ups.o strl.o screenshot.o audio/hermite.o
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o bps.o ups.o strl.o screenshot.o audio/hermite.o thread.o
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o strl.o
CC = gcc
@@ -12,6 +12,7 @@ HAVE_DSOUND = 1
HAVE_SDL = 1
HAVE_DYLIB = 1
HAVE_NETPLAY = 1
DYNAMIC = 1
ifeq ($(SLIM),)
HAVE_SDL_IMAGE = 1
@@ -117,6 +118,11 @@ endif
ifeq ($(HAVE_FFMPEG), 1)
LIBS += -lavformat -lavcodec -lavutil -lswscale -lws2_32 -lz
DEFINES += -DHAVE_FFMPEG -Iffmpeg
DEFINES += -DHAVE_FFMPEG_ALLOC_CONTEXT3
DEFINES += -DHAVE_FFMPEG_AVCODEC_OPEN2
DEFINES += -DHAVE_FFMPEG_AVIO_OPEN
DEFINES += -DHAVE_FFMPEG_AVFORMAT_WRITE_HEADER
DEFINES += -DHAVE_X264RGB
OBJ += record/ffemu.o
endif
@@ -160,17 +166,21 @@ clean:
rm -f tools/*.o
dist_x86: all
zip -r ssnes-win32-0.8.2.zip $(TARGET) $(JTARGET) ssnes.cfg snes.dll SDL.dll
zip -r ssnes-win32-0.9.zip $(TARGET) $(JTARGET) ssnes.cfg snes.dll SDL.dll
dist_x86_64: all
zip -r ssnes-win64-0.8.2.zip $(TARGET) $(JTARGET) ssnes.cfg snes.dll SDL.dll
zip -r ssnes-win64-0.9.zip $(TARGET) $(JTARGET) ssnes.cfg snes.dll SDL.dll
libs_x86:
wget https://github.com/downloads/Themaister/SSNES/SSNES-win32-libs.zip --no-check-certificate
unzip SSNES-win32-libs.zip
wget https://github.com/downloads/Themaister/SSNES/SSNES-win32-headers.zip --no-check-certificate
unzip SSNES-win32-headers.zip
libs_x86_64:
wget https://github.com/downloads/Themaister/SSNES/SSNES-win64-libs.zip --no-check-certificate
unzip SSNES-win64-libs.zip
wget https://github.com/downloads/Themaister/SSNES/SSNES-win64-headers.zip --no-check-certificate
unzip SSNES-win64-headers.zip
.PHONY: all install uninstall clean dist_x86 dist_x86_64 libs_x86 libs_x86_64
-39
View File
@@ -1,39 +0,0 @@
===================================================
SSNES - Simple Super Nintendo Emulator System
===================================================
SSNES is a simplistic emulator frontend designed for use without any GUI.
It is used from command line, but is designed to play well with eventual GUI frontends,
as the configuration file format is very simple.
This allows for great portability and focus on what matters in a frontend, such as video and audio performance.
Its core libraries for video and input are OpenGL and SDL, with many different audio library supported.
On first use you should get accustomed to how SSNES works.
- SSNES emphasizes simplicity. It has no graphical user interface, and is primarily used through the command line.
It is recommended for experienced users.
It is possible though to create a GUI which can use SSNES.
- SSNES isn't dependant on any emulator core, such as bSNES, SNES9x or ZSNES.
SSNES only talks to the libsnes interface,
which was created initially by bSNES but it has an implementation available for SNES9x as well.
- SSNES is configured through a config file. In the Win32 distribution, there should be a ssnes.cfg included.
This config file serves as a base for your custom configuration file.
It is recommended that you go through the config file and alter any configurations you might use.
This is a good way to learn about the features SSNES have.
It is possible to pick which configuration file you want with the '-c' flag.
E.g.: ssnes.exe -c myconfig.cfg rom.sfc.
- When unsure which command line flags to use, use the command: ssnes.exe --help.
- When something is not working as expected, try using verbose logging first to see what is happening:
E.g.: ssnes.exe -v romfile.sfc
If something is not working, and you want to file a bug report,
please include console output with the -v flag turned on.
- SSNES does not support zipped roms directly. It only supports roms with extentions .sfc and .smc.
However, SSNES can read roms from stdin,
which makes it possible to create wrapper programs that can support every zip format under the sun.
This can also be performed by a GUI frontend.
+10 -15
View File
@@ -21,7 +21,7 @@
#include <asoundlib.h>
#include "general.h"
#define TRY_ALSA(x) if ( x < 0 ) { \
#define TRY_ALSA(x) if (x < 0) { \
goto error; \
}
@@ -52,14 +52,14 @@ static bool find_float_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
static void* __alsa_init(const char* device, unsigned rate, unsigned latency)
{
alsa_t *alsa = calloc(1, sizeof(alsa_t));
if ( alsa == NULL )
if (!alsa)
return NULL;
snd_pcm_hw_params_t *params = NULL;
snd_pcm_sw_params_t *sw_params = NULL;
const char *alsa_dev = "default";
if ( device != NULL )
if (device)
alsa_dev = device;
TRY_ALSA(snd_pcm_open(&alsa->pcm, alsa_dev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK));
@@ -117,7 +117,7 @@ error:
return NULL;
}
static ssize_t __alsa_write(void* data, const void* buf, size_t size)
static ssize_t __alsa_write(void *data, const void *buf, size_t size)
{
alsa_t *alsa = data;
@@ -141,16 +141,16 @@ static ssize_t __alsa_write(void* data, const void* buf, size_t size)
frames = snd_pcm_writei(alsa->pcm, (const char*)buf + written * 2 * (alsa->has_float ? sizeof(float) : sizeof(int16_t)), size - written);
if ( frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE )
if (frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE)
{
if ( snd_pcm_recover(alsa->pcm, frames, 1) < 0 )
if (snd_pcm_recover(alsa->pcm, frames, 1) < 0)
return -1;
return 0;
}
else if ( frames == -EAGAIN && alsa->nonblock )
else if (frames == -EAGAIN && alsa->nonblock)
return 0;
else if ( frames < 0 )
else if (frames < 0)
return -1;
written += frames;
@@ -178,9 +178,9 @@ static bool __alsa_start(void *data)
static void __alsa_free(void *data)
{
alsa_t *alsa = data;
if ( alsa )
if (alsa)
{
if ( alsa->pcm )
if (alsa->pcm)
{
snd_pcm_drop(alsa->pcm);
snd_pcm_close(alsa->pcm);
@@ -200,8 +200,3 @@ const audio_driver_t audio_alsa = {
.ident = "alsa"
};
+6 -11
View File
@@ -20,13 +20,12 @@
#include "hermite.h"
#include <stdlib.h>
#define MAX_CHANS 8
#define CHANNELS 2
struct hermite_resampler
{
float chan_data[MAX_CHANS][4];
float chan_data[CHANNELS][4];
double r_frac;
unsigned chans;
};
static inline float hermite_kernel(float mu1, float a, float b, float c, float d)
@@ -47,21 +46,17 @@ static inline float hermite_kernel(float mu1, float a, float b, float c, float d
return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
}
hermite_resampler_t *hermite_new(unsigned channels)
hermite_resampler_t *hermite_new(void)
{
if (channels > MAX_CHANS)
return NULL;
hermite_resampler_t *re = calloc(1, sizeof(*re));
if (!re)
return NULL;
re->chans = channels;
return re;
}
void hermite_process(hermite_resampler_t *re, struct hermite_data *data)
{
double r_step = 1.0 / data->src_ratio;
double r_step = 1.0 / data->ratio;
size_t processed_out = 0;
size_t processed_in = 0;
@@ -75,7 +70,7 @@ void hermite_process(hermite_resampler_t *re, struct hermite_data *data)
while (re->r_frac <= 1.0 && processed_out < out_frames)
{
re->r_frac += r_step;
for (unsigned i = 0; i < re->chans; i++)
for (unsigned i = 0; i < CHANNELS; i++)
{
float res = hermite_kernel(re->r_frac,
re->chan_data[i][0], re->chan_data[i][1], re->chan_data[i][2], re->chan_data[i][3]);
@@ -88,7 +83,7 @@ void hermite_process(hermite_resampler_t *re, struct hermite_data *data)
{
re->r_frac -= 1.0;
for (unsigned i = 0; i < re->chans; i++)
for (unsigned i = 0; i < CHANNELS; i++)
{
re->chan_data[i][0] = re->chan_data[i][1];
re->chan_data[i][1] = re->chan_data[i][2];
+2 -3
View File
@@ -26,7 +26,7 @@
typedef struct hermite_resampler hermite_resampler_t;
hermite_resampler_t *hermite_new(unsigned channels);
hermite_resampler_t *hermite_new(void);
struct hermite_data
{
@@ -39,8 +39,7 @@ struct hermite_data
size_t input_frames_used;
size_t output_frames_gen;
bool end_of_input; // Just used to clone the SRC API.
double src_ratio;
double ratio;
};
void hermite_process(hermite_resampler_t *re, struct hermite_data *data);
+3 -4
View File
@@ -60,7 +60,7 @@ static void* __al_init(const char* device, unsigned rate, unsigned latency)
{
(void)device;
al_t *al = calloc(1, sizeof(al_t));
if ( al == NULL )
if (!al)
return NULL;
al->handle = alcOpenDevice(NULL);
@@ -133,7 +133,7 @@ static bool al_get_buffer(al_t *al, ALuint *buffer)
};
#endif
for(;;)
for (;;)
{
if (al_unqueue_buffers(al))
break;
@@ -221,7 +221,7 @@ static void __al_free(void *data)
{
alSourceStop(al->source);
alDeleteSources(1, &al->source);
if ( al->buffers )
if (al->buffers)
{
alDeleteBuffers(al->num_buffers, al->buffers);
free(al->buffers);
@@ -244,4 +244,3 @@ const audio_driver_t audio_openal = {
.ident = "openal"
};
+5 -5
View File
@@ -57,12 +57,12 @@ static void err_cb(void *userdata)
static void* __rsd_init(const char* device, unsigned rate, unsigned latency)
{
rsd_t *rsd = calloc(1, sizeof(rsd_t));
if ( rsd == NULL )
if (!rsd)
return NULL;
rsound_t *rd;
if ( rsd_init(&rd) < 0 )
if (rsd_init(&rd) < 0)
{
free(rsd);
return NULL;
@@ -80,14 +80,14 @@ static void* __rsd_init(const char* device, unsigned rate, unsigned latency)
rsd_set_param(rd, RSD_SAMPLERATE, &rate);
rsd_set_param(rd, RSD_LATENCY, &latency);
if ( device != NULL )
if (device)
rsd_set_param(rd, RSD_HOST, (void*)device);
rsd_set_param(rd, RSD_FORMAT, &format);
rsd_set_callback(rd, audio_cb, err_cb, 256, rsd);
if ( rsd_start(rd) < 0 )
if (rsd_start(rd) < 0)
{
free(rsd);
rsd_free(rd);
@@ -161,7 +161,7 @@ static void __rsd_set_nonblock_state(void *data, bool state)
static bool __rsd_start(void *data)
{
rsd_t *rsd = data;
if ( rsd_start(rsd->rd) < 0)
if (rsd_start(rsd->rd) < 0)
return false;
return true;
+83
View File
@@ -0,0 +1,83 @@
#ifndef AUDIO_UTILS_H
#define AUDIO_UTILS_H
#include <stdint.h>
#ifdef __SSE2__
#include <emmintrin.h>
#define audio_convert_s16_to_float audio_convert_s16_to_float_SSE2
#define audio_convert_float_to_s16 audio_convert_float_to_s16_SSE2
#else
#define audio_convert_s16_to_float audio_convert_s16_to_float_C
#define audio_convert_float_to_s16 audio_convert_float_to_s16_C
#endif
static inline void audio_convert_s16_to_float_C(float *out,
const int16_t *in, unsigned samples)
{
for (unsigned i = 0; i < samples; i++)
out[i] = (float)in[i] / 0x8000;
}
static inline void audio_convert_float_to_s16_C(int16_t *out,
const float *in, unsigned samples)
{
for (unsigned i = 0; i < samples; i++)
{
int32_t val = in[i] * 0x8000;
out[i] = (val > 0x7FFF) ? 0x7FFF : (val < -0x8000 ? -0x8000 : (int16_t)val);
}
}
#ifdef __SSE2__
static inline void audio_convert_s16_to_float_SSE2(float *out,
const int16_t *in, unsigned samples)
{
// Not aligned? FML :(
if (((uintptr_t)in & 7) || ((uintptr_t)out & 15))
{
audio_convert_s16_to_float_C(out, in, samples);
return;
}
__m128 factor = _mm_set1_ps(1.0f / 0x7fff);
unsigned i;
for (i = 0; i + 4 <= samples; i += 4, in += 4, out += 4)
{
__m64 input = *(const __m64*)in;
__m128 reg = _mm_cvtpi16_ps(input);
__m128 res = _mm_mul_ps(reg, factor);
*(__m128*)out = res;
}
audio_convert_s16_to_float_C(out, in, samples - i);
}
static inline void audio_convert_float_to_s16_SSE2(int16_t *out,
const float *in, unsigned samples)
{
// Not aligned? FML :(
if (((uintptr_t)in & 7) || ((uintptr_t)out & 15))
{
audio_convert_float_to_s16_C(out, in, samples);
return;
}
__m128 factor = _mm_set1_ps(0x7fff);
unsigned i;
for (i = 0; i + 4 <= samples; i += 4, in += 4, out += 4)
{
__m128 input = *(const __m128*)in;
__m128 res = _mm_mul_ps(input, factor);
__m64 output = _mm_cvtps_pi16(res);
*(__m64*)out = output;
}
audio_convert_float_to_s16_C(out, in, samples - i);
}
#endif
#endif
+22 -24
View File
@@ -16,7 +16,7 @@
*/
#include "autosave.h"
#include "SDL.h"
#include "thread.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
@@ -26,11 +26,11 @@
struct autosave
{
volatile bool quit;
SDL_mutex *lock;
slock_t *lock;
SDL_mutex *cond_lock;
SDL_cond *cond;
SDL_Thread *thread;
slock_t *cond_lock;
scond_t *cond;
sthread_t *thread;
void *buffer;
const void *snes_buffer;
@@ -39,7 +39,7 @@ struct autosave
unsigned interval;
};
static int autosave_thread(void *data)
static void autosave_thread(void *data)
{
autosave_t *save = data;
@@ -77,13 +77,11 @@ static int autosave_thread(void *data)
}
}
SDL_mutexP(save->cond_lock);
slock_lock(save->cond_lock);
if (!save->quit)
SDL_CondWaitTimeout(save->cond, save->cond_lock, save->interval * 1000);
SDL_mutexV(save->cond_lock);
scond_wait_timeout(save->cond, save->cond_lock, save->interval * 1000);
slock_unlock(save->cond_lock);
}
return 0;
}
autosave_t *autosave_new(const char *path, const void *data, size_t size, unsigned interval)
@@ -105,36 +103,36 @@ autosave_t *autosave_new(const char *path, const void *data, size_t size, unsign
}
memcpy(handle->buffer, handle->snes_buffer, handle->bufsize);
handle->lock = SDL_CreateMutex();
handle->cond_lock = SDL_CreateMutex();
handle->cond = SDL_CreateCond();
handle->lock = slock_new();
handle->cond_lock = slock_new();
handle->cond = scond_new();
handle->thread = SDL_CreateThread(autosave_thread, handle);
handle->thread = sthread_create(autosave_thread, handle);
return handle;
}
void autosave_lock(autosave_t *handle)
{
SDL_mutexP(handle->lock);
slock_lock(handle->lock);
}
void autosave_unlock(autosave_t *handle)
{
SDL_mutexV(handle->lock);
slock_unlock(handle->lock);
}
void autosave_free(autosave_t *handle)
{
SDL_mutexP(handle->cond_lock);
slock_lock(handle->cond_lock);
handle->quit = true;
SDL_mutexV(handle->cond_lock);
SDL_CondSignal(handle->cond);
SDL_WaitThread(handle->thread, NULL);
slock_unlock(handle->cond_lock);
scond_signal(handle->cond);
sthread_join(handle->thread);
SDL_DestroyMutex(handle->lock);
SDL_DestroyMutex(handle->cond_lock);
SDL_DestroyCond(handle->cond);
slock_free(handle->lock);
slock_free(handle->cond_lock);
scond_free(handle->cond);
free(handle->buffer);
free(handle);
+314 -72
View File
@@ -24,18 +24,49 @@
#include <errno.h>
#include "strl.h"
#ifndef _WIN32
#include <sys/param.h> // MAXPATHLEN
#else
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#ifndef MAXPATHLEN
#ifdef PATH_MAX
#define MAXPATHLEN PATH_MAX
#else
#define MAXPATHLEN 512
#endif
#endif
#define MAX_INCLUDE_DEPTH 16
struct entry_list
{
bool readonly; // If we got this from an #include, do not allow write.
char *key;
char *value;
struct entry_list *next;
};
struct include_list
{
char *path;
struct include_list *next;
};
struct config_file
{
char *path;
struct entry_list *entries;
struct entry_list *tail;
unsigned include_depth;
struct include_list *includes;
};
static config_file_t *config_file_new_internal(const char *path, unsigned depth);
static char *getaline(FILE *file)
{
char *newline = malloc(9);
@@ -58,13 +89,189 @@ static char *getaline(FILE *file)
return newline;
}
static bool parse_line(struct entry_list *list, char *line)
static char* extract_value(char *line, bool is_value)
{
if (is_value)
{
while (isspace(*line))
line++;
// If we don't have an equal sign here, we've got an invalid string...
if (*line != '=')
return false;
line++;
}
while (isspace(*line))
line++;
// We have a full string. Read until next ".
if (*line == '"')
{
line++;
char *tok = strtok(line, "\"");
if (!tok)
return false;
return strdup(tok);
}
else if (*line == '\0') // Nothing :(
return NULL;
else // We don't have that... Read till next space.
{
char *tok = strtok(line, " \n\t\f\r\v");
if (tok)
return strdup(tok);
else
return NULL;
}
}
static void set_list_readonly(struct entry_list *list)
{
while (list)
{
list->readonly = true;
list = list->next;
}
}
// Move semantics? :)
static void add_child_list(config_file_t *parent, config_file_t *child)
{
if (parent->entries)
{
struct entry_list *head = parent->entries;
while (head->next)
head = head->next;
set_list_readonly(child->entries);
head->next = child->entries;
}
else
{
set_list_readonly(child->entries);
parent->entries = child->entries;
}
child->entries = NULL;
// Rebase tail.
if (parent->entries)
{
struct entry_list *head = parent->entries;
while (head->next)
head = head->next;
parent->tail = head;
}
else
parent->tail = NULL;
}
static void add_include_list(config_file_t *conf, const char *path)
{
struct include_list *head = conf->includes;
struct include_list *node = calloc(1, sizeof(*node));
node->path = strdup(path);
if (head)
{
while (head->next)
head = head->next;
head->next = node;
}
else
conf->includes = node;
}
static void add_sub_conf(config_file_t *conf, char *line)
{
char *path = extract_value(line, false);
if (!path)
return;
add_include_list(conf, path);
char real_path[MAXPATHLEN];
#ifndef _WIN32
if (*path == '/')
{
strlcpy(real_path, path, sizeof(real_path));
}
else if (*path == '~')
{
const char *home = getenv("HOME");
strlcpy(real_path, home ? home : "/", sizeof(real_path));
strlcat(real_path, path + 1, sizeof(real_path));
}
else
{
strlcpy(real_path, conf->path, sizeof(real_path));
char *split = strrchr(real_path, '/');
if (split)
{
split[1] = '\0';
strlcat(real_path, path, sizeof(real_path));
}
else
strlcpy(real_path, path, sizeof(real_path));
}
#else
// Accomodate POSIX systems on Win32.
bool is_full_path = *path == '/';
if (is_full_path)
strlcpy(real_path, path, sizeof(real_path));
else
GetFullPathNameA(path, sizeof(real_path), real_path, NULL);
if (strcmp(path, real_path) != 0)
{
strlcpy(real_path, conf->path, sizeof(real_path));
char *split = strrchr(real_path, '/');
if (!split)
split = strrchr(real_path, '\\');
split[1] = '\0';
strlcat(real_path, path, sizeof(real_path));
}
#endif
config_file_t *sub_conf = config_file_new_internal(real_path, conf->include_depth + 1);
if (!sub_conf)
{
free(path);
return;
}
// Pilfer internal list! :D
add_child_list(conf, sub_conf);
config_file_free(sub_conf);
free(path);
}
static bool parse_line(config_file_t *conf, struct entry_list *list, char *line)
{
// Remove everything after comment.
char *comment = strchr(line, '#');
if (comment)
*comment = '\0';
// Starting line with # and include includes config files. :)
if ((comment == line) && (conf->include_depth < MAX_INCLUDE_DEPTH))
{
comment++;
if (strstr(comment, "include ") == comment)
{
add_sub_conf(conf, comment + strlen("include "));
return false;
}
}
else if (conf->include_depth >= MAX_INCLUDE_DEPTH)
fprintf(stderr, "!!! #include depth exceeded for config! Might be a cycle.\n");
// Skips to first character.
while (isspace(*line))
line++;
@@ -86,66 +293,42 @@ static bool parse_line(struct entry_list *list, char *line)
key[index] = '\0';
list->key = key;
while (isspace(*line))
line++;
// If we don't have an equal sign here, we've got an invalid string...
if (*line != '=')
list->value = extract_value(line, true);
if (!list->value)
{
list->key = NULL;
free(key);
return false;
}
line++;
while (isspace(*line))
line++;
// We have a full string. Read until next ".
if (*line == '"')
{
char *tok = strtok(line + 1, "\"");
if (tok == NULL)
{
list->key = NULL;
free(key);
return false;
}
list->value = strdup(tok);
}
else // We don't have that... Read till next space.
{
char *tok = strtok(line, " \t\f");
if (tok == NULL)
{
list->key = NULL;
free(key);
return false;
}
list->value = strdup(tok);
}
return true;
}
config_file_t *config_file_new(const char *path)
static config_file_t *config_file_new_internal(const char *path, unsigned depth)
{
struct config_file *conf = calloc(1, sizeof(*conf));
if (conf == NULL)
if (!conf)
return NULL;
if (path == NULL)
if (!path)
return conf;
FILE *file = fopen(path, "r");
if (!file)
conf->path = strdup(path);
if (!conf->path)
{
free(conf);
return NULL;
}
struct entry_list *tail = conf->entries;
conf->include_depth = depth;
FILE *file = fopen(path, "r");
if (!file)
{
free(conf->path);
free(conf);
return NULL;
}
while (!feof(file))
{
@@ -154,50 +337,69 @@ config_file_t *config_file_new(const char *path)
if (line)
{
if (parse_line(list, line))
if (parse_line(conf, list, line))
{
if (conf->entries == NULL)
if (conf->entries)
{
conf->entries = list;
tail = list;
conf->tail->next = list;
conf->tail = list;
}
else
{
tail->next = list;
tail = list;
conf->entries = list;
conf->tail = list;
}
}
free(line);
}
if (list != conf->tail)
free(list);
}
fclose(file);
return conf;
}
config_file_t *config_file_new(const char *path)
{
return config_file_new_internal(path, 0);
}
void config_file_free(config_file_t *conf)
{
if (conf != NULL)
if (!conf)
return;
struct entry_list *tmp = conf->entries;
while (tmp)
{
struct entry_list *tmp = conf->entries;
struct entry_list *old = tmp;
while (tmp != NULL)
{
free(tmp->key);
free(tmp->value);
old = tmp;
tmp = tmp->next;
free(old);
}
free(conf);
free(tmp->key);
free(tmp->value);
struct entry_list *hold = tmp;
tmp = tmp->next;
free(hold);
}
struct include_list *inc_tmp = conf->includes;
while (inc_tmp)
{
free(inc_tmp->path);
struct include_list *hold = inc_tmp;
inc_tmp = inc_tmp->next;
free(hold);
}
free(conf->path);
free(conf);
}
bool config_get_double(config_file_t *conf, const char *key, double *in)
{
struct entry_list *list = conf->entries;
while (list != NULL)
while (list)
{
if (strcmp(key, list->key) == 0)
{
@@ -213,7 +415,7 @@ bool config_get_int(config_file_t *conf, const char *key, int *in)
{
struct entry_list *list = conf->entries;
while (list != NULL)
while (list)
{
if (strcmp(key, list->key) == 0)
{
@@ -236,7 +438,7 @@ bool config_get_hex(config_file_t *conf, const char *key, unsigned *in)
{
struct entry_list *list = conf->entries;
while (list != NULL)
while (list)
{
if (strcmp(key, list->key) == 0)
{
@@ -259,7 +461,7 @@ bool config_get_char(config_file_t *conf, const char *key, char *in)
{
struct entry_list *list = conf->entries;
while (list != NULL)
while (list)
{
if (strcmp(key, list->key) == 0)
{
@@ -277,7 +479,7 @@ bool config_get_string(config_file_t *conf, const char *key, char **str)
{
struct entry_list *list = conf->entries;
while (list != NULL)
while (list)
{
if (strcmp(key, list->key) == 0)
{
@@ -293,7 +495,7 @@ bool config_get_array(config_file_t *conf, const char *key, char *buf, size_t si
{
struct entry_list *list = conf->entries;
while (list != NULL)
while (list)
{
if (strcmp(key, list->key) == 0)
{
@@ -309,7 +511,7 @@ bool config_get_bool(config_file_t *conf, const char *key, bool *in)
{
struct entry_list *list = conf->entries;
while (list != NULL)
while (list)
{
if (strcmp(key, list->key) == 0)
{
@@ -335,14 +537,15 @@ void config_set_string(config_file_t *conf, const char *key, const char *val)
{
struct entry_list *list = conf->entries;
struct entry_list *last = list;
while (list != NULL)
while (list)
{
if (strcmp(key, list->key) == 0)
if (!list->readonly && (strcmp(key, list->key) == 0))
{
free(list->value);
list->value = strdup(val);
return;
}
last = list;
list = list->next;
}
@@ -406,11 +609,50 @@ bool config_file_write(config_file_t *conf, const char *path)
void config_file_dump(config_file_t *conf, FILE *file)
{
struct entry_list *list = conf->entries;
while (list != NULL)
struct include_list *includes = conf->includes;
while (includes)
{
fprintf(file, "%s = \"%s\"\n", list->key, list->value);
fprintf(file, "#include \"%s\"\n", includes->path);
includes = includes->next;
}
struct entry_list *list = conf->entries;
while (list)
{
if (!list->readonly)
fprintf(file, "%s = \"%s\"\n", list->key, list->value);
list = list->next;
}
}
void config_file_dump_all(config_file_t *conf, FILE *file)
{
struct include_list *includes = conf->includes;
while (includes)
{
fprintf(file, "#include \"%s\"\n", includes->path);
includes = includes->next;
}
struct entry_list *list = conf->entries;
while (list)
{
fprintf(file, "%s = \"%s\" %s\n", list->key, list->value, list->readonly ? "(included)" : "");
list = list->next;
}
}
bool config_entry_exists(config_file_t *conf, const char *entry)
{
struct entry_list *list = conf->entries;
while (list)
{
if (strcmp(entry, list->key) == 0)
return true;
list = list->next;
}
return false;
}
+15 -5
View File
@@ -31,13 +31,21 @@ typedef struct config_file config_file_t;
// - # are treated as comments. Rest of the line is ignored.
// - Format is: key = value. There can be as many spaces as you like in-between.
// - Value can be wrapped inside "" for multiword strings. (foo = "hai u")
//
// - #include includes a config file in-place.
// Path is relative to where config file was loaded unless an absolute path is chosen.
// Key/value pairs from an #include are read-only, and cannot be modified.
// Loads a config file. Returns NULL if file doesn't exist. NULL path will create an empty config file.
// Loads a config file. Returns NULL if file doesn't exist.
// NULL path will create an empty config file.
config_file_t *config_file_new(const char *path);
// Frees config file.
void config_file_free(config_file_t *conf);
// All extract functions return true when value is valid and exists. Returns false otherwise.
// All extract functions return true when value is valid and exists.
// Returns false otherwise.
bool config_entry_exists(config_file_t *conf, const char *entry);
// Extracts a double from config file.
bool config_get_double(config_file_t *conf, const char *entry, double *in);
@@ -54,7 +62,8 @@ bool config_get_array(config_file_t *conf, const char *entry, char *in, size_t s
// Extracts a boolean from config. Valid boolean true are "true" and "1". Valid false are "false" and "0". Other values will be treated as an error.
bool config_get_bool(config_file_t *conf, const char *entry, bool *in);
// Setters. Similiar to the getters.
// Setters. Similar to the getters. Will not write to entry if the entry
// was obtained from an #include.
void config_set_double(config_file_t *conf, const char *entry, double value);
void config_set_int(config_file_t *conf, const char *entry, int val);
void config_set_char(config_file_t *conf, const char *entry, char val);
@@ -66,7 +75,8 @@ bool config_file_write(config_file_t *conf, const char *path);
// Dump the current config to an already opened file. Does not close the file.
void config_file_dump(config_file_t *conf, FILE *file);
// Also dumps inherited values, useful for logging.
void config_file_dump_all(config_file_t *conf, FILE *file);
#endif
+9 -12
View File
@@ -32,10 +32,6 @@
#include "input/keysym.h"
#ifdef HAVE_SRC
#include <samplerate.h>
#endif
///////////////// Drivers
#define VIDEO_GL 0
#define VIDEO_XVIDEO 11
@@ -113,8 +109,9 @@
////////////////
// Windowed
static const float xscale = 3.0; // Real x res = 296 * xscale
static const float yscale = 3.0; // Real y res = 224 * yscale
static const float xscale = 3.0; // Real x res = aspect * base_size * xscale
static const float yscale = 3.0; // Real y res = base_size * yscale
static const unsigned base_size = 224; // Base size for windowed mode.
// Fullscreen
static const bool fullscreen = false; // To start in Fullscreen or not
@@ -146,7 +143,7 @@ static const unsigned font_size = 48;
static const float message_pos_offset_x = 0.05;
static const float message_pos_offset_y = 0.05;
// Color of the message.
static const uint32_t message_color = 0xffffff; // RGB hex value.
static const uint32_t message_color = 0xffff00; // RGB hex value.
// Render-to-texture before rendering to screen (multi-pass shaders)
static const bool render_to_texture = false;
@@ -188,11 +185,6 @@ static const int out_latency = 64;
// Will sync audio. (recommended)
static const bool audio_sync = true;
// Defines the quality (and cpu reqirements) of samplerate conversion.
#ifdef HAVE_SRC
#define SAMPLERATE_QUALITY SRC_LINEAR
#endif
// Enables use of rewind. This will incur some memory footprint depending on the save state buffer.
// This rewind only works when using bSNES core atm.
static const bool rewind_enable = false;
@@ -216,6 +208,10 @@ static const bool netplay_client_swap_input = true;
// This could potentially lead to buggy games.
static const bool block_sram_overwrite = false;
// When saving savestates, state index is automatically incremented before saving.
// When the ROM is loaded, state index will be set to the highest existing value.
static const bool savestate_auto_index = false;
////////////////////
// Keybinds, Joypad
@@ -256,6 +252,7 @@ static const struct snes_keybind snes_keybinds_1[] = {
{ SSNES_REWIND, SK_r, NO_BTN, AXIS_NONE },
{ SSNES_MOVIE_RECORD_TOGGLE, SK_o, NO_BTN, AXIS_NONE },
{ SSNES_PAUSE_TOGGLE, SK_p, NO_BTN, AXIS_NONE },
{ SSNES_FRAMEADVANCE, SK_k, NO_BTN, AXIS_NONE },
{ SSNES_RESET, SK_h, NO_BTN, AXIS_NONE },
{ SSNES_SHADER_NEXT, SK_m, NO_BTN, AXIS_NONE },
{ SSNES_SHADER_PREV, SK_n, NO_BTN, AXIS_NONE },
+3 -3
View File
@@ -110,10 +110,10 @@ static const bool _ffmpeg_supp = true;
static const bool _ffmpeg_supp = false;
#endif
#ifdef HAVE_SRC
static const bool _src_supp = true;
#ifdef HAVE_X264RGB
static const bool _x264rgb_supp = true;
#else
static const bool _src_supp = false;
static const bool _x264rgb_supp = false;
#endif
#ifdef HAVE_CONFIGFILE
+1 -1
View File
@@ -1,6 +1,6 @@
.\" ssnes-joyconfig.1:
.TH "SSNES-JOYCONFIG" "1" "January 2011" "SSNES-JOYCONFIG" "System Manager's Manual: ssnes-joyconfig"
.TH "SSNES-JOYCONFIG" "1" "October 2011" "SSNES-JOYCONFIG" "System Manager's Manual: ssnes-joyconfig"
.SH NAME
+9 -1
View File
@@ -1,6 +1,6 @@
.\" ssnes.1:
.TH "SSNES" "1" "January 2011" "SSNES" "System Manager's Manual: ssnes"
.TH "SSNES" "1" "October 2011" "SSNES" "System Manager's Manual: ssnes"
.SH NAME
@@ -27,6 +27,9 @@ If no rom file path is defined on the command line, \fBssnes\fR will try to load
\fB--help, -h\fR
Prints help text.
\fB--features\fR
Prints available features compiled into SSNES, then exits.
.TP
\fB--save PATH, -s PATH\fR
Overrides the path used for save ram (*.srm).
@@ -115,6 +118,11 @@ Connects a four-way multitap into port 2 of the SNES. This allows for up to 5 pl
Activates video recording of gameplay into PATH. Using .mkv extension is recommended.
Codecs used are FFV1/FLAC, suitable for processing the material further.
.TP
\fB--size WIDTHxHEIGHT\fR
Allows specifying the exact output width and height of FFmpeg recording. This option will override any configuration settings.
The video input is scaled with point filtering before being encoded at the correct size.
.TP
\fB--bsvplay PATH, -P PATH\fR
Play back a movie recorded in the .bsv format (bSNES). Cart ROM and movie file need to correspond.
+19 -22
View File
@@ -237,7 +237,7 @@ void init_audio(void)
g_extern.audio_data.nonblock_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING;
driver.audio_data = driver.audio->init(strlen(g_settings.audio.device) ? g_settings.audio.device : NULL, g_settings.audio.out_rate, g_settings.audio.latency);
if ( driver.audio_data == NULL )
if (!driver.audio_data)
g_extern.audio_active = false;
if (g_extern.audio_active && driver.audio->use_float && driver.audio->use_float(driver.audio_data))
@@ -251,26 +251,28 @@ void init_audio(void)
else
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
#ifdef HAVE_SRC
int err;
g_extern.audio_data.source = src_new(g_settings.audio.src_quality, 2, &err);
g_extern.audio_data.source = hermite_new();
if (!g_extern.audio_data.source)
g_extern.audio_active = false;
#else
g_extern.audio_data.source = hermite_new(2);
if (!g_extern.audio_data.source)
g_extern.audio_active = false;
#endif
size_t max_bufsamples = g_extern.audio_data.block_chunk_size > g_extern.audio_data.nonblock_chunk_size ?
g_extern.audio_data.block_chunk_size : g_extern.audio_data.nonblock_chunk_size;
assert(g_settings.audio.out_rate < g_settings.audio.in_rate * AUDIO_MAX_RATIO);
max_bufsamples *= 2; // Accomodate rewind since at some point we might have two full buffers.
assert((g_extern.audio_data.data = malloc(max_bufsamples * sizeof(float))));
g_extern.audio_data.data_ptr = 0;
assert(g_settings.audio.out_rate < g_settings.audio.in_rate * AUDIO_MAX_RATIO);
assert((g_extern.audio_data.outsamples = malloc(max_bufsamples * sizeof(float) * AUDIO_MAX_RATIO)));
assert((g_extern.audio_data.conv_outsamples = malloc(max_bufsamples * sizeof(int16_t) * AUDIO_MAX_RATIO)));
// Needs to be able to hold full content of a full max_bufsamples in addition to its own.
assert((g_extern.audio_data.rewind_buf = malloc(max_bufsamples * sizeof(int16_t))));
g_extern.audio_data.rewind_size = max_bufsamples;
g_extern.audio_data.src_ratio =
(double)g_settings.audio.out_rate / g_settings.audio.in_rate;
init_dsp_plugin();
}
@@ -282,21 +284,16 @@ void uninit_audio(void)
return;
}
if ( driver.audio_data && driver.audio )
if (driver.audio_data && driver.audio)
driver.audio->free(driver.audio_data);
if ( g_extern.audio_data.source )
{
#ifdef HAVE_SRC
src_delete(g_extern.audio_data.source);
#else
if (g_extern.audio_data.source)
hermite_free(g_extern.audio_data.source);
#endif
}
free(g_extern.audio_data.data); g_extern.audio_data.data = NULL;
free(g_extern.audio_data.outsamples); g_extern.audio_data.outsamples = NULL;
free(g_extern.audio_data.conv_outsamples); g_extern.audio_data.conv_outsamples = NULL;
free(g_extern.audio_data.rewind_buf); g_extern.audio_data.rewind_buf = NULL;
deinit_dsp_plugin();
}
@@ -435,13 +432,13 @@ void init_video_input(void)
{
if (g_settings.video.force_aspect)
{
width = 224 * g_settings.video.xscale * g_settings.video.aspect_ratio;
height = 224 * g_settings.video.yscale;
width = roundf(g_settings.video.base_size * g_settings.video.xscale * g_settings.video.aspect_ratio);
height = roundf(g_settings.video.base_size * g_settings.video.yscale);
}
else
{
width = 256 * g_settings.video.xscale;
height = 224 * g_settings.video.yscale;
width = roundf(8.0f / 7.0f * g_settings.video.base_size * g_settings.video.xscale); // Assume 8:7 aspect.
height = roundf(g_settings.video.base_size * g_settings.video.yscale);
}
}
+1
View File
@@ -41,6 +41,7 @@ enum
SSNES_REWIND,
SSNES_MOVIE_RECORD_TOGGLE,
SSNES_PAUSE_TOGGLE,
SSNES_FRAMEADVANCE,
SSNES_RESET,
SSNES_SHADER_NEXT,
SSNES_SHADER_PREV,
+18 -12
View File
@@ -17,6 +17,7 @@
#include "dynamic.h"
#include "general.h"
#include "strl.h"
#include <string.h>
#include <assert.h>
@@ -38,16 +39,15 @@
#define SYM(type, x) do { \
p##x = (type)DLSYM(lib_handle, x); \
if (p##x == NULL) { SSNES_ERR("Failed to load symbol: \"%s\"\n", #x); exit(1); } \
} while(0)
} while (0)
#define OPT_SYM(type, x) do { \
p##x = (type)DLSYM(lib_handle, x); \
} while(0)
} while (0)
static dylib_t lib_handle = NULL;
#endif
void (*psnes_init)(void);
void (*psnes_set_video_refresh)(snes_video_refresh_t);
@@ -112,7 +112,7 @@ static void load_dynamic(void)
SYM(void (*)(snes_audio_sample_t), snes_set_audio_sample);
SYM(void (*)(snes_input_poll_t), snes_set_input_poll);
SYM(void (*)(snes_input_state_t), snes_set_input_state);
OPT_SYM(const char *(*)(void), snes_library_id);
SYM(const char *(*)(void), snes_library_id);
SYM(unsigned (*)(void), snes_library_revision_minor);
SYM(unsigned (*)(void), snes_library_revision_major);
SYM(void (*)(void), snes_cheat_reset);
@@ -144,7 +144,7 @@ static void load_dynamic(void)
#define SSYM(x) do { \
p##x = x; \
} while(0)
} while (0)
#ifndef HAVE_DYNAMIC
static void set_statics(void)
@@ -156,6 +156,7 @@ static void set_statics(void)
SSYM(snes_set_input_state);
SSYM(snes_library_revision_minor);
SSYM(snes_library_revision_major);
SSYM(snes_library_id);
SSYM(snes_cheat_reset);
SSYM(snes_cheat_set);
SSYM(snes_reset);
@@ -204,15 +205,20 @@ void init_dlsym(void)
}
#endif
if (strlen(g_settings.libsnes) > 0)
load_dynamic();
else
if (!*g_settings.libsnes)
{
SSNES_ERR("This binary is built to use runtime dynamic binding of libsnes. Set libsnes_path in config to load a libsnes library dynamically.\n");
exit(1);
}
#if defined(_WIN32)
strlcpy(g_settings.libsnes, "snes.dll", sizeof(g_settings.libsnes));
#elif defined(__APPLE__)
strlcpy(g_settings.libsnes, "libsnes.dylib", sizeof(g_settings.libsnes));
#else
set_statics();
strlcpy(g_settings.libsnes, "libsnes.so", sizeof(g_settings.libsnes));
#endif
}
load_dynamic();
#else
set_statics();
#endif
}
-19
View File
@@ -42,18 +42,12 @@ fifo_buffer_t* fifo_new(size_t size)
void fifo_free(fifo_buffer_t* buffer)
{
assert(buffer);
assert(buffer->buffer);
free(buffer->buffer);
free(buffer);
}
size_t fifo_read_avail(fifo_buffer_t* buffer)
{
assert(buffer);
assert(buffer->buffer);
size_t first = buffer->first;
size_t end = buffer->end;
if (end < first)
@@ -63,9 +57,6 @@ size_t fifo_read_avail(fifo_buffer_t* buffer)
size_t fifo_write_avail(fifo_buffer_t* buffer)
{
assert(buffer);
assert(buffer->buffer);
size_t first = buffer->first;
size_t end = buffer->end;
if (end < first)
@@ -76,11 +67,6 @@ size_t fifo_write_avail(fifo_buffer_t* buffer)
void fifo_write(fifo_buffer_t* buffer, const void* in_buf, size_t size)
{
assert(buffer);
assert(buffer->buffer);
assert(in_buf);
assert(fifo_write_avail(buffer) >= size);
size_t first_write = size;
size_t rest_write = 0;
if (buffer->end + size > buffer->bufsize)
@@ -99,11 +85,6 @@ void fifo_write(fifo_buffer_t* buffer, const void* in_buf, size_t size)
void fifo_read(fifo_buffer_t* buffer, void* in_buf, size_t size)
{
assert(buffer);
assert(buffer->buffer);
assert(in_buf);
assert(fifo_read_avail(buffer) >= size);
size_t first_read = size;
size_t rest_read = 0;
if (buffer->first + size > buffer->bufsize)
+7 -3
View File
@@ -251,7 +251,7 @@ static ssize_t read_rom_file(FILE* file, void** buf)
return -1;
}
for(;;)
for (;;)
{
size_t ret = fread(rom_buf + buf_ptr, 1, buf_size - buf_ptr, stdin);
buf_ptr += ret;
@@ -818,8 +818,12 @@ char** dir_list_new(const char *dir, const char *ext)
goto error;
if (strlcat(utf8_buf, "/*", sizeof(utf8_buf)) >= sizeof(utf8_buf))
goto error;
if (strlcat(utf8_buf, ext, sizeof(utf8_buf)) >= sizeof(utf8_buf))
goto error;
if (ext)
{
if (strlcat(utf8_buf, ext, sizeof(utf8_buf)) >= sizeof(utf8_buf))
goto error;
}
if (MultiByteToWideChar(CP_UTF8, 0, utf8_buf, -1, wchar_buf, MAXPATHLEN) == 0)
goto error;
+4
View File
@@ -26,6 +26,8 @@
#include <sys/types.h>
#include "general.h"
// Generic file, path and directory handling.
ssize_t read_file(const char *path, void **buf);
bool load_state(const char *path);
@@ -37,6 +39,8 @@ void save_ram_file(const char *path, int type);
bool init_rom_file(enum ssnes_game_type type);
// Returns a NULL-terminated list of files in a directory with full paths.
// If ext is NULL, any file will be picked.
// If non-NULL, only files with extension ext are added.
char** dir_list_new(const char *dir, const char *ext);
void dir_list_free(char **dir_list);
+39 -13
View File
@@ -20,8 +20,9 @@
#define __SSNES_GENERAL_H
#include <stdbool.h>
#include "driver.h"
#include <stdio.h>
#include <time.h>
#include "driver.h"
#include "record/ffemu.h"
#include "message.h"
#include "rewind.h"
@@ -36,14 +37,13 @@
#include "config.h"
#endif
#ifdef HAVE_SRC
#include <samplerate.h>
#else
#include "audio/hermite.h"
#endif
#ifndef _WIN32
#include <sys/param.h> // MAXPATHLEN
#else
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#ifndef MAXPATHLEN
@@ -74,6 +74,7 @@ struct settings
char driver[32];
float xscale;
float yscale;
unsigned base_size;
bool fullscreen;
unsigned fullscreen_x;
unsigned fullscreen_y;
@@ -121,7 +122,6 @@ struct settings
char device[MAXPATHLEN];
unsigned latency;
bool sync;
int src_quality;
char dsp_plugin[MAXPATHLEN];
char external_driver[MAXPATHLEN];
@@ -150,6 +150,7 @@ struct settings
unsigned autosave_interval;
bool block_sram_overwrite;
bool savestate_auto_index;
};
enum ssnes_game_type
@@ -209,11 +210,7 @@ struct global
struct
{
#ifdef HAVE_SRC
SRC_STATE *source;
#else
hermite_resampler_t *source;
#endif
float *data;
size_t data_ptr;
@@ -221,11 +218,17 @@ struct global
size_t nonblock_chunk_size;
size_t block_chunk_size;
double src_ratio;
bool use_float;
float *outsamples;
int16_t *conv_outsamples;
int16_t *rewind_buf;
size_t rewind_ptr;
size_t rewind_size;
dylib_t dsp_lib;
const ssnes_dsp_plugin_t *dsp_plugin;
void *dsp_handle;
@@ -260,6 +263,7 @@ struct global
// Pausing support
bool is_paused;
bool is_oneshot;
// Autosave support.
autosave_t *autosave[2];
@@ -277,8 +281,17 @@ struct global
ffemu_t *rec;
char record_path[MAXPATHLEN];
bool recording;
unsigned record_width;
unsigned record_height;
#endif
struct
{
const uint16_t *data;
unsigned width;
unsigned height;
} frame_cache;
char title_buf[64];
struct
@@ -306,17 +319,17 @@ extern struct global g_extern;
if (g_extern.verbose) \
fprintf(stderr, "SSNES: " __VA_ARGS__); \
fflush(stderr); \
} while(0)
} while (0)
#define SSNES_ERR(...) do { \
fprintf(stderr, "SSNES [ERROR] :: " __VA_ARGS__); \
fflush(stderr); \
} while(0)
} while (0)
#define SSNES_WARN(...) do { \
fprintf(stderr, "SSNES [WARN] :: " __VA_ARGS__); \
fflush(stderr); \
} while(0)
} while (0)
static inline uint32_t next_pow2(uint32_t v)
{
@@ -342,6 +355,19 @@ static inline uint8_t is_little_endian(void)
return u.y[0];
}
static inline void ssnes_sleep(unsigned usec)
{
#ifdef _WIN32
Sleep(10);
#else
struct timespec tv = {
.tv_sec = 0,
.tv_nsec = 10000000
};
nanosleep(&tv, NULL);
#endif
}
#endif
+22 -1
View File
@@ -25,6 +25,11 @@
#include "dynamic.h"
#include "general.h"
#include "sdlwrap.h"
#include "gfx_common.h"
#ifdef HAVE_FREETYPE
#include "fonts.h"
#endif
static bool g_input_dead = true;
static bool g_video_dead = true;
@@ -240,6 +245,21 @@ static bool setup_video(ext_t *ext, const video_info_t *video, const input_drive
font_color_g = font_color_g > 255 ? 255 : (font_color_g < 0 ? 0 : font_color_g);
font_color_b = font_color_b > 255 ? 255 : (font_color_b < 0 ? 0 : font_color_b);
const char *font = NULL;
#ifdef HAVE_FREETYPE
if (*g_settings.video.font_path)
font = g_settings.video.font_path;
else
font = font_renderer_get_default_font();
#else
font = *g_settings.video.font_path ?
g_settings.video.font_path : NULL;
#endif
char title_buf[128];
gfx_window_title_reset();
gfx_window_title(title_buf, sizeof(title_buf));
ssnes_video_info_t info = {
.width = video->width,
.height = video->height,
@@ -252,9 +272,10 @@ static bool setup_video(ext_t *ext, const video_info_t *video, const input_drive
.color_format = video->rgb32 ? SSNES_COLOR_FORMAT_ARGB8888 : SSNES_COLOR_FORMAT_XRGB1555,
.xml_shader = xml_shader,
.cg_shader = cg_shader,
.ttf_font = *g_settings.video.font_path ? g_settings.video.font_path : NULL,
.ttf_font = font,
.ttf_font_size = g_settings.video.font_size,
.ttf_font_color = (font_color_r << 16) | (font_color_g << 8) | (font_color_b << 0),
.title_hint = title_buf,
};
const ssnes_input_driver_t *input_driver = NULL;
+6 -3
View File
@@ -108,7 +108,10 @@ typedef struct ssnes_video_info
// Can be disregarded.
const char *ttf_font;
unsigned ttf_font_size;
unsigned ttf_font_color; // Font color, in format RGB888.
unsigned ttf_font_color; // Font color, in format ARGB8888. Alpha should be disregarded.
// A title that should be displayed in the title bar of the window.
const char *title_hint;
} ssnes_video_info_t;
// Some convenience macros.
@@ -119,7 +122,7 @@ typedef struct ssnes_video_info
// I hope no joypad will ever have this many buttons or axes ... ;)
// If joykey is this value, do not check that button.
#define SSNES_NO_AXIS ((unsigned short)0xFFFFU)
#define SSNES_NO_AXIS (0xFFFFFFFFU)
#define SSNES_NO_BTN ((unsigned short)0xFFFFU)
// Masks to test on joykey which hat direction is to be tested for.
@@ -236,7 +239,7 @@ typedef struct ssnes_video_driver
const char *ident;
// Needs to be defined to SSNES_GRAPHICS_API_VERSION.
// This is used to detect API mismatches.
// This is used to detect API/ABI mismatches.
int api_version;
} ssnes_video_driver_t;
+39 -5
View File
@@ -16,10 +16,10 @@
*/
#include "fonts.h"
#include "file.h"
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <assert.h>
#include <ft2build.h>
#include FT_FREETYPE_H
@@ -79,12 +79,16 @@ void font_renderer_msg(font_renderer_t *handle, const char *msg, struct font_out
if (!err)
{
struct font_output *tmp = calloc(1, sizeof(*tmp));
assert(tmp);
//fprintf(stderr, "Char: %c, off_x: %d, off_y: %d, bmp_left: %d, bmp_top: %d\n", msg[i], off_x, off_y, slot->bitmap_left, slot->bitmap_top);
if (!tmp)
break;
tmp->output = malloc(slot->bitmap.pitch * slot->bitmap.rows);
assert(tmp->output);
if (!tmp->output)
{
free(tmp);
break;
}
memcpy(tmp->output, slot->bitmap.buffer, slot->bitmap.pitch * slot->bitmap.rows);
tmp->width = slot->bitmap.width;
@@ -128,3 +132,33 @@ void font_renderer_free(font_renderer_t *handle)
if (handle->lib)
FT_Done_FreeType(handle->lib);
}
// Not the cleanest way to do things for sure, but should hopefully work ... :)
#if defined(_WIN32)
static const char *font_paths[] = {
"C:\\Windows\\Fonts\\consola.ttf",
"C:\\Windows\\Fonts\\verdana.ttf",
#elif defined(__APPLE__)
static const char *font_paths[] = {
"/Library/Fonts/Microsoft/Candara.ttf",
#else
static const char *font_paths[] = {
"/usr/share/fonts/TTF/DejaVuSansMono.ttf",
"/usr/share/fonts/TTF/DejaVuSans.ttf",
#endif
"osd-font.ttf", // Magic font to search for, useful for distribution.
};
// Highly OS/platform dependent.
const char *font_renderer_get_default_font(void)
{
for (unsigned i = 0; i < sizeof(font_paths) / sizeof(font_paths[0]); i++)
{
if (path_file_exists(font_paths[i]))
return font_paths[i];
}
return NULL;
}
+7 -3
View File
@@ -25,10 +25,10 @@ typedef struct font_renderer font_renderer_t;
struct font_output
{
uint8_t *output;
uint8_t *output; // 8-bit intensity.
unsigned width, height, pitch;
int off_x, off_y;
struct font_output *next;
struct font_output *next; // linked list.
};
struct font_output_list
@@ -37,8 +37,12 @@ struct font_output_list
};
font_renderer_t *font_renderer_new(const char *font_path, unsigned font_size);
void font_renderer_msg(font_renderer_t *handle, const char *msg, struct font_output_list *output);
void font_renderer_msg(font_renderer_t *handle, const char *msg,
struct font_output_list *output);
void font_renderer_free_output(struct font_output_list *list);
void font_renderer_free(font_renderer_t *handle);
const char *font_renderer_get_default_font(void);
#endif
+1 -1
View File
@@ -84,7 +84,7 @@ void gfx_set_dwm(void)
dwmlib = dylib_load("dwmapi.dll");
if (!dwmlib)
{
SSNES_LOG("Did not find dwmapi.dll");
SSNES_LOG("Did not find dwmapi.dll.\n");
return;
}
atexit(gfx_dwm_shutdown);
+179 -38
View File
@@ -34,6 +34,7 @@
#include "gl_common.h"
#include "gfx_common.h"
#include "sdlwrap.h"
#include "strl.h"
#define NO_SDL_GLEXT
#include "SDL.h"
@@ -172,6 +173,10 @@ typedef struct gl
#ifdef HAVE_FREETYPE
font_renderer_t *font;
GLuint font_tex;
int font_tex_w, font_tex_h;
void *font_tex_empty_buf;
char font_last_msg[256];
int font_last_width, font_last_height;
#endif
} gl_t;
@@ -332,9 +337,13 @@ static void gl_shader_scale(unsigned index, struct gl_fbo_scale *scale)
static inline void gl_init_font(gl_t *gl, const char *font_path, unsigned font_size)
{
#ifdef HAVE_FREETYPE
if (strlen(font_path) > 0)
const char *path = font_path;
if (!*path)
path = font_renderer_get_default_font();
if (path)
{
gl->font = font_renderer_new(font_path, font_size);
gl->font = font_renderer_new(path, font_size);
if (gl->font)
{
glGenTextures(1, &gl->font_tex);
@@ -348,6 +357,12 @@ static inline void gl_init_font(gl_t *gl, const char *font_path, unsigned font_s
else
SSNES_WARN("Couldn't init font renderer with font \"%s\"...\n", font_path);
}
else
SSNES_LOG("Did not find default font.\n");
#else
(void)gl;
(void)font_path;
(void)font_size;
#endif
}
@@ -523,7 +538,7 @@ static void gl_init_fbo(gl_t *gl, unsigned width, unsigned height)
error:
glDeleteTextures(gl->fbo_pass, gl->fbo_texture);
pglDeleteFramebuffers(gl->fbo_pass, gl->fbo);
SSNES_WARN("Failed to set up FBO. Two-pass shading will not work.\n");
SSNES_ERR("Failed to set up frame buffer objects. Multi-pass shading will not work.\n");
#else
(void)gl;
(void)width;
@@ -538,7 +553,12 @@ static inline void gl_deinit_font(gl_t *gl)
{
font_renderer_free(gl->font);
glDeleteTextures(1, &gl->font_tex);
if (gl->font_tex_empty_buf)
free(gl->font_tex_empty_buf);
}
#else
(void)gl;
#endif
}
////////////
@@ -605,67 +625,192 @@ static void set_viewport(gl_t *gl, unsigned width, unsigned height, bool force_f
//SSNES_LOG("Setting viewport @ %ux%u\n", width, height);
}
#ifdef HAVE_FREETYPE
// Somewhat overwhelming code just to render some damn fonts.
// We aim to use NPOT textures for compatibility with old and shitty cards.
// Also, we want to avoid reallocating a texture for each glyph (performance dips), so we
// contruct the whole texture using one call, and copy straight to it with
// glTexSubImage.
struct font_rect
{
int x, y;
int width, height;
int pot_width, pot_height;
};
static void calculate_msg_geometry(const struct font_output *head, struct font_rect *rect)
{
int x_min = head->off_x;
int x_max = head->off_x + head->width;
int y_min = head->off_y;
int y_max = head->off_y + head->height;
while ((head = head->next))
{
int left = head->off_x;
int right = head->off_x + head->width;
int bottom = head->off_y;
int top = head->off_y + head->height;
if (left < x_min)
x_min = left;
if (right > x_max)
x_max = right;
if (bottom < y_min)
y_min = bottom;
if (top > y_max)
y_max = top;
}
rect->x = x_min;
rect->y = y_min;
rect->width = x_max - x_min;
rect->height = y_max - y_min;
}
static void adjust_power_of_two(gl_t *gl, struct font_rect *geom)
{
// Some systems really hate NPOT textures.
geom->pot_width = next_pow2(geom->width);
geom->pot_height = next_pow2(geom->height);
if ((geom->pot_width > gl->font_tex_w) || (geom->pot_height > gl->font_tex_h))
{
gl->font_tex_empty_buf = realloc(gl->font_tex_empty_buf, geom->pot_width * geom->pot_height);
memset(gl->font_tex_empty_buf, 0, geom->pot_width * geom->pot_height);
glPixelStorei(GL_UNPACK_ALIGNMENT, 8);
glPixelStorei(GL_UNPACK_ROW_LENGTH, geom->pot_width);
glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY8, geom->pot_width, geom->pot_height,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, gl->font_tex_empty_buf);
gl->font_tex_w = geom->pot_width;
gl->font_tex_h = geom->pot_height;
}
}
// Old style "blitting", so we can render all the fonts in one go.
// TODO: Is it possible that fonts could overlap if we blit without alpha blending?
static void blit_fonts(gl_t *gl, const struct font_output *head, const struct font_rect *geom)
{
// Clear out earlier fonts.
glPixelStorei(GL_UNPACK_ALIGNMENT, 8);
glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->font_tex_w);
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0, gl->font_tex_w, gl->font_tex_h,
GL_LUMINANCE, GL_UNSIGNED_BYTE, gl->font_tex_empty_buf);
while (head)
{
// head has top-left oriented coords.
int x = head->off_x - geom->x;
int y = head->off_y - geom->y;
y = gl->font_tex_h - head->height - y - 1;
glPixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(head->pitch));
glPixelStorei(GL_UNPACK_ROW_LENGTH, head->pitch);
glTexSubImage2D(GL_TEXTURE_2D,
0, x, y, head->width, head->height,
GL_LUMINANCE, GL_UNSIGNED_BYTE, head->output);
head = head->next;
}
}
static void calculate_font_coords(gl_t *gl,
GLfloat font_vertex[8], GLfloat font_tex_coords[8])
{
GLfloat lx = g_settings.video.msg_pos_x;
GLfloat hx = (GLfloat)gl->font_last_width / gl->vp_width + lx;
GLfloat ly = g_settings.video.msg_pos_y;
GLfloat hy = (GLfloat)gl->font_last_height / gl->vp_height + ly;
font_vertex[0] = lx;
font_vertex[1] = ly;
font_vertex[2] = lx;
font_vertex[3] = hy;
font_vertex[4] = hx;
font_vertex[5] = hy;
font_vertex[6] = hx;
font_vertex[7] = ly;
lx = 0.0f;
hx = (GLfloat)gl->font_last_width / gl->font_tex_w;
ly = 1.0f - (GLfloat)gl->font_last_height / gl->font_tex_h;
hy = 1.0f;
font_tex_coords[0] = lx;
font_tex_coords[1] = hy;
font_tex_coords[2] = lx;
font_tex_coords[3] = ly;
font_tex_coords[4] = hx;
font_tex_coords[5] = ly;
font_tex_coords[6] = hx;
font_tex_coords[7] = hy;
}
static void gl_render_msg(gl_t *gl, const char *msg)
{
#ifdef HAVE_FREETYPE
if (!gl->font)
return;
GLfloat font_vertex[8];
GLfloat font_tex_coords[8];
// Deactivate custom shaders. Enable the font texture.
gl_shader_use(0);
set_viewport(gl, gl->win_width, gl->win_height, false);
glBindTexture(GL_TEXTURE_2D, gl->font_tex);
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), font_vertex);
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), tex_coords); // Use the static one (uses whole texture).
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), font_tex_coords);
// Need blending.
// Using fixed function pipeline here since we cannot guarantee presence of shaders (would be kinda overkill anyways).
glEnable(GL_BLEND);
glColor4f(g_settings.video.msg_color_r, g_settings.video.msg_color_g, g_settings.video.msg_color_b, 1);
glColor4f(g_settings.video.msg_color_r, g_settings.video.msg_color_g,
g_settings.video.msg_color_b, 1);
struct font_output_list out;
font_renderer_msg(gl->font, msg, &out);
struct font_output *head = out.head;
while (head != NULL)
// If we get the same message, there's obviously no need to render fonts again ...
if (strcmp(gl->font_last_msg, msg) != 0)
{
GLfloat lx = (GLfloat)head->off_x / gl->vp_width + g_settings.video.msg_pos_x;
GLfloat hx = (GLfloat)(head->off_x + head->width) / gl->vp_width + g_settings.video.msg_pos_x;
GLfloat ly = (GLfloat)head->off_y / gl->vp_height + g_settings.video.msg_pos_y;
GLfloat hy = (GLfloat)(head->off_y + head->height) / gl->vp_height + g_settings.video.msg_pos_y;
font_renderer_msg(gl->font, msg, &out);
struct font_output *head = out.head;
font_vertex[0] = lx;
font_vertex[1] = ly;
font_vertex[2] = lx;
font_vertex[3] = hy;
font_vertex[4] = hx;
font_vertex[5] = hy;
font_vertex[6] = hx;
font_vertex[7] = ly;
struct font_rect geom;
calculate_msg_geometry(head, &geom);
adjust_power_of_two(gl, &geom);
blit_fonts(gl, head, &geom);
glPixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(head->pitch));
glPixelStorei(GL_UNPACK_ROW_LENGTH, head->pitch);
glTexImage2D(GL_TEXTURE_2D,
0, GL_INTENSITY8, head->width, head->height, 0, GL_LUMINANCE,
GL_UNSIGNED_BYTE, head->output);
font_renderer_free_output(&out);
strlcpy(gl->font_last_msg, msg, sizeof(gl->font_last_msg));
head = head->next;
glDrawArrays(GL_QUADS, 0, 4);
gl->font_last_width = geom.width;
gl->font_last_height = geom.height;
}
font_renderer_free_output(&out);
glColor4f(1, 1, 1, 1);
calculate_font_coords(gl, font_vertex, font_tex_coords);
glDrawArrays(GL_QUADS, 0, 4);
// Go back to old rendering path.
glColor4f(1, 1, 1, 1);
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords);
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes_flipped);
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
glDisable(GL_BLEND);
#endif
}
#else
static void gl_render_msg(gl_t *gl, const char *msg)
{
(void)gl;
(void)msg;
}
#endif
static inline void set_lut_texture_coords(const GLfloat *coords)
{
@@ -697,19 +842,14 @@ static void check_window(gl_t *gl)
gl->frame_count);
if (quit)
{
gl->quitting = true;
}
else if (resize)
{
gl->should_resize = true;
}
}
static bool gl_frame(void *data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
{
gl_t *gl = data;
check_window(gl);
gl_shader_use(1);
gl->frame_count++;
@@ -784,7 +924,7 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
{
gl->should_resize = false;
sdlwrap_set_video_mode(gl->win_width, gl->win_height, 0, g_settings.video.fullscreen);
sdlwrap_set_resize(gl->win_width, gl->win_height);
#ifdef HAVE_FBO
if (!gl->render_to_tex)
@@ -1191,6 +1331,7 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
static bool gl_alive(void *data)
{
gl_t *gl = data;
check_window(gl);
return !gl->quitting;
}
+9 -3
View File
@@ -80,9 +80,13 @@ static void sdl_gfx_free(void *data)
static void sdl_init_font(sdl_video_t *vid, const char *font_path, unsigned font_size)
{
#ifdef HAVE_FREETYPE
if (*font_path)
const char *path = font_path;
if (!*path)
path = font_renderer_get_default_font();
if (path)
{
vid->font = font_renderer_new(font_path, font_size);
vid->font = font_renderer_new(path, font_size);
if (vid->font)
{
int r = g_settings.video.msg_color_r * 255;
@@ -108,6 +112,8 @@ static void sdl_init_font(sdl_video_t *vid, const char *font_path, unsigned font
else
SSNES_WARN("Failed to init font.\n");
}
else
SSNES_LOG("Did not find default font.\n");
#else
(void)vid;
(void)font_path;
@@ -459,7 +465,6 @@ static void check_window(sdl_video_t *vid)
static bool sdl_gfx_frame(void *data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
{
sdl_video_t *vid = data;
check_window(vid);
if (SDL_MUSTLOCK(vid->buffer))
SDL_LockSurface(vid->buffer);
@@ -531,6 +536,7 @@ static void sdl_gfx_set_nonblock_state(void *data, bool state)
static bool sdl_gfx_alive(void *data)
{
sdl_video_t *vid = data;
check_window(vid);
return !vid->quitting;
}
+18
View File
@@ -22,6 +22,7 @@
#ifdef __APPLE__
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl.h>
#endif
// SDL 1.2 is portable, sure, but you still need some platform specific workarounds ;)
@@ -170,6 +171,23 @@ bool sdlwrap_set_video_mode(
return true;
}
// SDL 1.2 has an awkward model where you need to "confirm" window resizing.
// SDL 1.3 luckily removes this quirk.
void sdlwrap_set_resize(unsigned width, unsigned height)
{
#if SDL_MODERN
(void)width;
(void)height;
#else
#ifndef __APPLE__ // Resizing on OSX is broken in 1.2 it seems :)
static const int resizable = SDL_RESIZABLE;
#else
static const int resizable = 0;
#endif
SDL_SetVideoMode(width, height, 0, SDL_OPENGL | (g_fullscreen ? SDL_FULLSCREEN : resizable));
#endif
}
void sdlwrap_wm_set_caption(const char *str)
{
#if SDL_MODERN
+2
View File
@@ -58,6 +58,8 @@ bool sdlwrap_key_pressed(int key);
void sdlwrap_check_window(bool *quit,
bool *resize, unsigned *width, unsigned *height, unsigned frame_count);
void sdlwrap_set_resize(unsigned width, unsigned height);
bool sdlwrap_get_wm_info(SDL_SysWMinfo *info);
bool sdlwrap_window_has_focus(void);
+2 -3
View File
@@ -288,8 +288,6 @@ void gl_cg_deinit(void)
static bool load_plain(const char *path)
{
SSNES_LOG("Loading Cg file: %s\n", path);
if (strlen(g_settings.video.second_pass_shader) > 0)
SSNES_LOG("Loading 2nd pass: %s\n", g_settings.video.second_pass_shader);
char *listing[3] = {NULL};
const char *list = NULL;
@@ -308,8 +306,9 @@ static bool load_plain(const char *path)
if (list)
listing[1] = strdup(list);
if (strlen(g_settings.video.second_pass_shader) > 0)
if (*g_settings.video.second_pass_shader && g_settings.video.render_to_texture)
{
SSNES_LOG("Loading 2nd pass: %s\n", g_settings.video.second_pass_shader);
prg[2].fprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, g_settings.video.second_pass_shader, cgFProf, "main_fragment", 0);
prg[2].vprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, g_settings.video.second_pass_shader, cgVProf, "main_vertex", 0);
+1 -1
View File
@@ -900,7 +900,7 @@ bool gl_glsl_init(const char *path)
return false;
// SSNES custom two-pass with two different files.
if (num_progs == 1 && *g_settings.video.second_pass_shader)
if (num_progs == 1 && *g_settings.video.second_pass_shader && g_settings.video.render_to_texture)
{
unsigned secondary_progs = get_xml_shaders(g_settings.video.second_pass_shader, progs, 1);
if (secondary_progs == 1)
+8 -2
View File
@@ -184,9 +184,13 @@ static void set_fullscreen(xv_t *xv)
static void xv_init_font(xv_t *xv, const char *font_path, unsigned font_size)
{
#ifdef HAVE_FREETYPE
if (*font_path)
const char *path = font_path;
if (!*path)
path = font_renderer_get_default_font();
if (path)
{
xv->font = font_renderer_new(font_path, font_size);
xv->font = font_renderer_new(path, font_size);
if (xv->font)
{
int r = g_settings.video.msg_color_r * 255;
@@ -202,6 +206,8 @@ static void xv_init_font(xv_t *xv, const char *font_path, unsigned font_size)
else
SSNES_WARN("Failed to init font.\n");
}
else
SSNES_LOG("Did not find default font.\n");
#endif
}
+1
View File
@@ -55,6 +55,7 @@ static const struct key_bind lut_binds[] = {
{ SDLK_KP_MULTIPLY, SK_KP_MULTIPLY },
{ SDLK_KP_DIVIDE, SK_KP_DIVIDE },
{ SDLK_BACKQUOTE, SK_BACKQUOTE },
{ SDLK_PAUSE, SK_PAUSE },
{ SDLK_KP0, SK_KP0 },
{ SDLK_KP1, SK_KP1 },
{ SDLK_KP2, SK_KP2 },
+1
View File
@@ -65,6 +65,7 @@ static const struct key_bind lut_binds[] = {
{ XK_KP_Multiply, SK_KP_MULTIPLY },
{ XK_KP_Divide, SK_KP_DIVIDE },
{ XK_grave, SK_BACKQUOTE },
{ XK_Pause, SK_PAUSE },
{ XK_KP_0, SK_KP0 },
{ XK_KP_1, SK_KP1 },
{ XK_KP_2, SK_KP2 },
+2
View File
@@ -74,6 +74,8 @@ typedef int16_t (*snes_input_state_t)(bool port, unsigned device, unsigned index
unsigned snes_library_revision_major(void);
unsigned snes_library_revision_minor(void);
const char *snes_library_id(void);
void snes_set_video_refresh(snes_video_refresh_t);
void snes_set_audio_sample(snes_audio_sample_t);
void snes_set_input_poll(snes_input_poll_t);
+1 -1
View File
@@ -78,7 +78,7 @@ uint32_t crc32_adjust(uint32_t crc32, uint8_t input)
uint32_t crc32_calculate(const uint8_t *data, unsigned length)
{
uint32_t crc32 = ~0;
for(unsigned i = 0; i < length; i++)
for (unsigned i = 0; i < length; i++)
crc32 = crc32_adjust(crc32, data[i]);
return ~crc32;
}
+69 -32
View File
@@ -15,15 +15,6 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "netplay.h"
#include "general.h"
#include "autosave.h"
#include "dynamic.h"
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <assert.h>
#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
@@ -32,10 +23,6 @@
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
// Woohoo, Winsock has headers from the STONE AGE! :D
#define close(x) closesocket(x)
#define CONST_CAST (const char*)
#define NONCONST_CAST (char*)
#else
#include <sys/select.h>
#include <sys/types.h>
@@ -45,11 +32,29 @@
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#endif
#include "netplay.h"
#include "general.h"
#include "autosave.h"
#include "dynamic.h"
#include "message.h"
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <assert.h>
#ifdef _WIN32
// Woohoo, Winsock has headers from the STONE AGE! :D
#define close(x) closesocket(x)
#define CONST_CAST (const char*)
#define NONCONST_CAST (char*)
#else
#define CONST_CAST
#define NONCONST_CAST
#endif
#define PREV_PTR(x) ((x) == 0 ? handle->buffer_size - 1 : (x) - 1)
#define NEXT_PTR(x) ((x + 1) % handle->buffer_size)
@@ -90,11 +95,19 @@ struct netplay
uint32_t packet_buffer[UDP_FRAME_PACKETS * 2]; // To compat UDP packet loss we also send old data along with the packets.
uint32_t frame_count;
uint32_t read_frame_count;
uint32_t other_frame_count;
struct addrinfo *addr;
struct sockaddr_storage their_addr;
bool has_client_addr;
};
static void warn_hangup(void)
{
SSNES_WARN("Netplay has disconnected. Will continue without connection ...\n");
if (g_extern.msg_queue)
msg_queue_push(g_extern.msg_queue, "Netplay has disconnected. Will continue without connection.", 0, 480);
}
void input_poll_net(void)
{
if (!netplay_should_skip(g_extern.netplay) && netplay_can_poll(g_extern.netplay))
@@ -263,9 +276,27 @@ bool netplay_can_poll(netplay_t *handle)
return handle->can_poll;
}
// Not really a hash, but should be enough to differentiate implementations from each other.
// Subtle differences in the implementation will not be possible to spot.
// The alternative would have been checking serialization sizes, but it was troublesome for cross platform compat.
static uint32_t implementation_magic_value(void)
{
uint32_t res = 0;
res |= (psnes_library_revision_major() & 0xf) << 0;
res |= (psnes_library_revision_minor() & 0xf) << 4;
// Shouldn't really use this, but oh well :) It'll do the job.
const char *lib = psnes_library_id();
size_t len = strlen(lib);
for (size_t i = 0; i < len; i++)
res ^= lib[i] << (i & 0xf);
return res;
}
static bool send_info(netplay_t *handle)
{
uint32_t header[3] = { htonl(g_extern.cart_crc), htonl(psnes_serialize_size()), htonl(psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM)) };
uint32_t header[3] = { htonl(g_extern.cart_crc), htonl(implementation_magic_value()), htonl(psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM)) };
if (send(handle->fd, CONST_CAST header, sizeof(header), 0) != sizeof(header))
return false;
@@ -400,7 +431,7 @@ static bool send_chunk(netplay_t *handle)
{
if (sendto(handle->udp_fd, CONST_CAST handle->packet_buffer, sizeof(handle->packet_buffer), 0, addr, sizeof(struct sockaddr)) != sizeof(handle->packet_buffer))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
warn_hangup();
handle->has_connection = false;
return false;
}
@@ -440,7 +471,7 @@ static int poll_input(netplay_t *handle, bool block)
if (block && !send_chunk(handle))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
warn_hangup();
handle->has_connection = false;
return -1;
}
@@ -482,7 +513,7 @@ static bool get_self_input_state(netplay_t *handle)
if (!send_chunk(handle))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
warn_hangup();
handle->has_connection = false;
return false;
}
@@ -543,7 +574,7 @@ bool netplay_poll(netplay_t *handle)
if (!get_self_input_state(handle))
return false;
// We skip reading the first frame so the host has a change to grab our host info so we don't block forever :')
// We skip reading the first frame so the host has a chance to grab our host info so we don't block forever :')
if (handle->frame_count == 0)
{
handle->buffer[0].used_real = true;
@@ -554,20 +585,22 @@ bool netplay_poll(netplay_t *handle)
return true;
}
//fprintf(stderr, "Other ptr: %lu, Read ptr: %lu, Self ptr: %lu\n", handle->other_ptr, handle->read_ptr, handle->self_ptr);
//fprintf(stderr, "Before poll: Other ptr: %lu, Read ptr: %lu, Self ptr: %lu\n", handle->other_ptr, handle->read_ptr, handle->self_ptr);
/*
if (handle->buffer_size > 1)
{
assert(handle->other_ptr != handle->self_ptr);
assert(handle->read_ptr != handle->self_ptr);
}
assert(handle->other_ptr == handle->read_ptr);
*/
// We might have reached the end of the buffer, where we simply have to block.
int res = poll_input(handle, handle->other_ptr == NEXT_PTR(handle->self_ptr));
int res = poll_input(handle, handle->other_ptr == handle->self_ptr);
if (res == -1)
{
handle->has_connection = false;
SSNES_WARN("Netplay connection timed out. Will continue without netplay.\n");
warn_hangup();
return false;
}
@@ -579,26 +612,28 @@ bool netplay_poll(netplay_t *handle)
uint32_t buffer[UDP_FRAME_PACKETS * 2];
if (!receive_data(handle, buffer, sizeof(buffer)))
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
warn_hangup();
handle->has_connection = false;
return false;
}
parse_packet(handle, buffer, UDP_FRAME_PACKETS);
} while ((handle->read_frame_count <= handle->frame_count) &&
poll_input(handle, (handle->other_ptr == NEXT_PTR(handle->self_ptr)) &&
poll_input(handle, (handle->other_ptr == handle->self_ptr) &&
(first_read == handle->read_frame_count)) == 1);
}
else
{
// Cannot allow this. Should not happen though.
if (NEXT_PTR(handle->self_ptr) == handle->other_ptr)
if (handle->self_ptr == handle->other_ptr)
{
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
warn_hangup();
return false;
}
}
//fprintf(stderr, "After poll: Other ptr: %lu, Read ptr: %lu, Self ptr: %lu\n", handle->other_ptr, handle->read_ptr, handle->self_ptr);
if (handle->read_ptr != handle->self_ptr)
simulate_input(handle);
else
@@ -668,36 +703,38 @@ void netplay_post_frame(netplay_t *handle)
handle->frame_count++;
// Nothing to do...
if (handle->other_ptr == handle->read_ptr)
if (handle->other_frame_count == handle->read_frame_count)
return;
// Skip ahead if we predicted correctly. Skip until our simulation failed.
while (handle->other_ptr != handle->read_ptr)
while (handle->other_frame_count < handle->read_frame_count)
{
struct delta_frame *ptr = &handle->buffer[handle->other_ptr];
if ((ptr->simulated_input_state != ptr->real_input_state) && !ptr->used_real)
break;
handle->other_ptr = NEXT_PTR(handle->other_ptr);
handle->other_frame_count++;
}
if (handle->other_ptr != handle->read_ptr)
if (handle->other_frame_count < handle->read_frame_count)
{
// Replay frames
handle->is_replay = true;
handle->tmp_ptr = handle->other_ptr;
psnes_unserialize(handle->buffer[handle->other_ptr].state, handle->state_size);
while (handle->tmp_ptr != handle->self_ptr)
bool first = true;
while (first || (handle->tmp_ptr != handle->self_ptr))
{
psnes_serialize(handle->buffer[handle->tmp_ptr].state, handle->state_size);
lock_autosave();
psnes_run();
unlock_autosave();
handle->tmp_ptr = NEXT_PTR(handle->tmp_ptr);
first = false;
}
handle->other_ptr = handle->read_ptr;
handle->other_frame_count = handle->read_frame_count;
handle->is_replay = false;
}
}
+7 -2
View File
@@ -75,7 +75,12 @@ if [ $HAVE_FFMPEG != no ]; then
( [ $HAVE_FFMPEG = auto ] && ( [ $HAVE_AVCODEC = no ] || [ $HAVE_AVFORMAT = no ] || [ $HAVE_AVUTIL = no ] || [ $HAVE_SWSCALE = no ] ) && HAVE_FFMPEG=no ) || HAVE_FFMPEG=yes
fi
check_pkgconf SRC samplerate
if [ $HAVE_FFMPEG = yes ]; then
check_lib FFMPEG_ALLOC_CONTEXT3 "$AVCODEC_LIBS" avcodec_alloc_context3
check_lib FFMPEG_AVCODEC_OPEN2 "$AVCODEC_LIBS" avcodec_open2
check_lib FFMPEG_AVIO_OPEN "$AVFORMAT_LIBS" avio_open
check_lib FFMPEG_AVFORMAT_WRITE_HEADER "$AVFORMAT_LIBS" avformat_write_header
fi
check_lib DYNAMIC $DYLIB dlopen
@@ -89,7 +94,7 @@ check_pkgconf PYTHON python3
add_define_make OS $OS
# Creates config.mk and config.h.
VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL DYLIB CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL PYTHON"
VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL DYLIB CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER X264RGB"
create_config_make config.mk $VARS
create_config_header config.h $VARS
+3 -3
View File
@@ -1,18 +1,18 @@
. qb/qb.params.sh
PACKAGE_NAME=ssnes
PACKAGE_VERSION=0.8.2
PACKAGE_VERSION=0.9
# Adds a command line opt to ./configure --help
# $1: Variable (HAVE_ALSA, HAVE_OSS, etc)
# $2: Comment
# $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_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" no
add_command_line_enable X264RGB "Enable lossless X264 RGB recording" no
add_command_line_enable DYLIB "Enable dynamic loading support" auto
add_command_line_enable NETPLAY "Enable netplay support" auto
add_command_line_enable SRC "Enable libsamplerate support" no
add_command_line_enable CONFIGFILE "Disable support for config file" yes
add_command_line_enable CG "Enable Cg shader support" auto
add_command_line_enable XML "Enable bSNES-style XML shader support" auto
+194 -101
View File
@@ -13,7 +13,11 @@
#include <stdbool.h>
#include "ffemu.h"
#include "fifo_buffer.h"
#include "SDL_thread.h"
#include "thread.h"
#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif
struct video_info
{
@@ -27,6 +31,7 @@ struct video_info
size_t outbuf_size;
int fmt;
int pix_fmt;
int pix_size;
AVFormatContext *format;
@@ -62,13 +67,14 @@ struct ffemu
struct ffemu_params params;
SDL_cond *cond;
SDL_mutex *cond_lock;
SDL_mutex *lock;
scond_t *cond;
slock_t *cond_lock;
slock_t *lock;
fifo_buffer_t *audio_fifo;
fifo_buffer_t *video_fifo;
fifo_buffer_t *attr_fifo;
SDL_Thread *thread;
sthread_t *thread;
volatile bool alive;
volatile bool can_sleep;
};
@@ -79,14 +85,27 @@ static bool init_audio(struct audio_info *audio, struct ffemu_params *param)
if (!codec)
return false;
// FFmpeg just loves to deprecate stuff :)
#ifdef HAVE_FFMPEG_ALLOC_CONTEXT3
audio->codec = avcodec_alloc_context3(codec);
#else
audio->codec = avcodec_alloc_context();
avcodec_get_context_defaults(audio->codec);
#endif
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;
#ifdef HAVE_FFMPEG_AVCODEC_OPEN2
if (avcodec_open2(audio->codec, codec, NULL) != 0)
#else
if (avcodec_open(audio->codec, codec) != 0)
#endif
{
return false;
}
audio->buffer = av_malloc(audio->codec->frame_size * param->channels * sizeof(int16_t));
if (!audio->buffer)
@@ -100,9 +119,13 @@ static bool init_audio(struct audio_info *audio, struct ffemu_params *param)
return true;
}
static bool init_video(struct video_info *video, struct ffemu_params *param)
static bool init_video(struct video_info *video, const struct ffemu_params *param)
{
#ifdef HAVE_X264RGB
AVCodec *codec = avcodec_find_encoder(CODEC_ID_H264);
#else
AVCodec *codec = avcodec_find_encoder(CODEC_ID_FFV1);
#endif
if (!codec)
return false;
@@ -118,26 +141,56 @@ static bool init_video(struct video_info *video, struct ffemu_params *param)
video->pix_size = sizeof(uint32_t);
}
#ifdef HAVE_X264RGB
video->pix_fmt = PIX_FMT_BGR24;
#else
video->pix_fmt = PIX_FMT_RGB32;
#endif
#ifdef HAVE_FFMPEG_ALLOC_CONTEXT3
video->codec = avcodec_alloc_context3(codec);
#else
video->codec = avcodec_alloc_context();
avcodec_get_context_defaults(video->codec);
#endif
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->pix_fmt = PIX_FMT_RGB32;
video->codec->sample_aspect_ratio = av_d2q(param->aspect_ratio * param->out_height / param->out_width, 255);
video->codec->thread_count = 2;
video->codec->pix_fmt = video->pix_fmt;
if (avcodec_open2(video->codec, codec, NULL) != 0)
AVDictionary *opts = NULL;
#ifdef HAVE_X264RGB
video->codec->thread_count = 3;
av_dict_set(&opts, "crf", "0", 0);
#else
video->codec->thread_count = 2;
#endif
#ifdef HAVE_FFMPEG_AVCODEC_OPEN2
if (avcodec_open2(video->codec, codec, &opts) != 0)
#else
if (avcodec_open(video->codec, codec) != 0)
#endif
{
return false;
}
#ifdef HAVE_FFMPEG_AVCODEC_OPEN2
if (opts)
av_dict_free(&opts);
#endif
// Allocate a big buffer :p ffmpeg API doesn't seem to give us some clues how big this buffer should be.
video->outbuf_size = 1 << 23;
video->outbuf = av_malloc(video->outbuf_size);
// Just to make sure we can handle the biggest frames. Seemed to crash with just 256 * 224.
int size = avpicture_get_size(PIX_FMT_RGB32, param->fb_width, param->fb_height);
size_t size = avpicture_get_size(video->pix_fmt, 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_RGB32, param->fb_width, param->fb_height);
avpicture_fill((AVPicture*)video->conv_frame, video->conv_frame_buf, video->pix_fmt, param->out_width, param->out_height);
return true;
}
@@ -147,7 +200,23 @@ static bool 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 (avio_open(&ctx->pb, ctx->filename, AVIO_FLAG_WRITE) < 0)
// FFmpeg sure likes to make things difficult.
#if defined(AVIO_FLAG_WRITE)
#define FFMPEG_FLAG_RW AVIO_FLAG_WRITE
#elif defined(AVIO_WRONLY)
#define FFMPEG_FLAG_RW AVIO_WRONLY
#elif defined(URL_WRONLY)
#define FFMPEG_FLAG_RW URL_WRONLY
#else
#define FFMPEG_FLAG_RW 2 // Seems to be consistent, but you never know.
#endif
#ifdef HAVE_FFMPEG_AVIO_OPEN
if (avio_open(&ctx->pb, ctx->filename, FFMPEG_FLAG_RW) < 0)
#else
if (url_fopen(&ctx->pb, ctx->filename, FFMPEG_FLAG_RW) < 0)
#endif
{
av_free(ctx);
return false;
@@ -169,22 +238,32 @@ static bool init_muxer(ffemu_t *handle)
handle->audio.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
handle->muxer.astream = stream;
#ifdef HAVE_X264RGB // Avoids a warning at end about non-monotonically increasing DTS values. It seems to be harmless to disable this.
ctx->oformat->flags |= AVFMT_TS_NONSTRICT;
#endif
#ifdef HAVE_FFMPEG_AVFORMAT_WRITE_HEADER
if (avformat_write_header(ctx, NULL) < 0)
#else
if (av_write_header(ctx) != 0)
#endif
{
return false;
}
handle->muxer.ctx = ctx;
return true;
}
#define MAX_FRAMES 64
#define MAX_FRAMES 32
static int SDLCALL ffemu_thread(void *data);
static void ffemu_thread(void *data);
static bool init_thread(ffemu_t *handle)
{
assert(handle->lock = SDL_CreateMutex());
assert(handle->cond_lock = SDL_CreateMutex());
assert(handle->cond = SDL_CreateCond());
assert(handle->lock = slock_new());
assert(handle->cond_lock = slock_new());
assert(handle->cond = scond_new());
assert(handle->audio_fifo = fifo_new(32000 * sizeof(int16_t) * handle->params.channels * MAX_FRAMES / 60));
assert(handle->attr_fifo = fifo_new(sizeof(struct ffemu_video_data) * MAX_FRAMES));
assert(handle->video_fifo = fifo_new(handle->params.fb_width * handle->params.fb_height *
@@ -192,7 +271,7 @@ static bool init_thread(ffemu_t *handle)
handle->alive = true;
handle->can_sleep = true;
assert(handle->thread = SDL_CreateThread(ffemu_thread, handle));
assert(handle->thread = sthread_create(ffemu_thread, handle));
return true;
}
@@ -201,17 +280,17 @@ static void deinit_thread(ffemu_t *handle)
{
if (handle->thread)
{
SDL_mutexP(handle->cond_lock);
slock_lock(handle->cond_lock);
handle->alive = false;
handle->can_sleep = false;
SDL_mutexV(handle->cond_lock);
slock_unlock(handle->cond_lock);
SDL_CondSignal(handle->cond);
SDL_WaitThread(handle->thread, NULL);
scond_signal(handle->cond);
sthread_join(handle->thread);
SDL_DestroyMutex(handle->lock);
SDL_DestroyMutex(handle->cond_lock);
SDL_DestroyCond(handle->cond);
slock_free(handle->lock);
slock_free(handle->cond_lock);
scond_free(handle->cond);
handle->thread = NULL;
}
@@ -306,9 +385,9 @@ int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data)
{
for (;;)
{
SDL_mutexP(handle->lock);
slock_lock(handle->lock);
unsigned avail = fifo_write_avail(handle->attr_fifo);
SDL_mutexV(handle->lock);
slock_unlock(handle->lock);
if (!handle->alive)
return -1;
@@ -316,24 +395,33 @@ int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data)
if (avail >= sizeof(*data))
break;
SDL_mutexP(handle->cond_lock);
slock_lock(handle->cond_lock);
if (handle->can_sleep)
{
handle->can_sleep = false;
SDL_CondWait(handle->cond, handle->cond_lock);
scond_wait(handle->cond, handle->cond_lock);
handle->can_sleep = true;
}
else
SDL_CondSignal(handle->cond);
scond_signal(handle->cond);
SDL_mutexV(handle->cond_lock);
slock_unlock(handle->cond_lock);
}
SDL_mutexP(handle->lock);
fifo_write(handle->attr_fifo, data, sizeof(*data));
fifo_write(handle->video_fifo, data->data, data->pitch * data->height);
SDL_mutexV(handle->lock);
SDL_CondSignal(handle->cond);
slock_lock(handle->lock);
// Tightly pack our frame to conserve memory. libsnes tends to use a very large pitch.
struct ffemu_video_data attr_data = *data;
attr_data.pitch = attr_data.width * handle->video.pix_size;
fifo_write(handle->attr_fifo, &attr_data, sizeof(attr_data));
unsigned offset = 0;
for (unsigned y = 0; y < data->height; y++, offset += data->pitch)
fifo_write(handle->video_fifo, (const uint8_t*)data->data + offset, attr_data.pitch);
slock_unlock(handle->lock);
scond_signal(handle->cond);
return 0;
}
@@ -342,9 +430,9 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
{
for (;;)
{
SDL_mutexP(handle->lock);
slock_lock(handle->lock);
unsigned avail = fifo_write_avail(handle->audio_fifo);
SDL_mutexV(handle->lock);
slock_unlock(handle->lock);
if (!handle->alive)
return -1;
@@ -352,23 +440,23 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
if (avail >= data->frames * handle->params.channels * sizeof(int16_t))
break;
SDL_mutexP(handle->cond_lock);
slock_lock(handle->cond_lock);
if (handle->can_sleep)
{
handle->can_sleep = false;
SDL_CondWait(handle->cond, handle->cond_lock);
scond_wait(handle->cond, handle->cond_lock);
handle->can_sleep = true;
}
else
SDL_CondSignal(handle->cond);
scond_signal(handle->cond);
SDL_mutexV(handle->cond_lock);
slock_unlock(handle->cond_lock);
}
SDL_mutexP(handle->lock);
slock_lock(handle->lock);
fifo_write(handle->audio_fifo, data->data, data->frames * handle->params.channels * sizeof(int16_t));
SDL_mutexV(handle->lock);
SDL_CondSignal(handle->cond);
slock_unlock(handle->lock);
scond_signal(handle->cond);
return 0;
}
@@ -376,16 +464,15 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
static int ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_data *data)
{
handle->video.sws_ctx = sws_getCachedContext(handle->video.sws_ctx, data->width, data->height, handle->video.fmt,
handle->params.out_width, handle->params.out_height, PIX_FMT_RGB32, SWS_POINT,
handle->params.out_width, handle->params.out_height, handle->video.pix_fmt, SWS_POINT,
NULL, NULL, NULL);
int linesize = data->pitch;
sws_scale(handle->video.sws_ctx, (const uint8_t* const*)&data->data, &linesize, 0,
handle->video.codec->height, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
data->height, 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);
@@ -422,7 +509,7 @@ static int ffemu_push_audio_thread(ffemu_t *handle, const struct ffemu_audio_dat
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;
size_t write_frames = data->frames - written_frames > can_write ? can_write : data->frames - written_frames;
memcpy(handle->audio.buffer + handle->audio.frames_in_buffer * handle->params.channels,
data->data + written_frames * handle->params.channels,
@@ -443,10 +530,8 @@ static int ffemu_push_audio_thread(ffemu_t *handle, const struct ffemu_audio_dat
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.pts = av_rescale_q(handle->audio.codec->coded_frame->pts, handle->audio.codec->time_base, handle->muxer.astream->time_base);
pkt.flags |= AV_PKT_FLAG_KEY;
handle->audio.frames_in_buffer = 0;
@@ -466,6 +551,35 @@ int ffemu_finalize(ffemu_t *handle)
{
deinit_thread(handle);
// Push out audio still in queue.
size_t audio_buf_size = 512 * handle->params.channels * sizeof(int16_t);
int16_t *audio_buf = av_malloc(audio_buf_size);
if (audio_buf)
{
while (fifo_read_avail(handle->audio_fifo) >= audio_buf_size)
{
fifo_read(handle->audio_fifo, audio_buf, sizeof(audio_buf_size));
struct ffemu_audio_data aud = {
.frames = 512,
.data = audio_buf
};
ffemu_push_audio_thread(handle, &aud);
}
size_t avail = fifo_read_avail(handle->audio_fifo);
fifo_read(handle->audio_fifo, audio_buf, avail);
struct ffemu_audio_data aud = {
.frames = avail / (sizeof(int16_t) * handle->params.channels),
.data = audio_buf
};
ffemu_push_audio_thread(handle, &aud);
av_free(audio_buf);
}
// Push out frames still stuck in queue.
void *video_buf = av_malloc(2 * handle->params.fb_width * handle->params.fb_height * handle->video.pix_size);
if (video_buf)
@@ -481,24 +595,6 @@ int ffemu_finalize(ffemu_t *handle)
av_free(video_buf);
}
size_t audio_buf_size = 128 * handle->params.channels * sizeof(int16_t);
int16_t *audio_buf = av_malloc(audio_buf_size);
if (audio_buf)
{
while (fifo_read_avail(handle->audio_fifo) >= audio_buf_size)
{
fifo_read(handle->audio_fifo, audio_buf, sizeof(audio_buf_size));
struct ffemu_audio_data aud = {
.frames = 128,
.data = audio_buf
};
ffemu_push_audio_thread(handle, &aud);
}
av_free(audio_buf);
}
deinit_thread_buf(handle);
// Push out delayed frames. (MPEG codecs)
@@ -508,24 +604,22 @@ int ffemu_finalize(ffemu_t *handle)
pkt.data = handle->video.outbuf;
int out_size = 0;
do
for (;;)
{
out_size = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, NULL);
if (out_size <= 0)
break;
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);
int err = av_interleaved_write_frame(handle->muxer.ctx, &pkt);
if (err < 0)
break;
}
// Write final data.
av_write_trailer(handle->muxer.ctx);
@@ -533,7 +627,7 @@ int ffemu_finalize(ffemu_t *handle)
return 0;
}
static int SDLCALL ffemu_thread(void *data)
static void ffemu_thread(void *data)
{
ffemu_t *ff = data;
@@ -541,47 +635,47 @@ static int SDLCALL ffemu_thread(void *data)
void *video_buf = av_malloc(2 * ff->params.fb_width * ff->params.fb_height * ff->video.pix_size);
assert(video_buf);
size_t audio_buf_size = 128 * ff->params.channels * sizeof(int16_t);
size_t audio_buf_size = 512 * ff->params.channels * sizeof(int16_t);
int16_t *audio_buf = av_malloc(audio_buf_size);
assert(audio_buf);
struct ffemu_video_data attr_buf;
while (ff->alive)
{
struct ffemu_video_data attr_buf;
bool avail_video = false;
bool avail_audio = false;
SDL_mutexP(ff->lock);
slock_lock(ff->lock);
if (fifo_read_avail(ff->attr_fifo) >= sizeof(attr_buf))
avail_video = true;
if (fifo_read_avail(ff->audio_fifo) >= audio_buf_size)
avail_audio = true;
SDL_mutexV(ff->lock);
slock_unlock(ff->lock);
if (!avail_video && !avail_audio)
{
SDL_mutexP(ff->cond_lock);
slock_lock(ff->cond_lock);
if (ff->can_sleep)
{
ff->can_sleep = false;
SDL_CondWait(ff->cond, ff->cond_lock);
scond_wait(ff->cond, ff->cond_lock);
ff->can_sleep = true;
}
else
SDL_CondSignal(ff->cond);
scond_signal(ff->cond);
SDL_mutexV(ff->cond_lock);
slock_unlock(ff->cond_lock);
}
if (avail_video)
{
SDL_mutexP(ff->lock);
slock_lock(ff->lock);
fifo_read(ff->attr_fifo, &attr_buf, sizeof(attr_buf));
fifo_read(ff->video_fifo, video_buf, attr_buf.height * attr_buf.pitch);
SDL_mutexV(ff->lock);
SDL_CondSignal(ff->cond);
slock_unlock(ff->lock);
scond_signal(ff->cond);
attr_buf.data = video_buf;
ffemu_push_video_thread(ff, &attr_buf);
@@ -589,13 +683,13 @@ static int SDLCALL ffemu_thread(void *data)
if (avail_audio)
{
SDL_mutexP(ff->lock);
slock_lock(ff->lock);
fifo_read(ff->audio_fifo, audio_buf, audio_buf_size);
SDL_mutexV(ff->lock);
SDL_CondSignal(ff->cond);
slock_unlock(ff->lock);
scond_signal(ff->cond);
struct ffemu_audio_data aud = {
.frames = 128,
.frames = 512,
.data = audio_buf
};
@@ -605,6 +699,5 @@ static int SDLCALL ffemu_thread(void *data)
av_free(video_buf);
av_free(audio_buf);
return 0;
}
-1
View File
@@ -29,7 +29,6 @@ struct state_manager
uint64_t *buffer;
size_t buf_size;
uint32_t *tmp_state;
uint32_t *scratch_buf;
size_t top_ptr;
size_t bottom_ptr;
size_t state_size;
+15 -16
View File
@@ -120,6 +120,7 @@ static void set_defaults(void)
g_settings.video.xscale = xscale;
g_settings.video.yscale = yscale;
g_settings.video.base_size = base_size;
g_settings.video.fullscreen = g_extern.force_fullscreen ? true : fullscreen;
g_settings.video.fullscreen_x = fullscreen_x;
g_settings.video.fullscreen_y = fullscreen_y;
@@ -161,10 +162,6 @@ static void set_defaults(void)
g_settings.audio.latency = out_latency;
g_settings.audio.sync = audio_sync;
#ifdef HAVE_SRC
g_settings.audio.src_quality = SAMPLERATE_QUALITY;
#endif
g_settings.rewind_enable = rewind_enable;
g_settings.rewind_buffer_size = rewind_buffer_size;
g_settings.rewind_granularity = rewind_granularity;
@@ -172,6 +169,7 @@ static void set_defaults(void)
g_settings.autosave_interval = autosave_interval;
g_settings.block_sram_overwrite = block_sram_overwrite;
g_settings.savestate_auto_index = savestate_auto_index;
assert(sizeof(g_settings.input.binds[0]) >= sizeof(snes_keybinds_1));
assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_rest));
@@ -291,7 +289,11 @@ static void parse_config_file(void)
return;
if (g_extern.verbose)
config_file_dump(conf, stderr);
{
fprintf(stderr, "=== Config ===\n");
config_file_dump_all(conf, stderr);
fprintf(stderr, "=== Config end ===\n");
}
int tmp_int;
double tmp_double;
@@ -300,6 +302,7 @@ static void parse_config_file(void)
CONFIG_GET_DOUBLE(video.xscale, "video_xscale");
CONFIG_GET_DOUBLE(video.yscale, "video_yscale");
CONFIG_GET_INT(video.base_size, "video_base_size");
CONFIG_GET_INT(video.fullscreen_x, "video_fullscreen_x");
CONFIG_GET_INT(video.fullscreen_y, "video_fullscreen_y");
@@ -383,17 +386,6 @@ static void parse_config_file(void)
CONFIG_GET_INT(audio.latency, "audio_latency");
CONFIG_GET_BOOL(audio.sync, "audio_sync");
#ifdef HAVE_SRC
if (config_get_int(conf, "audio_src_quality", &tmp_int))
{
int quals[] = { SRC_ZERO_ORDER_HOLD, SRC_LINEAR, SRC_SINC_FASTEST,
SRC_SINC_MEDIUM_QUALITY, SRC_SINC_BEST_QUALITY };
if (tmp_int > 0 && tmp_int < 6)
g_settings.audio.src_quality = quals[tmp_int];
}
#endif
CONFIG_GET_STRING(video.driver, "video_driver");
CONFIG_GET_STRING(audio.driver, "audio_driver");
CONFIG_GET_STRING(audio.dsp_plugin, "audio_dsp_plugin");
@@ -421,6 +413,7 @@ static void parse_config_file(void)
CONFIG_GET_STRING(cheat_settings_path, "cheat_settings_path");
CONFIG_GET_BOOL(block_sram_overwrite, "block_sram_overwrite");
CONFIG_GET_BOOL(savestate_auto_index, "savestate_auto_index");
if (!g_extern.has_set_save_path && config_get_array(conf, "savefile_directory", tmp_str, sizeof(tmp_str)))
{
@@ -486,6 +479,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(rewind, SSNES_REWIND)
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
DECLARE_BIND(pause_toggle, SSNES_PAUSE_TOGGLE)
DECLARE_BIND(frame_advance, SSNES_FRAMEADVANCE)
DECLARE_BIND(reset, SSNES_RESET)
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
@@ -521,6 +515,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(rewind, SSNES_REWIND)
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
DECLARE_BIND(pause_toggle, SSNES_PAUSE_TOGGLE)
DECLARE_BIND(frame_advance, SSNES_FRAMEADVANCE)
DECLARE_BIND(reset, SSNES_RESET)
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
@@ -556,6 +551,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(rewind, SSNES_REWIND)
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
DECLARE_BIND(pause_toggle, SSNES_PAUSE_TOGGLE)
DECLARE_BIND(frame_advance, SSNES_FRAMEADVANCE)
DECLARE_BIND(reset, SSNES_RESET)
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
@@ -591,6 +587,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(rewind, SSNES_REWIND)
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
DECLARE_BIND(pause_toggle, SSNES_PAUSE_TOGGLE)
DECLARE_BIND(frame_advance, SSNES_FRAMEADVANCE)
DECLARE_BIND(reset, SSNES_RESET)
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
@@ -626,6 +623,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
DECLARE_BIND(rewind, SSNES_REWIND)
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
DECLARE_BIND(pause_toggle, SSNES_PAUSE_TOGGLE)
DECLARE_BIND(frame_advance, SSNES_FRAMEADVANCE)
DECLARE_BIND(reset, SSNES_RESET)
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
@@ -709,6 +707,7 @@ static const struct key_map sk_map[] = {
{ "scroll_lock", SK_SCROLLOCK },
{ "tilde", SK_BACKQUOTE },
{ "backquote", SK_BACKQUOTE },
{ "pause", SK_PAUSE },
{ "nul", SK_UNKNOWN },
};
+2 -2
View File
@@ -68,8 +68,8 @@ do_phoenix_build()
git pull origin master
fi
make -f Makefile.win32 clean || die "Failed to clean ..."
make -f Makefile.win32 CC="$C_COMPILER" CXX="$CXX_COMPILER" WINDRES="$WINDRES" -j4 all || die "Failed to build ..."
make -f Makefile.win clean || die "Failed to clean ..."
make -f Makefile.win CC="$C_COMPILER" CXX="$CXX_COMPILER" WINDRES="$WINDRES" -j4 all || die "Failed to build ..."
touch ssnes-phoenix.cfg
cp ssnes-phoenix.cfg ssnes-phoenix.exe ../ || die "Failed to copy ..."
+256 -88
View File
@@ -15,14 +15,14 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <libsnes.hpp>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <time.h>
#include <errno.h>
#include "driver.h"
#include "file.h"
#include "general.h"
@@ -34,17 +34,14 @@
#include "strl.h"
#include "screenshot.h"
#include "cheats.h"
#include "audio/utils.h"
#include <assert.h>
#ifdef HAVE_SRC
#include <samplerate.h>
#endif
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#ifdef __APPLE__
#include "SDL.h"
// OSX seems to really need -lSDLmain,
@@ -184,42 +181,61 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
if (!driver.video->frame(driver.video_data, data, width, height, (height == 448 || height == 478) ? 1024 : 2048, msg))
g_extern.video_active = false;
#endif
g_extern.frame_cache.data = data;
g_extern.frame_cache.width = width;
g_extern.frame_cache.height = height;
}
static void audio_sample(uint16_t left, uint16_t right)
static void video_cached_frame(void)
{
if (!g_extern.audio_active)
return;
#ifdef HAVE_FFMPEG
// Cannot allow FFmpeg recording when pushing duped frames.
bool recording = g_extern.recording;
g_extern.recording = false;
#endif
const float *output_data = NULL;
unsigned output_frames = 0;
// Not 100% safe, since the library might have
// freed the memory, but no known implementations do this :D
// It would be really stupid at any rate ...
if (g_extern.frame_cache.data)
{
// Push the pipeline through. A hack sort of.
SSNES_LOG("Pushing cached frame.\n");
video_frame(g_extern.frame_cache.data,
g_extern.frame_cache.width,
g_extern.frame_cache.height);
}
#ifdef HAVE_FFMPEG
g_extern.audio_data.conv_outsamples[g_extern.audio_data.data_ptr] = left;
g_extern.recording = recording;
#endif
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)left/0x8000;
#ifdef HAVE_FFMPEG
g_extern.audio_data.conv_outsamples[g_extern.audio_data.data_ptr] = right;
#endif
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)right/0x8000;
if (g_extern.audio_data.data_ptr < g_extern.audio_data.chunk_size)
return;
}
static bool audio_flush(const int16_t *data, unsigned samples)
{
#ifdef HAVE_FFMPEG
if (g_extern.recording)
{
struct ffemu_audio_data ffemu_data = {
.data = g_extern.audio_data.conv_outsamples,
.frames = g_extern.audio_data.data_ptr / 2
.data = data,
.frames = samples / 2
};
ffemu_push_audio(g_extern.rec, &ffemu_data);
}
#endif
if (g_extern.is_paused)
return true;
const float *output_data = NULL;
unsigned output_frames = 0;
audio_convert_s16_to_float(g_extern.audio_data.data, data, samples);
ssnes_dsp_input_t dsp_input = {
.samples = g_extern.audio_data.data,
.frames = g_extern.audio_data.data_ptr / 2
.frames = samples / 2
};
ssnes_dsp_output_t dsp_output = {
@@ -229,29 +245,17 @@ static void audio_sample(uint16_t left, uint16_t right)
if (g_extern.audio_data.dsp_plugin)
g_extern.audio_data.dsp_plugin->process(g_extern.audio_data.dsp_handle, &dsp_output, &dsp_input);
if (g_extern.frame_is_reverse) // Disable fucked up audio when rewinding...
memset(g_extern.audio_data.data, 0, g_extern.audio_data.chunk_size * sizeof(float));
#ifdef HAVE_SRC
SRC_DATA src_data = {
#else
struct hermite_data src_data = {
#endif
.data_in = dsp_output.samples ? dsp_output.samples : g_extern.audio_data.data,
.data_out = g_extern.audio_data.outsamples,
.input_frames = dsp_output.samples ? dsp_output.frames : (g_extern.audio_data.data_ptr / 2),
.output_frames = g_extern.audio_data.chunk_size * 8,
.end_of_input = 0,
.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate,
.input_frames = dsp_output.samples ? dsp_output.frames : (samples / 2),
.ratio = g_extern.audio_data.src_ratio,
.output_frames = samples * g_extern.audio_data.src_ratio,
};
if (dsp_output.should_resample)
{
#ifdef HAVE_SRC
src_process(g_extern.audio_data.source, &src_data);
#else
hermite_process(g_extern.audio_data.source, &src_data);
#endif
output_data = g_extern.audio_data.outsamples;
output_frames = src_data.output_frames_gen;
@@ -262,30 +266,52 @@ static void audio_sample(uint16_t left, uint16_t right)
output_frames = dsp_output.frames;
}
if (g_extern.audio_data.use_float)
{
if (driver.audio->write(driver.audio_data, output_data, output_frames * sizeof(float) * 2) < 0)
{
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
g_extern.audio_active = false;
return false;
}
}
else
{
for (unsigned i = 0; i < output_frames * 2; i++)
{
int32_t val = output_data[i] * 0x8000;
g_extern.audio_data.conv_outsamples[i] = (val > 0x7FFF) ? 0x7FFF : (val < -0x8000 ? -0x8000 : (int16_t)val);
}
audio_convert_float_to_s16(g_extern.audio_data.conv_outsamples,
output_data, output_frames * 2);
if (driver.audio->write(driver.audio_data, g_extern.audio_data.conv_outsamples, output_frames * sizeof(int16_t) * 2) < 0)
{
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
g_extern.audio_active = false;
return false;
}
}
return true;
}
static void audio_sample_rewind(uint16_t left, uint16_t right)
{
if (!g_extern.audio_active)
return;
g_extern.audio_data.rewind_buf[--g_extern.audio_data.rewind_ptr] = right;
g_extern.audio_data.rewind_buf[--g_extern.audio_data.rewind_ptr] = left;
}
static void audio_sample(uint16_t left, uint16_t right)
{
if (!g_extern.audio_active)
return;
g_extern.audio_data.conv_outsamples[g_extern.audio_data.data_ptr++] = left;
g_extern.audio_data.conv_outsamples[g_extern.audio_data.data_ptr++] = right;
if (g_extern.audio_data.data_ptr < g_extern.audio_data.chunk_size)
return;
g_extern.audio_active = audio_flush(g_extern.audio_data.conv_outsamples,
g_extern.audio_data.data_ptr);
g_extern.audio_data.data_ptr = 0;
}
@@ -316,7 +342,10 @@ static int16_t input_state(bool port, unsigned device, unsigned index, unsigned
g_settings.input.binds[4]
};
int16_t res = driver.input->input_state(driver.input_data, binds, port, device, index, id);
int16_t res = 0;
if (id < 12)
res = driver.input->input_state(driver.input_data, binds, port, device, index, id);
if (g_extern.bsv_movie && !g_extern.bsv_movie_playback)
bsv_movie_set_input(g_extern.bsv_movie, res);
@@ -332,7 +361,7 @@ static int16_t input_state(bool port, unsigned device, unsigned index, unsigned
#endif
#ifdef _WIN32
#define PACKAGE_VERSION "0.8.2"
#define PACKAGE_VERSION "0.9"
#endif
#include "config.features.h"
@@ -359,7 +388,7 @@ static void print_features(void)
_PSUPP(fbo, "FBO", "OpenGL render-to-texture (multi-pass shaders)");
_PSUPP(dynamic, "Dynamic", "Dynamic run-time loading of libsnes library");
_PSUPP(ffmpeg, "FFmpeg", "On-the-fly recording of gameplay with libavcodec");
_PSUPP(src, "SRC", "libsamplerate audio resampling");
_PSUPP(x264rgb, "x264 RGB", "x264 lossless RGB recording for FFmpeg");
_PSUPP(configfile, "Config file", "Configuration file support");
_PSUPP(freetype, "FreeType", "TTF font rendering with FreeType");
_PSUPP(netplay, "Netplay", "Peer-to-peer netplay");
@@ -374,6 +403,7 @@ static void print_help(void)
puts("===================================================================");
puts("Usage: ssnes [rom file] [options...]");
puts("\t-h/--help: Show this help message.");
puts("\t--features: Prints available features compiled into SSNES.");
puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin.");
puts("\t-f/--fullscreen: Start SSNES in fullscreen regardless of config settings.");
puts("\t-S/--savestate: Path to use for save states. If not selected, *.state will be assumed.");
@@ -402,14 +432,13 @@ static void print_help(void)
#ifdef HAVE_FFMPEG
puts("\t-r/--record: Path to record video file.\n\t\tUsing .mkv extension is recommended, and codecs used are FFV1/FLAC.");
puts("\t--size: Overrides output video size when recording with FFmpeg (format: widthxheight).");
#endif
puts("\t-v/--verbose: Verbose logging.");
puts("\t-U/--ups: Specifies path for UPS patch that will be applied to ROM.");
puts("\t--bps: Specifies path for BPS patch that will be applied to ROM.");
puts("\t-X/--xml: Specifies path to XML memory map.");
puts("\t-D/--detach: Detach SSNES from the running console. Not relevant for all platforms.\n");
print_features();
}
static void set_basename(const char *path)
@@ -510,6 +539,7 @@ static void parse_input(int argc, char *argv[])
{ "fullscreen", 0, NULL, 'f' },
#ifdef HAVE_FFMPEG
{ "record", 1, NULL, 'r' },
{ "size", 1, &val, 's' },
#endif
{ "verbose", 0, NULL, 'v' },
{ "gameboy", 1, NULL, 'g' },
@@ -536,6 +566,7 @@ static void parse_input(int argc, char *argv[])
{ "bps", 1, &val, 'B' },
{ "xml", 1, NULL, 'X' },
{ "detach", 0, NULL, 'D' },
{ "features", 0, &val, 'f' },
{ NULL, 0, NULL, 0 }
};
@@ -554,7 +585,7 @@ static void parse_input(int argc, char *argv[])
#endif
char optstring[] = "hs:fvS:m:p4jJg:b:B:Y:Z:P:HC:F:U:DN:X:" FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
for(;;)
for (;;)
{
val = 0;
int c = getopt_long(argc, argv, optstring, opts, &option_index);
@@ -704,10 +735,40 @@ static void parse_input(int argc, char *argv[])
case 'p':
g_extern.netplay_port = strtoul(optarg, NULL, 0);
break;
case 'B':
strlcpy(g_extern.bps_name, optarg, sizeof(g_extern.bps_name));
g_extern.bps_pref = true;
break;
#ifdef HAVE_FFMPEG
case 's':
{
errno = 0;
char *ptr;
g_extern.record_width = strtoul(optarg, &ptr, 0);
if ((*ptr != 'x') || errno)
{
SSNES_ERR("Wrong format for --size.\n");
print_help();
exit(1);
}
ptr++;
g_extern.record_height = strtoul(ptr, &ptr, 0);
if ((*ptr != '\0') || errno)
{
SSNES_ERR("Wrong format for --size.\n");
print_help();
exit(1);
}
break;
}
#endif
case 'f':
print_features();
exit(0);
default:
break;
}
@@ -848,22 +909,31 @@ static void init_recording(void)
struct ffemu_params params = {
.out_width = 256,
.out_height = 224,
.fb_width = 1024,
.fb_width = 512,
.fb_height = 512,
.channels = 2,
.samplerate = 32000,
.filename = g_extern.record_path,
.fps = psnes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps,
.aspect_ratio = 4.0 / 3,
.rgb32 = false,
};
if (g_settings.video.hires_record)
if (g_extern.record_width || g_extern.record_height)
{
params.out_width = g_extern.record_width;
params.out_height = g_extern.record_height;
}
else if (g_settings.video.hires_record)
{
params.out_width = 512;
params.out_height = 448;
}
if (g_settings.video.force_aspect)
params.aspect_ratio = g_settings.video.aspect_ratio;
else
params.aspect_ratio = (float)params.out_width / params.out_height;
if (g_settings.video.post_filter_record && g_extern.filter.active)
{
g_extern.filter.psize(&params.out_width, &params.out_height);
@@ -872,7 +942,7 @@ static void init_recording(void)
unsigned max_width = 512;
unsigned max_height = 512;
g_extern.filter.psize(&max_width, &max_height);
params.fb_width = g_extern.filter.pitch >> 2;
params.fb_width = next_pow2(max_width);
params.fb_height = next_pow2(max_height);
}
@@ -1070,6 +1140,53 @@ static void deinit_autosave(void)
}
}
static void set_savestate_auto_index(void)
{
if (!g_settings.savestate_auto_index)
return;
char state_path[MAXPATHLEN];
strlcpy(state_path, g_extern.savestate_name, sizeof(state_path));
char *split = strrchr(state_path, '/');
if (!split)
split = strrchr(state_path, '\\');
const char *base = state_path;
const char *dir = state_path;
if (split)
{
*split = '\0';
base = split + 1;
}
unsigned max_index = 0;
char **dir_list = dir_list_new(dir, NULL);
if (!dir_list)
return;
unsigned index = 0;
const char *dir_elem;
while ((dir_elem = dir_list[index++]))
{
if (!strstr(dir_elem, base))
continue;
const char *end = dir_elem + strlen(dir_elem);
while ((end != dir_elem) && isdigit(end[-1])) end--;
unsigned index = strtoul(end, NULL, 0);
if (index > max_index)
max_index = index;
}
dir_list_free(dir_list);
g_extern.state_slot = max_index;
SSNES_LOG("Found last state slot: #%u\n", g_extern.state_slot);
}
static void fill_pathnames(void)
{
switch (g_extern.game_type)
@@ -1127,6 +1244,9 @@ static void check_savestates(void)
bool should_savestate = driver.input->key_pressed(driver.input_data, SSNES_SAVE_STATE_KEY);
if (should_savestate && !old_should_savestate)
{
if (g_settings.savestate_auto_index)
g_extern.state_slot++;
char save_path[MAXPATHLEN];
if (g_extern.state_slot > 0)
@@ -1134,7 +1254,7 @@ static void check_savestates(void)
else
snprintf(save_path, sizeof(save_path), "%s", g_extern.savestate_name);
if(!save_state(save_path))
if (!save_state(save_path))
{
msg_queue_clear(g_extern.msg_queue);
char msg[512];
@@ -1160,7 +1280,7 @@ static void check_savestates(void)
else
snprintf(load_path, sizeof(load_path), "%s", g_extern.savestate_name);
if(!load_state(load_path))
if (!load_state(load_path))
{
msg_queue_clear(g_extern.msg_queue);
char msg[512];
@@ -1176,19 +1296,25 @@ static void check_savestates(void)
old_should_loadstate = should_loadstate;
}
static void check_fullscreen(void)
static bool check_fullscreen(void)
{
static bool was_pressed = false;
bool pressed;
// If we go fullscreen we drop all drivers and reinit to be safe.
if ((pressed = driver.input->key_pressed(driver.input_data, SSNES_FULLSCREEN_TOGGLE_KEY)) && !was_pressed)
static bool was_pressed = false;
bool pressed = driver.input->key_pressed(driver.input_data, SSNES_FULLSCREEN_TOGGLE_KEY);
bool toggle = pressed && !was_pressed;
if (toggle)
{
g_settings.video.fullscreen = !g_settings.video.fullscreen;
uninit_drivers();
init_drivers();
// Poll input to avoid possibly stale data to corrupt things.
if (driver.input)
driver.input->poll(driver.input_data);
}
was_pressed = pressed;
return toggle;
}
static void check_stateslots(void)
@@ -1244,12 +1370,42 @@ static void check_input_rate(void)
msg_queue_clear(g_extern.msg_queue);
msg_queue_push(g_extern.msg_queue, msg, 0, 180);
SSNES_LOG("%s\n", msg);
g_extern.audio_data.src_ratio =
(double)g_settings.audio.out_rate / g_settings.audio.in_rate;
}
}
static inline void flush_rewind_audio(void)
{
if (g_extern.frame_is_reverse && g_extern.audio_active) // We just rewound. Flush rewind audio buffer.
{
g_extern.audio_active = audio_flush(g_extern.audio_data.rewind_buf + g_extern.audio_data.rewind_ptr,
g_extern.audio_data.rewind_size - g_extern.audio_data.rewind_ptr);
}
}
static inline void setup_rewind_audio(void)
{
// Push audio ready to be played.
g_extern.audio_data.rewind_ptr = g_extern.audio_data.rewind_size;
for (unsigned i = 0; i < g_extern.audio_data.data_ptr; i += 2)
{
g_extern.audio_data.rewind_buf[--g_extern.audio_data.rewind_ptr] =
g_extern.audio_data.conv_outsamples[i + 1];
g_extern.audio_data.rewind_buf[--g_extern.audio_data.rewind_ptr] =
g_extern.audio_data.conv_outsamples[i + 0];
}
g_extern.audio_data.data_ptr = 0;
}
static void check_rewind(void)
{
flush_rewind_audio();
g_extern.frame_is_reverse = false;
static bool first = true;
if (first)
{
@@ -1267,13 +1423,13 @@ static void check_rewind(void)
if (state_manager_pop(g_extern.state_manager, &buf))
{
g_extern.frame_is_reverse = true;
msg_queue_push(g_extern.msg_queue, "Rewinding!", 0, 30);
setup_rewind_audio();
msg_queue_push(g_extern.msg_queue, "Rewinding!", 0, g_extern.is_paused ? 1 : 30);
psnes_unserialize(buf, psnes_serialize_size());
if (g_extern.bsv_movie)
{
bsv_movie_frame_rewind(g_extern.bsv_movie);
}
}
else
msg_queue_push(g_extern.msg_queue, "Reached end of rewind buffer!", 0, 30);
@@ -1288,6 +1444,8 @@ static void check_rewind(void)
state_manager_push(g_extern.state_manager, g_extern.state_buf);
}
}
psnes_set_audio_sample(g_extern.frame_is_reverse ? audio_sample_rewind : audio_sample);
}
static void check_movie_record(void)
@@ -1332,6 +1490,9 @@ static void check_pause(void)
static bool old_state = false;
bool new_state = driver.input->key_pressed(driver.input_data, SSNES_PAUSE_TOGGLE);
// FRAMEADVANCE will set us into pause mode.
new_state |= !g_extern.is_paused && driver.input->key_pressed(driver.input_data, SSNES_FRAMEADVANCE);
static bool old_focus = true;
bool focus = true;
@@ -1386,6 +1547,20 @@ static void check_pause(void)
old_state = new_state;
}
static void check_oneshot(void)
{
static bool old_state = false;
bool new_state = driver.input->key_pressed(driver.input_data, SSNES_FRAMEADVANCE);
g_extern.is_oneshot = (new_state && !old_state);
old_state = new_state;
// Rewind buttons works like FRAMEREWIND when paused. We will one-shot in that case.
static bool old_rewind_state = false;
bool new_rewind_state = driver.input->key_pressed(driver.input_data, SSNES_REWIND);
g_extern.is_oneshot |= new_rewind_state && !old_rewind_state;
old_rewind_state = new_rewind_state;
}
static void check_reset(void)
{
if (driver.input->key_pressed(driver.input_data, SSNES_RESET))
@@ -1498,12 +1673,13 @@ static void do_state_checks(void)
{
if (!g_extern.netplay)
{
check_reset();
#ifdef HAVE_XML
check_cheats();
#endif
check_pause();
if (g_extern.is_paused)
check_oneshot();
if (check_fullscreen() && g_extern.is_paused)
video_cached_frame();
if (g_extern.is_paused && !g_extern.is_oneshot)
return;
set_fast_forward_button(
@@ -1515,21 +1691,23 @@ static void do_state_checks(void)
check_stateslots();
check_savestates();
}
check_rewind();
check_rewind();
if (!g_extern.bsv_movie_playback)
check_movie_record();
#ifdef HAVE_XML
check_shader_dir();
check_cheats();
#endif
#ifdef HAVE_DYLIB
check_dsp_config();
#endif
check_reset();
}
check_fullscreen();
else
check_fullscreen();
#ifdef HAVE_DYLIB
// DSP plugin doesn't use variable input rate.
@@ -1542,10 +1720,7 @@ static void do_state_checks(void)
static void fill_title_buf(void)
{
if (psnes_library_id)
snprintf(g_extern.title_buf, sizeof(g_extern.title_buf), "SSNES : %s", psnes_library_id());
else
snprintf(g_extern.title_buf, sizeof(g_extern.title_buf), "SSNES");
snprintf(g_extern.title_buf, sizeof(g_extern.title_buf), "SSNES : %s", psnes_library_id());
}
static void init_state(void)
@@ -1574,6 +1749,7 @@ int main(int argc, char *argv[])
SSNES_LOG("Version of libsnes API: %u.%u\n", psnes_library_revision_major(), psnes_library_revision_minor());
fill_pathnames();
set_savestate_auto_index();
if (!init_rom_file(g_extern.game_type))
goto error;
@@ -1633,7 +1809,7 @@ int main(int argc, char *argv[])
do_state_checks();
// Run libsnes for one frame.
if (!g_extern.is_paused)
if (!g_extern.is_paused || g_extern.is_oneshot)
{
lock_autosave();
@@ -1658,15 +1834,7 @@ int main(int argc, char *argv[])
else
{
input_poll();
#ifdef _WIN32
Sleep(10);
#else
struct timespec tv = {
.tv_sec = 0,
.tv_nsec = 10000000
};
nanosleep(&tv, NULL);
#endif
ssnes_sleep(10);
}
}
+15 -3
View File
@@ -19,10 +19,14 @@
# Path to external video driver using the SSNES driver API.
# video_external_driver =
# Windowed xscale and yscale (Real x res: 224 * xscale * aspect_ratio, real y scale: 224 * xscale)
# Windowed xscale and yscale
# (Real x res: base_size * xscale * aspect_ratio, real y res: base_size * yscale)
# video_xscale = 3.0
# video_yscale = 3.0
# Base size for windowed mode calculations. Generally 224 for SNES, but can be modified.
# video_base_size = 224
# Fullscreen resolution. Resolution of 0 uses the resolution of the desktop.
# video_fullscreen_x = 0
# video_fullscreen_y = 0
@@ -132,8 +136,6 @@
# Desired audio latency in milliseconds. Might not be honored if driver can't provide given latency.
# audio_latency = 64
# libsamplerate quality. Valid values are from 1 to 5. These values map to zero_order_hold, linear, sinc_fastest, sinc_medium and sinc_best respectively.
# audio_src_quality =
### Input
@@ -275,6 +277,9 @@
# Toggle between paused and non-paused state
# input_pause_toggle = p
# Frame advance when game is paused
# input_frame_advance = k
# Reset the emulated SNES.
# input_reset = h
@@ -320,3 +325,10 @@
# Block SRAM from being overwritten when loading save states.
# Might potentially lead to buggy games.
# block_sram_overwrite = false
# When saving a savestate, save state index is automatically increased before
# it is saved.
# Also, when loading a ROM, the index will be set to the highest existing index.
# There is no upper bound on the index.
# savestate_auto_index = false
+319
View File
@@ -0,0 +1,319 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - 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 "thread.h"
#include <stdlib.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <pthread.h>
#include <time.h>
#endif
#ifdef __MACH__
#include <mach/clock.h>
#include <mach/mach.h>
#endif
struct thread_data
{
void (*func)(void*);
void *userdata;
};
#ifdef _WIN32
struct sthread
{
HANDLE thread;
};
static DWORD CALLBACK thread_wrap(void *data_)
{
struct thread_data *data = data_;
data->func(data->userdata);
free(data);
return 0;
}
sthread_t *sthread_create(void (*thread_func)(void*), void *userdata)
{
sthread_t *thread = calloc(1, sizeof(*thread));
if (!thread)
return NULL;
struct thread_data *data = calloc(1, sizeof(*data));
if (!data)
{
free(thread);
return NULL;
}
data->func = thread_func;
data->userdata = userdata;
thread->thread = CreateThread(NULL, 0, thread_wrap, data, 0, NULL);
if (!thread->thread)
{
free(data);
free(thread);
return NULL;
}
return thread;
}
void sthread_join(sthread_t *thread)
{
WaitForSingleObject(thread->thread, INFINITE);
CloseHandle(thread->thread);
free(thread);
}
struct slock
{
CRITICAL_SECTION lock;
};
slock_t *slock_new(void)
{
slock_t *lock = calloc(1, sizeof(*lock));
if (!lock)
return NULL;
InitializeCriticalSection(&lock->lock);
return lock;
}
void slock_free(slock_t *lock)
{
DeleteCriticalSection(&lock->lock);
free(lock);
}
void slock_lock(slock_t *lock)
{
EnterCriticalSection(&lock->lock);
}
void slock_unlock(slock_t *lock)
{
LeaveCriticalSection(&lock->lock);
}
struct scond
{
HANDLE event;
};
scond_t *scond_new(void)
{
scond_t *cond = calloc(1, sizeof(*cond));
if (!cond)
return NULL;
cond->event = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!cond->event)
{
free(cond);
return NULL;
}
return cond;
}
void scond_wait(scond_t *cond, slock_t *lock)
{
WaitForSingleObject(cond->event, 0);
slock_unlock(lock);
WaitForSingleObject(cond->event, INFINITE);
slock_lock(lock);
}
bool scond_wait_timeout(scond_t *cond, slock_t *lock, unsigned timeout_ms)
{
WaitForSingleObject(cond->event, 0);
slock_unlock(lock);
DWORD res = WaitForSingleObject(cond->event, timeout_ms);
slock_lock(lock);
return res == WAIT_OBJECT_0;
}
void scond_signal(scond_t *cond)
{
SetEvent(cond->event);
}
void scond_free(scond_t *cond)
{
CloseHandle(cond->event);
free(cond);
}
#else
struct sthread
{
pthread_t id;
};
static void *thread_wrap(void *data_)
{
struct thread_data *data = data_;
data->func(data->userdata);
free(data);
pthread_exit(NULL);
}
sthread_t *sthread_create(void (*thread_func)(void*), void *userdata)
{
sthread_t *thr = calloc(1, sizeof(*thr));
if (!thr)
return NULL;
struct thread_data *data = calloc(1, sizeof(*data));
if (!data)
{
free(thr);
return NULL;
}
data->func = thread_func;
data->userdata = userdata;
if (pthread_create(&thr->id, NULL, thread_wrap, data) < 0)
{
free(data);
free(thr);
return NULL;
}
return thr;
}
void sthread_join(sthread_t *thread)
{
pthread_join(thread->id, NULL);
free(thread);
}
struct slock
{
pthread_mutex_t lock;
};
slock_t *slock_new(void)
{
slock_t *lock = calloc(1, sizeof(*lock));
if (!lock)
return NULL;
if (pthread_mutex_init(&lock->lock, NULL) < 0)
{
free(lock);
return NULL;
}
return lock;
}
void slock_free(slock_t *lock)
{
pthread_mutex_destroy(&lock->lock);
free(lock);
}
void slock_lock(slock_t *lock)
{
pthread_mutex_lock(&lock->lock);
}
void slock_unlock(slock_t *lock)
{
pthread_mutex_unlock(&lock->lock);
}
struct scond
{
pthread_cond_t cond;
};
scond_t *scond_new(void)
{
scond_t *cond = calloc(1, sizeof(*cond));
if (!cond)
return NULL;
if (pthread_cond_init(&cond->cond, NULL) < 0)
{
free(cond);
return NULL;
}
return cond;
}
void scond_free(scond_t *cond)
{
pthread_cond_destroy(&cond->cond);
free(cond);
}
void scond_wait(scond_t *cond, slock_t *lock)
{
pthread_cond_wait(&cond->cond, &lock->lock);
}
bool scond_wait_timeout(scond_t *cond, slock_t *lock, unsigned timeout_ms)
{
struct timespec now;
#ifdef __MACH__ // OSX doesn't have clock_gettime ... :(
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
now.tv_sec = mts.tv_sec;
now.tv_nsec = mts.tv_nsec;
#else
clock_gettime(CLOCK_REALTIME, &now);
#endif
now.tv_sec += timeout_ms / 1000;
now.tv_nsec += timeout_ms * 1000000L;
now.tv_sec += now.tv_nsec / 1000000000L;
now.tv_nsec = now.tv_nsec % 1000000000L;
int ret = pthread_cond_timedwait(&cond->cond, &lock->lock, &now);
return ret == 0;
}
void scond_signal(scond_t *cond)
{
pthread_cond_signal(&cond->cond);
}
#endif
+52
View File
@@ -0,0 +1,52 @@
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010-2011 - 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 THREAD_H__
#define THREAD_H__
#include <stdbool.h>
// Implements the bare minimum needed for SSNES. :)
typedef struct sthread sthread_t;
// Threading
sthread_t *sthread_create(void (*thread_func)(void*), void *userdata);
void sthread_join(sthread_t *thread);
// Mutexes
typedef struct slock slock_t;
slock_t *slock_new(void);
void slock_free(slock_t *lock);
void slock_lock(slock_t *lock);
void slock_unlock(slock_t *lock);
// Condition variables.
typedef struct scond scond_t;
scond_t *scond_new(void);
void scond_free(scond_t *cond);
void scond_wait(scond_t *cond, slock_t *lock);
bool scond_wait_timeout(scond_t *cond, slock_t *lock, unsigned timeout_ms);
void scond_signal(scond_t *cond);
#endif
+2 -1
View File
@@ -92,6 +92,7 @@ static struct bind binds[] = {
MISC_BIND("Rewind", rewind)
MISC_BIND("Movie recording toggle", movie_record_toggle)
MISC_BIND("Pause", pause_toggle)
MISC_BIND("Frame advance", frame_advance)
MISC_BIND("Reset", reset)
MISC_BIND("Next shader", shader_next)
MISC_BIND("Previous shader", shader_prev)
@@ -235,7 +236,7 @@ static void parse_input(int argc, char *argv[])
};
int option_index = 0;
for(;;)
for (;;)
{
int c = getopt_long(argc, argv, optstring, opts, &option_index);
if (c == -1)