mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-09 03:29:43 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70e9f084b7 | ||
|
|
824790a80d | ||
|
|
23d23983bc | ||
|
|
c42655ed91 | ||
|
|
f46ee78c76 | ||
|
|
5d1d188231 | ||
|
|
c55ce543c2 | ||
|
|
de8bc62474 | ||
|
|
397da887a7 | ||
|
|
f44733d7f6 | ||
|
|
5faa28a1a2 | ||
|
|
efaa85ff9b | ||
|
|
8f5580be10 | ||
|
|
c7acd99548 | ||
|
|
64ee13e6f4 | ||
|
|
e134f8afc7 | ||
|
|
dd89e6fbe0 | ||
|
|
a79a1b46f7 | ||
|
|
6467f51ea8 | ||
|
|
141eefb03b | ||
|
|
068c224d83 | ||
|
|
5bc8090ea7 | ||
|
|
466ab1ef8d | ||
|
|
a1c19ed1f5 | ||
|
|
cce4b7d20c | ||
|
|
80925ffc11 | ||
|
|
9eb14e328d | ||
|
|
5ae0cb1821 | ||
|
|
fc594f7f0f | ||
|
|
0c4e406ee7 | ||
|
|
a082ec5251 | ||
|
|
953b5daf0a | ||
|
|
b4ff622f32 | ||
|
|
f26b54c998 | ||
|
|
d7b3f39136 | ||
|
|
5bcf601a40 | ||
|
|
e91ebdac97 | ||
|
|
b2dd12d186 | ||
|
|
9c79bf0ba2 | ||
|
|
f48247a18b | ||
|
|
48e015558e | ||
|
|
0ff3cbc93b | ||
|
|
25f0e162a4 | ||
|
|
0810812cd9 | ||
|
|
15e0b721e6 | ||
|
|
9d12c3ac87 | ||
|
|
a7c3d8d17a | ||
|
|
8fbfdee011 | ||
|
|
bc999e72fa | ||
|
|
c7f8110670 | ||
|
|
b0dbc67281 | ||
|
|
4916f7b08b | ||
|
|
1058d94ec1 | ||
|
|
340dca3c6d | ||
|
|
4ae0f3da22 | ||
|
|
7d2d1ce49e | ||
|
|
fa26f6a10c | ||
|
|
db71a5cdba | ||
|
|
5058fdcd1a | ||
|
|
ef04f199d5 | ||
|
|
d26dd9d9e7 | ||
|
|
d2bc4096de | ||
|
|
08ca1a54d3 | ||
|
|
869eddfc1a | ||
|
|
fbced43f11 | ||
|
|
c996f816b8 | ||
|
|
33ecedb462 | ||
|
|
a58676c9e1 | ||
|
|
1ee9722e5f | ||
|
|
81250657f0 | ||
|
|
7d33cccc8e | ||
|
|
3db79ab2f0 | ||
|
|
ea0f6813d2 | ||
|
|
f6d4d32c03 | ||
|
|
892e82141f | ||
|
|
65ec7fae9c | ||
|
|
a0acb75a19 | ||
|
|
bcae973d82 | ||
|
|
d01a4fe61b | ||
|
|
8d4620b680 | ||
|
|
c2cedb1b6f | ||
|
|
157dcad87b |
No files matched your search
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,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
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ enum
|
||||
SSNES_REWIND,
|
||||
SSNES_MOVIE_RECORD_TOGGLE,
|
||||
SSNES_PAUSE_TOGGLE,
|
||||
SSNES_FRAMEADVANCE,
|
||||
SSNES_RESET,
|
||||
SSNES_SHADER_NEXT,
|
||||
SSNES_SHADER_PREV,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 },
|
||||
};
|
||||
|
||||
|
||||
@@ -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 ..."
|
||||
|
||||
|
||||
@@ -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(¶ms.out_width, ¶ms.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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user