Merge branch 'master' of ssh://10.0.0.4:443/~/bin/ssnes

This commit is contained in:
Themaister 2011-01-01 18:24:12 +01:00
commit 92adb4c645
31 changed files with 2552 additions and 287 deletions

View File

@ -2,43 +2,44 @@ include config.mk
TARGET = ssnes
DEFINES =
OBJ = ssnes.o file.o driver.o
libsnes = -lsnes
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o
LIBS = -lsamplerate $(libsnes)
LIBS = -lsamplerate
ifeq ($(BUILD_RSOUND), 1)
ifeq ($(HAVE_RSOUND), 1)
OBJ += audio/rsound.o
LIBS += -lrsound
endif
ifeq ($(BUILD_OSS), 1)
ifeq ($(HAVE_OSS), 1)
OBJ += audio/oss.o
endif
ifeq ($(BUILD_ALSA), 1)
ifeq ($(HAVE_ALSA), 1)
OBJ += audio/alsa.o
LIBS += -lasound
endif
ifeq ($(BUILD_ROAR), 1)
ifeq ($(HAVE_ROAR), 1)
OBJ += audio/roar.o
LIBS += -lroar
endif
ifeq ($(BUILD_AL), 1)
ifeq ($(HAVE_AL), 1)
OBJ += audio/openal.o
LIBS += -lopenal
endif
ifeq ($(HAVE_JACK),1)
OBJ += audio/jack.o
LIBS += -ljack
endif
ifeq ($(BUILD_OPENGL), 1)
ifeq ($(HAVE_GLFW), 1)
OBJ += gfx/gl.o
LIBS += -lglfw
endif
ifeq ($(BUILD_CG), 1)
ifeq ($(HAVE_CG), 1)
LIBS += -lCg -lCgGL
DEFINES += -DHAVE_CG
endif
ifeq ($(BUILD_FILTER), 1)
ifeq ($(HAVE_FILTER), 1)
OBJ += hqflt/hq.o
OBJ += hqflt/grayscale.o
OBJ += hqflt/bleed.o
@ -46,9 +47,19 @@ ifeq ($(BUILD_FILTER), 1)
OBJ += hqflt/snes_ntsc/snes_ntsc.o
endif
CFLAGS = -Wall -O3 -std=gnu99 -Wno-unused-variable -I. $(DEFINES)
ifeq ($(HAVE_DYNAMIC), 1)
LIBS += -ldl
else
LIBS += $(libsnes)
endif
all: $(TARGET)
CFLAGS = -Wall -O3 -g -std=gnu99 -I.
all: $(TARGET) config.mk
config.mk: configure qb/*
@echo "config.mk is outdated or non-existing. Run ./configure again."
@exit 1
ssnes: $(OBJ)
$(CXX) -o $@ $(OBJ) $(LIBS) $(CFLAGS)
@ -57,14 +68,16 @@ ssnes: $(OBJ)
$(CC) $(CFLAGS) -c -o $@ $<
install: $(TARGET)
install -m755 $(TARGET) $(PREFIX)/bin
install -m755 $(TARGET) $(DESTDIR)/$(PREFIX)/bin
install -m644 ssnes.cfg $(DESTDIR)/etc/ssnes.cfg
uninstall: $(TARGET)
rm -rf $(PREFIX)/bin/$(TARGET)
rm -rf $(DESTDIR)/$(PREFIX)/bin/$(TARGET)
clean:
rm -f *.o
rm -f audio/*.o
rm -f conf/*.o
rm -f gfx/*.o
rm -f hqflt/*.o
rm -f hqflt/snes_ntsc/*.o

View File

@ -10,9 +10,7 @@ This enables the possibility of custom front-ends for the emulator.
# Philosophy
SSNES attempts to be very small and lean, while still having all the useful core features expected from an emulator.
It is close in spirit to suckless' DWM, in that configuring the emulator requires a recompile.
The configuration is done through editing a C header file.
C programming skills are not necessary to configure it (no programming involved), but some basic programming experience might be needed.
It is used through command-line.
# Dependencies
@ -33,6 +31,7 @@ SSNES needs one of these audio driver libraries:
- RoarAudio
- RSound
- OpenAL
- JACK
# Building libsnes
@ -46,17 +45,26 @@ SSNES needs one of these audio driver libraries:
# Configuring
SSNES configuring is done through editing <tt>config.h</tt> and <tt>config.mk</tt>.
The default configs can be found in <tt>config.h.def</tt> and <tt>config.mk.def</tt> respectively.
Do note that you might have to edit <tt>config.mk</tt> if you edit driver and filter options!
By default, ALSA audio driver is assumed.
The default configuration is defined in config.def.h.
These can later be tweaked by using the ssnes config file.
A sample configuration file is installed to /etc/ssnes.cfg.
This is the system-wide config file.
Each user should create a config file in $XDG\_CONFIG\_HOME/ssnes/ssnes.cfg.
The users only need to configure a certain option if the desired value deviates from the value defined in config.def.h.
Most options in <tt>config.h</tt> should be self-explanatory.
To configure joypads, start up <tt>jstest /dev/input/js0</tt> to determine which joypad buttons (and axis) to use.
# Compiling and installing
The good old <tt>make && sudo make install</tt> should do the trick :)
As most packages, SSNES is built using the standard <tt>./configure && make && make install</tt>
Do note that the build system is not autotools based, but resembles it.
Notable options for ./configure:
--with-libsnes=: Normally libsnes is located with -lsnes, however, this can be overridden.
--enable-dynamic: Do not link to libsnes at compile time, but load libsnes dynamically at runtime. libsnes\_path in config file defines which library to load. Useful for development.
Do note that these two options are mutually exclusive.
# Filters and Cg shader support
@ -67,3 +75,5 @@ Cg shaders are compiled at run-time, and shaders could be dropped in.
All shaders share a common interface to pass some essential arguments such as texture size and viewport size. (Common for pixel art scalers)
Some Cg shaders are included in hqflt/cg/ and could be used as an example.
While these shaders are Cg, they closely resemble the GLSL shaders found in bSNES shader pack, so porting them is trivial.

View File

@ -190,7 +190,8 @@ const audio_driver_t audio_alsa = {
.stop = __alsa_stop,
.start = __alsa_start,
.set_nonblock_state = __alsa_set_nonblock_state,
.free = __alsa_free
.free = __alsa_free,
.ident = "alsa"
};

300
audio/jack.c Normal file
View File

@ -0,0 +1,300 @@
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "driver.h"
#include <stdlib.h>
#include "general.h"
#include <jack/jack.h>
#include <jack/types.h>
#include <jack/ringbuffer.h>
#include <pthread.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#define FRAMES(x) (x / (sizeof(int16_t) * 2))
#define SAMPLES(x) (x / sizeof(int16_t))
typedef struct jack
{
jack_client_t *client;
jack_port_t *ports[2];
jack_ringbuffer_t *buffer[2];
volatile bool shutdown;
bool nonblock;
pthread_cond_t cond;
pthread_mutex_t cond_lock;
} jack_t;
static int process_cb(jack_nframes_t nframes, void *data)
{
jack_t *jd = data;
if (nframes <= 0)
{
pthread_cond_signal(&jd->cond);
return 0;
}
jack_nframes_t avail[2];
avail[0] = jack_ringbuffer_read_space(jd->buffer[0]);
avail[1] = jack_ringbuffer_read_space(jd->buffer[1]);
jack_nframes_t min_avail = ((avail[0] < avail[1]) ? avail[0] : avail[1]) / sizeof(jack_default_audio_sample_t);
if (min_avail > nframes)
min_avail = nframes;
//static int underrun = 0;
//if (min_avail < nframes)
//{
// SSNES_LOG("JACK: Underrun count: %d\n", underrun++);
// fprintf(stderr, "required %d frames, got %d.\n", (int)nframes, (int)min_avail);
//}
for (int i = 0; i < 2; i++)
{
jack_default_audio_sample_t *out = jack_port_get_buffer(jd->ports[i], nframes);
assert(out);
jack_ringbuffer_read(jd->buffer[i], (char*)out, min_avail * sizeof(jack_default_audio_sample_t));
for (jack_nframes_t f = min_avail; f < nframes; f++)
{
out[f] = 0.0f;
}
}
pthread_cond_signal(&jd->cond);
return 0;
}
static void shutdown_cb(void *data)
{
jack_t *jd = data;
jd->shutdown = true;
pthread_cond_signal(&jd->cond);
}
static inline void s16_to_float(jack_default_audio_sample_t * restrict out, const int16_t * restrict in, size_t samples)
{
for (int i = 0; i < samples; i++)
out[i] = (float)in[i] / 0x8000;
}
static void parse_ports(const char **dest_ports, const char **jports)
{
int parsed = 0;
const char *con = strtok(g_settings.audio.device, ",");
if (con)
dest_ports[parsed++] = con;
con = strtok(NULL, ",");
if (con)
dest_ports[parsed++] = con;
for (int i = parsed; i < 2; i++)
dest_ports[i] = jports[i];
}
static void* __jack_init(const char* device, int rate, int latency)
{
jack_t *jd = calloc(1, sizeof(jack_t));
if ( jd == NULL )
return NULL;
const char **jports = NULL;
jd->client = jack_client_open("SSNES", JackNullOption, NULL);
if (jd->client == NULL)
goto error;
g_settings.audio.out_rate = jack_get_sample_rate(jd->client);
jack_set_process_callback(jd->client, process_cb, jd);
jack_on_shutdown(jd->client, shutdown_cb, jd);
jd->ports[0] = jack_port_register(jd->client, "left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
jd->ports[1] = jack_port_register(jd->client, "right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
if (jd->ports[0] == NULL || jd->ports[1] == NULL)
{
SSNES_ERR("Failed to register ports.\n");
goto error;
}
jack_nframes_t bufsize;
jack_nframes_t jack_bufsize = jack_get_buffer_size(jd->client);
bufsize = (latency * g_settings.audio.out_rate / 1000) > jack_bufsize * 2 ? (latency * g_settings.audio.out_rate / 1000) : jack_bufsize * 2;
bufsize *= sizeof(jack_default_audio_sample_t);
//fprintf(stderr, "jack buffer size: %d\n", (int)bufsize);
for (int i = 0; i < 2; i++)
{
jd->buffer[i] = jack_ringbuffer_create(bufsize);
if (jd->buffer[i] == NULL)
{
SSNES_ERR("Failed to create buffers.\n");
goto error;
}
}
const char *dest_ports[2];
jports = jack_get_ports(jd->client, NULL, NULL, JackPortIsPhysical | JackPortIsInput);
if (jports == NULL)
{
SSNES_ERR("Failed to get ports.\n");
goto error;
}
parse_ports(dest_ports, jports);
if (jack_activate(jd->client) < 0)
{
SSNES_ERR("Failed to activate Jack...\n");
goto error;
}
for (int i = 0; i < 2; i++)
{
if (jack_connect(jd->client, jack_port_name(jd->ports[i]), dest_ports[i]))
{
SSNES_ERR("Failed to connect to Jack port.\n");
goto error;
}
}
pthread_cond_init(&jd->cond, NULL);
pthread_mutex_init(&jd->cond_lock, NULL);
jack_free(jports);
return jd;
error:
if (jports != NULL)
jack_free(jports);
return NULL;
}
static size_t write_buffer(jack_t *jd, const void *buf, size_t size)
{
//fprintf(stderr, "write_buffer: size: %zu\n", size);
// Convert our data to float, deinterleave and write.
jack_default_audio_sample_t out_buffer[size / sizeof(int16_t)];
jack_default_audio_sample_t out_deinterleaved_buffer[2][FRAMES(size)];
s16_to_float(out_buffer, buf, SAMPLES(size));
for (int i = 0; i < 2; i++)
for (size_t j = 0; j < FRAMES(size); j++)
out_deinterleaved_buffer[i][j] = out_buffer[j * 2 + i];
for(;;)
{
if (jd->shutdown)
return 0;
size_t avail[2];
avail[0] = jack_ringbuffer_write_space(jd->buffer[0]);
avail[1] = jack_ringbuffer_write_space(jd->buffer[1]);
size_t min_avail = avail[0] < avail[1] ? avail[0] : avail[1];
if (jd->nonblock)
{
if (min_avail < FRAMES(size) * sizeof(jack_default_audio_sample_t))
size = min_avail * 2 * sizeof(int16_t) / sizeof(jack_default_audio_sample_t);
break;
}
else
{
//fprintf(stderr, "Write avail is: %d\n", (int)min_avail);
if (min_avail >= FRAMES(size) * sizeof(jack_default_audio_sample_t))
break;
}
pthread_mutex_lock(&jd->cond_lock);
pthread_cond_wait(&jd->cond, &jd->cond_lock);
pthread_mutex_unlock(&jd->cond_lock);
}
for (int i = 0; i < 2; i++)
jack_ringbuffer_write(jd->buffer[i], (const char*)out_deinterleaved_buffer[i], FRAMES(size) * sizeof(jack_default_audio_sample_t));
return size;
}
static ssize_t __jack_write(void* data, const void* buf, size_t size)
{
jack_t *jd = data;
return write_buffer(jd, buf, size);
}
static bool __jack_stop(void *data)
{
(void)data;
return true;
}
static void __jack_set_nonblock_state(void *data, bool state)
{
jack_t *jd = data;
jd->nonblock = state;
}
static bool __jack_start(void *data)
{
(void)data;
return true;
}
static void __jack_free(void *data)
{
jack_t *jd = data;
jd->shutdown = true;
if (jd->client != NULL)
{
jack_deactivate(jd->client);
jack_client_close(jd->client);
}
for (int i = 0; i < 2; i++)
if (jd->buffer[i] != NULL)
jack_ringbuffer_free(jd->buffer[i]);
pthread_mutex_destroy(&jd->cond_lock);
pthread_cond_destroy(&jd->cond);
free(jd);
}
const audio_driver_t audio_jack = {
.init = __jack_init,
.write = __jack_write,
.stop = __jack_stop,
.start = __jack_start,
.set_nonblock_state = __jack_set_nonblock_state,
.free = __jack_free,
.ident = "jack"
};

View File

@ -233,7 +233,8 @@ const audio_driver_t audio_openal = {
.stop = __al_stop,
.start = __al_start,
.set_nonblock_state = __al_set_nonblock_state,
.free = __al_free
.free = __al_free,
.ident = "openal"
};

View File

@ -136,7 +136,8 @@ const audio_driver_t audio_oss = {
.stop = __oss_stop,
.start = __oss_start,
.set_nonblock_state = __oss_set_nonblock_state,
.free = __oss_free
.free = __oss_free,
.ident = "oss"
};

View File

@ -107,7 +107,8 @@ const audio_driver_t audio_roar = {
.stop = __roar_stop,
.start = __roar_start,
.set_nonblock_state = __roar_set_nonblock_state,
.free = __roar_free
.free = __roar_free,
.ident = "roar"
};

View File

@ -135,7 +135,8 @@ const audio_driver_t audio_rsound = {
.stop = __rsd_stop,
.start = __rsd_start,
.set_nonblock_state = __rsd_set_nonblock_state,
.free = __rsd_free
.free = __rsd_free,
.ident = "rsound"
};

293
conf/config_file.c Normal file
View File

@ -0,0 +1,293 @@
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "config_file.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "general.h"
struct entry_list
{
char *key;
char *value;
struct entry_list *next;
};
struct config_file
{
struct entry_list *entries;
};
static char *getaline(FILE *file)
{
char *newline = malloc(9);
size_t cur_size = 8;
size_t index = 0;
int in = getc(file);
while (in != EOF && in != '\n')
{
if (index == cur_size)
{
cur_size *= 2;
newline = realloc(newline, cur_size + 1);
}
newline[index++] = in;
in = getc(file);
}
newline[index] = '\0';
return newline;
}
static bool parse_line(struct entry_list *list, char *line)
{
// Remove everything after comment.
char *comment = strchr(line, '#');
if (comment)
*comment = '\0';
// Skips to first character.
while (isspace(*line))
line++;
char *key = malloc(9);
size_t cur_size = 8;
size_t index = 0;
while (isgraph(*line))
{
if (index == cur_size)
{
cur_size *= 2;
key = realloc(key, cur_size + 1);
}
key[index++] = *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->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;
}
static void print_config(config_file_t *conf)
{
struct entry_list *tmp = conf->entries;
while (tmp != NULL)
{
printf("Key: \"%s\", Value: \"%s\"\n", tmp->key, tmp->value);
tmp = tmp->next;
}
}
config_file_t *config_file_new(const char *path)
{
FILE *file = fopen(path, "r");
if (!file)
return NULL;
struct config_file *conf = calloc(1, sizeof(*conf));
if (conf == NULL)
return NULL;
struct entry_list *tail = conf->entries;
while (!feof(file))
{
struct entry_list *list = calloc(1, sizeof(*list));
char *line = getaline(file);
if (line)
{
if (parse_line(list, line))
{
if (conf->entries == NULL)
{
conf->entries = list;
tail = list;
}
else
{
tail->next = list;
tail = list;
}
}
free(line);
}
}
fclose(file);
if (g_extern.verbose)
print_config(conf);
return conf;
}
void config_file_free(config_file_t *conf)
{
if (conf != NULL)
{
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);
}
}
bool config_get_double(config_file_t *conf, const char *key, double *in)
{
struct entry_list *list = conf->entries;
while (list != NULL)
{
if (strcmp(key, list->key) == 0)
{
*in = strtod(list->value, NULL);
return true;
}
list = list->next;
}
return false;
}
bool config_get_int(config_file_t *conf, const char *key, int *in)
{
struct entry_list *list = conf->entries;
while (list != NULL)
{
if (strcmp(key, list->key) == 0)
{
*in = strtol(list->value, NULL, 0);
return true;
}
list = list->next;
}
return false;
}
bool config_get_char(config_file_t *conf, const char *key, char *in)
{
struct entry_list *list = conf->entries;
while (list != NULL)
{
if (strcmp(key, list->key) == 0)
{
if (strlen(list->value) > 1)
return false;
*in = *list->value;
return true;
}
list = list->next;
}
return false;
}
bool config_get_string(config_file_t *conf, const char *key, char **str)
{
struct entry_list *list = conf->entries;
while (list != NULL)
{
if (strcmp(key, list->key) == 0)
{
*str = strdup(list->value);
return true;
}
list = list->next;
}
return false;
}
bool config_get_bool(config_file_t *conf, const char *key, bool *in)
{
struct entry_list *list = conf->entries;
while (list != NULL)
{
if (strcmp(key, list->key) == 0)
{
if (strcasecmp(list->value, "true") == 0)
*in = true;
else if (strcasecmp(list->value, "1") == 0)
*in = true;
else if (strcasecmp(list->value, "false") == 0)
*in = false;
else if (strcasecmp(list->value, "0") == 0)
*in = false;
else
return false;
return true;
}
list = list->next;
}
return false;
}

53
conf/config_file.h Normal file
View File

@ -0,0 +1,53 @@
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __CONFIG_FILE_H
#define __CONFIG_FILE_H
#include <stdint.h>
#include <stdbool.h>
typedef struct config_file config_file_t;
/////
// Config file format
// - # 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")
// Loads a config file. Returns NULL if file doesn't exist.
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.
// Extracts a double from config file.
bool config_get_double(config_file_t *conf, const char *entry, double *in);
// Extracts an int from config file.
bool config_get_int(config_file_t *conf, const char *entry, int *in);
// Extracts a single char. If value consists of several chars, this is an error.
bool config_get_char(config_file_t *conf, const char *entry, char *in);
// Extracts an allocated string in *in. This must be free()-d if this function succeeds.
bool config_get_string(config_file_t *conf, const char *entry, char **in);
// 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);
#endif

View File

@ -37,11 +37,11 @@
#define AUDIO_ALSA 3
#define AUDIO_ROAR 4
#define AUDIO_AL 5
#define AUDIO_JACK 6
////////////////////////
// Chooses which video and audio subsystem to use. Remember to update config.mk if you change these.
#define VIDEO_DRIVER VIDEO_GL
#define AUDIO_DRIVER AUDIO_ALSA
#define VIDEO_DEFAULT_DRIVER VIDEO_GL
#define AUDIO_DEFAULT_DRIVER AUDIO_ALSA
////////////////
@ -53,7 +53,7 @@ static const float xscale = 3.0; // Real x res = 296 * xscale
static const float yscale = 3.0; // Real y res = 224 * yscale
// Fullscreen
#define START_FULLSCREEN false; // To start in Fullscreen or not
static const bool fullscreen = false; // To start in Fullscreen or not
static const unsigned fullscreen_x = 1280;
static const unsigned fullscreen_y = 720;
@ -63,28 +63,9 @@ static const bool vsync = true;
// Smooths picture
static const bool video_smooth = true;
// Path to custom Cg shader. If using custom shaders, it is recommended to disable video_smooth.
#ifdef HAVE_CG
extern char cg_shader_path[];
#define DEFAULT_CG_SHADER "hqflt/cg/quad.cg"
#endif
// On resize and fullscreen, rendering area will stay 4:3
static const bool force_aspect = true;
/////////// Video filters (CPU based)
#define FILTER_NONE 0
#define FILTER_HQ2X 1
#define FILTER_HQ4X 2
#define FILTER_GRAYSCALE 3
#define FILTER_BLEED 4
#define FILTER_NTSC 5
////////////////////////
// If you change this to something other than FILTER_NONE, make sure that you build the filter module in config.mk.
#define VIDEO_FILTER FILTER_NONE
////////////////
// Audio
////////////////
@ -124,8 +105,6 @@ static const bool audio_sync = true;
#define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF)
#define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U)
#define AXIS_NEG_GET(x) ((x >> 16) & 0xFFFF)
#define AXIS_POS_GET(x) (x & 0xFFFF)
#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
// To figure out which joypad buttons to use, check jstest or similar.

View File

@ -1,13 +0,0 @@
BUILD_OPENGL = 1
BUILD_CG = 0
BUILD_FILTER = 0
BUILD_RSOUND = 0
BUILD_OSS = 0
BUILD_ALSA = 1
BUILD_ROAR = 0
BUILD_AL = 0
PREFIX = /usr/local

13
configure vendored Executable file
View File

@ -0,0 +1,13 @@
#!/bin/sh
echo ""
. qb/config.params.sh
parse_input "$@"
. qb/qb.comp.sh
. qb/config.libs.sh

151
driver.c
View File

@ -17,9 +17,74 @@
#include "driver.h"
#include "config.h"
#include "general.h"
#include <stdio.h>
#include <string.h>
#include "hqflt/filters.h"
#include "config.h"
static const audio_driver_t *audio_drivers[] = {
#ifdef HAVE_ALSA
&audio_alsa,
#endif
#ifdef HAVE_OSS
&audio_oss,
#endif
#ifdef HAVE_RSOUND
&audio_rsound,
#endif
#ifdef HAVE_AL
&audio_openal,
#endif
#ifdef HAVE_ROAR
&audio_roar,
#endif
#ifdef HAVE_JACK
&audio_jack,
#endif
};
static const video_driver_t *video_drivers[] = {
#ifdef HAVE_GLFW
&video_gl,
#endif
};
static void find_audio_driver(void)
{
for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
{
if (strcasecmp(g_settings.audio.driver, audio_drivers[i]->ident) == 0)
{
driver.audio = audio_drivers[i];
return;
}
}
SSNES_ERR("Couldn't find any audio driver named \"%s\"\n", g_settings.audio.driver);
fprintf(stderr, "Available audio drivers are:\n");
for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
fprintf(stderr, "\t%s\n", audio_drivers[i]->ident);
exit(1);
}
static void find_video_driver(void)
{
for (int i = 0; i < sizeof(video_drivers) / sizeof(video_driver_t*); i++)
{
if (strcasecmp(g_settings.video.driver, video_drivers[i]->ident) == 0)
{
driver.video = video_drivers[i];
return;
}
}
SSNES_ERR("Couldn't find any video driver named \"%s\"\n", g_settings.video.driver);
fprintf(stderr, "Available video drivers are:\n");
for (int i = 0; i < sizeof(video_drivers) / sizeof(video_driver_t*); i++)
fprintf(stderr, "\t%s\n", video_drivers[i]->ident);
exit(1);
}
void init_drivers(void)
{
@ -35,73 +100,76 @@ void uninit_drivers(void)
void init_audio(void)
{
if (!audio_enable)
if (!g_settings.audio.enable)
{
audio_active = false;
g_extern.audio_active = false;
return;
}
driver.audio_data = driver.audio->init(audio_device, out_rate, out_latency);
if ( driver.audio_data == NULL )
audio_active = false;
find_audio_driver();
if (!audio_sync && audio_active)
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 )
g_extern.audio_active = false;
if (!g_settings.audio.sync && g_extern.audio_active)
driver.audio->set_nonblock_state(driver.audio_data, true);
int err;
source = src_new(SAMPLERATE_QUALITY, 2, &err);
if (!source)
audio_active = false;
g_extern.source = src_new(g_settings.audio.src_quality, 2, &err);
if (!g_extern.source)
g_extern.audio_active = false;
}
void uninit_audio(void)
{
if (!audio_enable)
if (!g_settings.audio.enable)
{
audio_active = false;
g_extern.audio_active = false;
return;
}
if ( driver.audio_data && driver.audio )
driver.audio->free(driver.audio_data);
if ( source )
src_delete(source);
if ( g_extern.source )
src_delete(g_extern.source);
}
void init_video_input(void)
{
int scale;
int scale = 2;
find_video_driver();
// We multiply scales with 2 to allow for hi-res games.
#if VIDEO_FILTER == FILTER_NONE
scale = 2;
#elif VIDEO_FILTER == FILTER_HQ2X
scale = 4;
#elif VIDEO_FILTER == FILTER_HQ4X
scale = 8;
#elif VIDEO_FILTER == FILTER_NTSC
scale = 8;
#elif VIDEO_FILTER == FILTER_GRAYSCALE
scale = 2;
#elif VIDEO_FILTER == FILTER_BLEED
scale = 2;
#else
scale = 2;
#if HAVE_FILTER
switch (g_settings.video.filter)
{
case FILTER_HQ2X:
scale = 4;
break;
case FILTER_HQ4X:
case FILTER_NTSC:
scale = 8;
break;
default:
break;
}
#endif
video_info_t video = {
.width = (fullscreen) ? fullscreen_x : (296 * xscale),
.height = (fullscreen) ? fullscreen_y : (224 * yscale),
.fullscreen = fullscreen,
.vsync = vsync,
.force_aspect = force_aspect,
.smooth = video_smooth,
.width = (g_settings.video.fullscreen) ? g_settings.video.fullscreen_x : (296 * g_settings.video.xscale),
.height = (g_settings.video.fullscreen) ? g_settings.video.fullscreen_y : (224 * g_settings.video.yscale),
.fullscreen = g_settings.video.fullscreen,
.vsync = g_settings.video.vsync,
.force_aspect = g_settings.video.force_aspect,
.smooth = g_settings.video.smooth,
.input_scale = scale,
};
const input_driver_t *tmp = driver.input;
driver.video_data = driver.video->init(&video, &(driver.input));
driver.video_data = driver.video->init(&video, &driver.input);
if ( driver.video_data == NULL )
{
@ -139,9 +207,9 @@ void uninit_video_input(void)
driver.input->free(driver.input_data);
}
bool video_active = true;
bool audio_active = true;
driver_t driver;
#if 0
driver_t driver = {
#if VIDEO_DRIVER == VIDEO_GL
.video = &video_gl,
@ -149,12 +217,12 @@ driver_t driver = {
#error "Define a valid video driver in config.h"
#endif
#if AUDIO_DRIVER == AUDIO_RSOUND
#if AUDIO_DRIVER == AUDIO_ALSA
.audio = &audio_alsa,
#elif AUDIO_DRIVER == AUDIO_RSOUND
.audio = &audio_rsound,
#elif AUDIO_DRIVER == AUDIO_OSS
.audio = &audio_oss,
#elif AUDIO_DRIVER == AUDIO_ALSA
.audio = &audio_alsa,
#elif AUDIO_DRIVER == AUDIO_ROAR
.audio = &audio_roar,
#elif AUDIO_DRIVER == AUDIO_AL
@ -163,4 +231,5 @@ driver_t driver = {
#error "Define a valid audio driver in config.h"
#endif
};
#endif

View File

@ -54,14 +54,19 @@ typedef struct audio_driver
bool (*start)(void* data);
void (*set_nonblock_state)(void* data, bool toggle); // Should we care about blocking in audio thread? Fast forwarding.
void (*free)(void* data);
const char *ident;
} audio_driver_t;
#define AXIS_NEG_GET(x) ((x >> 16) & 0xFFFF)
#define AXIS_POS_GET(x) (x & 0xFFFF)
#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
typedef struct input_driver
{
void* (*init)(void);
void (*poll)(void* data);
int16_t (*input_state)(void* data, const struct snes_keybind **snes_keybinds, bool port, unsigned device, unsigned index, unsigned id);
void (*free)(void* data);
const char *ident;
} input_driver_t;
typedef struct video_driver
@ -71,6 +76,7 @@ typedef struct video_driver
bool (*frame)(void* data, const uint16_t* frame, int width, int height, int pitch);
void (*set_nonblock_state)(void* data, bool toggle); // Should we care about syncing to vblank? Fast forwarding.
void (*free)(void* data);
const char *ident;
} video_driver_t;
@ -92,8 +98,6 @@ void uninit_video_input(void);
void init_audio(void);
void uninit_audio(void);
extern bool video_active;
extern bool audio_active;
extern driver_t driver;
//////////////////////////////////////////////// Backends
@ -102,6 +106,7 @@ extern const audio_driver_t audio_oss;
extern const audio_driver_t audio_alsa;
extern const audio_driver_t audio_roar;
extern const audio_driver_t audio_openal;
extern const audio_driver_t audio_jack;
extern const video_driver_t video_gl;
////////////////////////////////////////////////

142
dynamic.c Normal file
View File

@ -0,0 +1,142 @@
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "dynamic.h"
#include "general.h"
#include <string.h>
#include "config.h"
#ifdef HAVE_DYNAMIC
#include <dlfcn.h>
#define SYM(x) do { \
p##x = dlsym(lib_handle, #x); \
if (p##x == NULL) { SSNES_ERR("Failed to load symbol: \"%s\"\n", #x); exit(1); } \
} while(0)
#endif
#ifdef HAVE_DYNAMIC
static void *lib_handle = NULL;
#endif
void (*psnes_init)(void);
void (*psnes_set_video_refresh)(snes_video_refresh_t);
void (*psnes_set_audio_sample)(snes_audio_sample_t);
void (*psnes_set_input_poll)(snes_input_poll_t);
void (*psnes_set_input_state)(snes_input_state_t);
void (*psnes_run)(void);
unsigned (*psnes_library_revision_minor)(void);
unsigned (*psnes_library_revision_major)(void);
bool (*psnes_load_cartridge_normal)(const char*, const uint8_t*, unsigned);
unsigned (*psnes_serialize_size)(void);
bool (*psnes_serialize)(uint8_t*, unsigned);
bool (*psnes_unserialize)(const uint8_t*, unsigned);
void (*psnes_set_cartridge_basename)(const char*);
uint8_t* (*psnes_get_memory_data)(unsigned);
unsigned (*psnes_get_memory_size)(unsigned);
void (*psnes_unload_cartridge)(void);
void (*psnes_term)(void);
#ifdef HAVE_DYNAMIC
static void load_dynamic(void)
{
SSNES_LOG("Loading dynamic libsnes from: \"%s\"\n", g_settings.libsnes);
lib_handle = dlopen(g_settings.libsnes, RTLD_LAZY);
if (!lib_handle)
{
SSNES_ERR("Failed to open dynamic library: \"%s\"\n", g_settings.libsnes);
exit(1);
}
SYM(snes_init);
SYM(snes_set_video_refresh);
SYM(snes_set_audio_sample);
SYM(snes_set_input_poll);
SYM(snes_set_input_state);
SYM(snes_library_revision_minor);
SYM(snes_library_revision_major);
SYM(snes_run);
SYM(snes_load_cartridge_normal);
SYM(snes_serialize_size);
SYM(snes_serialize);
SYM(snes_unserialize);
SYM(snes_set_cartridge_basename);
SYM(snes_get_memory_data);
SYM(snes_get_memory_size);
SYM(snes_unload_cartridge);
SYM(snes_term);
}
#endif
#define SSYM(x) do { \
p##x = x; \
} while(0)
#ifndef HAVE_DYNAMIC
static void set_statics(void)
{
SSYM(snes_init);
SSYM(snes_set_video_refresh);
SSYM(snes_set_audio_sample);
SSYM(snes_set_input_poll);
SSYM(snes_set_input_state);
SSYM(snes_library_revision_minor);
SSYM(snes_library_revision_major);
SSYM(snes_run);
SSYM(snes_load_cartridge_normal);
SSYM(snes_serialize_size);
SSYM(snes_serialize);
SSYM(snes_unserialize);
SSYM(snes_set_cartridge_basename);
SSYM(snes_get_memory_data);
SSYM(snes_get_memory_size);
SSYM(snes_unload_cartridge);
SSYM(snes_term);
}
#endif
void init_dlsym(void)
{
#ifdef HAVE_DYNAMIC
if (strlen(g_settings.libsnes) > 0)
load_dynamic();
else
{
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);
}
#else
set_statics();
#endif
}
void uninit_dlsym(void)
{
#ifdef HAVE_DYNAMIC
if (lib_handle)
dlclose(lib_handle);
#endif
}

54
dynamic.h Normal file
View File

@ -0,0 +1,54 @@
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __DYNAMIC_H
#define __DYNAMIC_H
#include <stdbool.h>
#include <libsnes.hpp>
void init_dlsym(void);
void uninit_dlsym(void);
extern void (*psnes_init)(void);
extern void (*psnes_set_video_refresh)(snes_video_refresh_t);
extern void (*psnes_set_audio_sample)(snes_audio_sample_t);
extern void (*psnes_set_input_poll)(snes_input_poll_t);
extern void (*psnes_set_input_state)(snes_input_state_t);
extern unsigned (*psnes_library_revision_minor)(void);
extern unsigned (*psnes_library_revision_major)(void);
extern bool (*psnes_load_cartridge_normal)(const char*, const uint8_t*, unsigned);
extern unsigned (*psnes_serialize_size)(void);
extern bool (*psnes_serialize)(uint8_t*, unsigned);
extern bool (*psnes_unserialize)(const uint8_t*, unsigned);
extern void (*psnes_run)(void);
extern void (*psnes_set_cartridge_basename)(const char*);
extern uint8_t* (*psnes_get_memory_data)(unsigned);
extern unsigned (*psnes_get_memory_size)(unsigned);
extern void (*psnes_unload_cartridge)(void);
extern void (*psnes_term)(void);
#endif

13
file.c
View File

@ -21,6 +21,7 @@
#include <stdbool.h>
#include <libsnes.hpp>
#include <string.h>
#include "dynamic.h"
ssize_t read_file(FILE* file, void** buf)
{
@ -101,7 +102,7 @@ void write_file(const char* path, uint8_t* data, size_t size)
if ( file != NULL )
{
SSNES_LOG("Saving state \"%s\". Size: %d bytes.\n", path, (int)size);
snes_serialize(data, size);
psnes_serialize(data, size);
if ( fwrite(data, 1, size, file) != size )
SSNES_ERR("Did not save state properly.\n");
fclose(file);
@ -118,7 +119,7 @@ void load_state(const char* path, uint8_t* data, size_t size)
if ( fread(data, 1, size, file) != size )
SSNES_ERR("Did not load state properly.\n");
fclose(file);
snes_unserialize(data, size);
psnes_unserialize(data, size);
}
else
{
@ -136,8 +137,8 @@ void load_save_file(const char* path, int type)
return;
}
size_t size = snes_get_memory_size(type);
uint8_t *data = snes_get_memory_data(type);
size_t size = psnes_get_memory_size(type);
uint8_t *data = psnes_get_memory_data(type);
if (size == 0 || !data)
{
@ -158,8 +159,8 @@ void load_save_file(const char* path, int type)
void save_file(const char* path, int type)
{
size_t size = snes_get_memory_size(type);
uint8_t *data = snes_get_memory_data(type);
size_t size = psnes_get_memory_size(type);
uint8_t *data = psnes_get_memory_data(type);
if ( data && size > 0 )
write_file(path, data, size);

View File

@ -21,9 +21,75 @@
#include <stdbool.h>
#include <samplerate.h>
#include "driver.h"
#include <stdio.h>
#define MAX_PLAYERS 2
#define MAX_BINDS 14
struct settings
{
struct
{
char driver[32];
float xscale;
float yscale;
bool fullscreen;
unsigned fullscreen_x;
unsigned fullscreen_y;
bool vsync;
bool smooth;
bool force_aspect;
char cg_shader_path[256];
unsigned filter;
} video;
struct
{
char driver[32];
bool enable;
unsigned out_rate;
unsigned in_rate;
char device[256];
unsigned latency;
bool sync;
int src_quality;
} audio;
struct
{
char driver[32];
struct snes_keybind binds[MAX_PLAYERS][MAX_BINDS];
int save_state_key;
int load_state_key;
int toggle_fullscreen_key;
int exit_emulator_key;
float axis_threshold;
} input;
char libsnes[256];
};
struct global
{
bool verbose;
SRC_STATE *source;
bool audio_active;
bool video_active;
FILE *rom_file;
char savefile_name_srm[256];
char config_path[256];
char basename[256];
};
void parse_config(void);
extern struct settings g_settings;
extern struct global g_extern;
#define SSNES_LOG(msg, args...) do { \
if (verbose) \
if (g_extern.verbose) \
fprintf(stderr, "SSNES: " msg, ##args); \
} while(0)
@ -31,8 +97,4 @@
fprintf(stderr, "SSNES [ERROR] :: " msg, ##args); \
} while(0)
extern bool verbose;
extern SRC_STATE *source;
extern bool fullscreen;
#endif

161
gfx/gl.c
View File

@ -18,13 +18,15 @@
#define GL_GLEXT_PROTOTYPES
#include "driver.h"
#include "config.h"
#include <GL/glfw.h>
#include <GL/glext.h>
#include <stdint.h>
#include "libsnes.hpp"
#include <stdio.h>
#include <sys/time.h>
#include <string.h>
#include "general.h"
#include "config.h"
#ifdef HAVE_CG
@ -49,6 +51,7 @@ static const GLfloat tex_coords[] = {
static bool keep_aspect = true;
#ifdef HAVE_CG
static CGparameter cg_mvp_matrix;
static bool cg_active = false;
#endif
static GLuint gl_width = 0, gl_height = 0;
typedef struct gl
@ -65,6 +68,11 @@ typedef struct gl
#endif
GLuint texture;
GLuint tex_filter;
unsigned last_width;
unsigned last_height;
unsigned tex_w, tex_h;
GLfloat tex_coords[8];
} gl_t;
@ -116,9 +124,9 @@ static bool glfw_is_pressed(int port_num, const struct snes_keybind *key, unsign
if (key->joyaxis != AXIS_NONE)
{
if (AXIS_NEG_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_NEG_GET(key->joyaxis)] <= -AXIS_THRESHOLD)
if (AXIS_NEG_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_NEG_GET(key->joyaxis)] <= -g_settings.input.axis_threshold)
return true;
if (AXIS_POS_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_POS_GET(key->joyaxis)] >= AXIS_THRESHOLD)
if (AXIS_POS_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_POS_GET(key->joyaxis)] >= g_settings.input.axis_threshold)
return true;
}
return false;
@ -168,7 +176,8 @@ static void glfw_free_input(void *data)
static const input_driver_t input_glfw = {
.poll = glfw_input_poll,
.input_state = glfw_input_state,
.free = glfw_free_input
.free = glfw_free_input,
.ident = "glfw"
};
static void GLFWCALL resize(int width, int height)
@ -207,7 +216,8 @@ static void GLFWCALL resize(int width, int height)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
#ifdef HAVE_CG
cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
if (cg_active)
cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
#endif
gl_width = out_width;
gl_height = out_height;
@ -251,18 +261,43 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i
glClear(GL_COLOR_BUFFER_BIT);
#if HAVE_CG
cgGLSetParameter2f(gl->cg_video_size, width, height);
cgGLSetParameter2f(gl->cg_texture_size, width, height);
cgGLSetParameter2f(gl->cg_output_size, gl_width, gl_height);
if (cg_active)
{
cgGLSetParameter2f(gl->cg_video_size, width, height);
cgGLSetParameter2f(gl->cg_texture_size, gl->tex_w, gl->tex_h);
cgGLSetParameter2f(gl->cg_output_size, gl_width, gl_height);
cgGLSetParameter2f(gl->cg_Vvideo_size, width, height);
cgGLSetParameter2f(gl->cg_Vtexture_size, width, height);
cgGLSetParameter2f(gl->cg_Voutput_size, gl_width, gl_height);
cgGLSetParameter2f(gl->cg_Vvideo_size, width, height);
cgGLSetParameter2f(gl->cg_Vtexture_size, gl->tex_w, gl->tex_h);
cgGLSetParameter2f(gl->cg_Voutput_size, gl_width, gl_height);
}
#endif
if (width != gl->last_width || height != gl->last_height) // res change. need to clear out texture.
{
gl->last_width = width;
gl->last_height = height;
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
uint8_t *tmp = calloc(1, gl->tex_w * gl->tex_h * sizeof(uint16_t));
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0, gl->tex_w, gl->tex_h, GL_BGRA,
GL_UNSIGNED_SHORT_1_5_5_5_REV, tmp);
free(tmp);
gl->tex_coords[0] = 0;
gl->tex_coords[1] = (GLfloat)height / gl->tex_h;
gl->tex_coords[2] = 0;
gl->tex_coords[3] = 0;
gl->tex_coords[4] = (GLfloat)width / gl->tex_w;
gl->tex_coords[5] = 0;
gl->tex_coords[6] = (GLfloat)width / gl->tex_w;
gl->tex_coords[7] = (GLfloat)height / gl->tex_h;
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch >> 1);
glTexImage2D(GL_TEXTURE_2D,
0, GL_RGBA, width, height, 0, GL_BGRA,
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0, width, height, GL_BGRA,
GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
glDrawArrays(GL_QUADS, 0, 4);
@ -276,7 +311,8 @@ static void gl_free(void *data)
{
gl_t *gl = data;
#ifdef HAVE_CG
cgDestroyContext(gl->cgCtx);
if (cg_active)
cgDestroyContext(gl->cgCtx);
#endif
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
@ -298,7 +334,7 @@ static void gl_set_nonblock_state(void *data, bool state)
static void* gl_init(video_info_t *video, const input_driver_t **input)
{
gl_t *gl = malloc(sizeof(gl_t));
gl_t *gl = calloc(1, sizeof(gl_t));
if ( gl == NULL )
return NULL;
@ -350,47 +386,65 @@ static void* gl_init(video_info_t *video, const input_driver_t **input)
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(3, GL_FLOAT, 3 * sizeof(GLfloat), vertexes);
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), tex_coords);
memcpy(gl->tex_coords, tex_coords, sizeof(tex_coords));
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords);
gl->tex_w = 256 * video->input_scale;
gl->tex_h = 256 * video->input_scale;
uint8_t *tmp = calloc(1, gl->tex_w * gl->tex_h * sizeof(uint16_t));
glTexImage2D(GL_TEXTURE_2D,
0, GL_RGBA, gl->tex_w, gl->tex_h, 0, GL_BGRA,
GL_UNSIGNED_SHORT_1_5_5_5_REV, tmp);
free(tmp);
gl->last_width = gl->tex_w;
gl->last_height = gl->tex_h;
#ifdef HAVE_CG
gl->cgCtx = cgCreateContext();
if (gl->cgCtx == NULL)
cg_active = false;
if (strlen(g_settings.video.cg_shader_path) > 0)
{
fprintf(stderr, "Failed to create Cg context\n");
goto error;
}
gl->cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT);
gl->cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX);
if (gl->cgFProf == CG_PROFILE_UNKNOWN || gl->cgVProf == CG_PROFILE_UNKNOWN)
{
fprintf(stderr, "Invalid profile type\n");
goto error;
}
cgGLSetOptimalOptions(gl->cgFProf);
cgGLSetOptimalOptions(gl->cgVProf);
gl->cgFPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, cg_shader_path, gl->cgFProf, "main_fragment", 0);
gl->cgVPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, cg_shader_path, gl->cgVProf, "main_vertex", 0);
if (gl->cgFPrg == NULL || gl->cgVPrg == NULL)
{
CGerror err = cgGetError();
fprintf(stderr, "CG error: %s\n", cgGetErrorString(err));
goto error;
}
cgGLLoadProgram(gl->cgFPrg);
cgGLLoadProgram(gl->cgVPrg);
cgGLEnableProfile(gl->cgFProf);
cgGLEnableProfile(gl->cgVProf);
cgGLBindProgram(gl->cgFPrg);
cgGLBindProgram(gl->cgVPrg);
SSNES_LOG("Loading Cg file: %s\n", g_settings.video.cg_shader_path);
gl->cgCtx = cgCreateContext();
if (gl->cgCtx == NULL)
{
fprintf(stderr, "Failed to create Cg context\n");
goto error;
}
gl->cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT);
gl->cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX);
if (gl->cgFProf == CG_PROFILE_UNKNOWN || gl->cgVProf == CG_PROFILE_UNKNOWN)
{
fprintf(stderr, "Invalid profile type\n");
goto error;
}
cgGLSetOptimalOptions(gl->cgFProf);
cgGLSetOptimalOptions(gl->cgVProf);
gl->cgFPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, g_settings.video.cg_shader_path, gl->cgFProf, "main_fragment", 0);
gl->cgVPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, g_settings.video.cg_shader_path, gl->cgVProf, "main_vertex", 0);
if (gl->cgFPrg == NULL || gl->cgVPrg == NULL)
{
CGerror err = cgGetError();
fprintf(stderr, "CG error: %s\n", cgGetErrorString(err));
goto error;
}
cgGLLoadProgram(gl->cgFPrg);
cgGLLoadProgram(gl->cgVPrg);
cgGLEnableProfile(gl->cgFProf);
cgGLEnableProfile(gl->cgVProf);
cgGLBindProgram(gl->cgFPrg);
cgGLBindProgram(gl->cgVPrg);
gl->cg_video_size = cgGetNamedParameter(gl->cgFPrg, "IN.video_size");
gl->cg_texture_size = cgGetNamedParameter(gl->cgFPrg, "IN.texture_size");
gl->cg_output_size = cgGetNamedParameter(gl->cgFPrg, "IN.output_size");
gl->cg_Vvideo_size = cgGetNamedParameter(gl->cgVPrg, "IN.video_size");
gl->cg_Vtexture_size = cgGetNamedParameter(gl->cgVPrg, "IN.texture_size");
gl->cg_Voutput_size = cgGetNamedParameter(gl->cgVPrg, "IN.output_size");
cg_mvp_matrix = cgGetNamedParameter(gl->cgVPrg, "modelViewProj");
cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
gl->cg_video_size = cgGetNamedParameter(gl->cgFPrg, "IN.video_size");
gl->cg_texture_size = cgGetNamedParameter(gl->cgFPrg, "IN.texture_size");
gl->cg_output_size = cgGetNamedParameter(gl->cgFPrg, "IN.output_size");
gl->cg_Vvideo_size = cgGetNamedParameter(gl->cgVPrg, "IN.video_size");
gl->cg_Vtexture_size = cgGetNamedParameter(gl->cgVPrg, "IN.texture_size");
gl->cg_Voutput_size = cgGetNamedParameter(gl->cgVPrg, "IN.output_size");
cg_mvp_matrix = cgGetNamedParameter(gl->cgVPrg, "modelViewProj");
cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
cg_active = true;
}
#endif
*input = &input_glfw;
@ -406,7 +460,8 @@ const video_driver_t video_gl = {
.init = gl_init,
.frame = gl_frame,
.set_nonblock_state = gl_set_nonblock_state,
.free = gl_free
.free = gl_free,
.ident = "glfw"
};

View File

@ -52,7 +52,7 @@ output main_fragment(float2 texCoord : TEXCOORD0, uniform sampler2D decal : TEXU
float2 rubyTextureSize = IN.texture_size;
float2 xy = barrelDistortion(texCoord.xy);
float2 one = 0.999/rubyTextureSize;
float2 one = 1.0/rubyTextureSize;
xy = xy + float2(0.0 , -0.5 * (phase + (1-phase) * rubyInputSize.y/rubyOutputSize.y) * one.y);
float4 texels[8];
texels[0] = TEX2D(xy + float2(-one.x,0.0));

43
hqflt/filters.h Normal file
View File

@ -0,0 +1,43 @@
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __FILTERS_H
#define __FILTERS_H
#include "config.h"
#ifdef HAVE_FILTER
#include "pastlib.h"
#include "grayscale.h"
#include "bleed.h"
#include "ntsc.h"
#define FILTER_HQ2X 1
#define FILTER_HQ4X 2
#define FILTER_GRAYSCALE 3
#define FILTER_BLEED 4
#define FILTER_NTSC 5
#define FILTER_HQ2X_STR "hq2x"
#define FILTER_HQ4X_STR "hq4x"
#define FILTER_GRAYSCALE_STR "grayscale"
#define FILTER_BLEED_STR "bleed"
#define FILTER_NTSC_STR "ntsc"
#endif
#endif

2
qb/conf.comp.sh Normal file
View File

@ -0,0 +1,2 @@
USE_LANG_C="yes"
USE_LANG_CXX="yes"

32
qb/config.libs.sh Normal file
View File

@ -0,0 +1,32 @@
. qb/qb.libs.sh
check_switch_c C99 -std=gnu99
check_critical C99 "Cannot find C99 compatible compiler."
if [ $HAVE_DYNAMIC != yes ]; then
check_lib_cxx SNES $LIBSNES snes_init -ldl
check_critical SNES "Cannot find libsnes."
add_define_make libsnes $LIBSNES
fi
check_lib ALSA -lasound snd_pcm_open
check_header OSS sys/soundcard.h
check_lib AL -lopenal alcOpenDevice
check_lib RSOUND -lrsound rsd_init
check_lib ROAR -lroar roar_vs_new
check_lib JACK -ljack jack_client_open
check_lib GLFW -lglfw glfwInit
check_critical GLFW "Cannot find GLFW library."
check_lib CG -lCg cgCreateContext
check_lib SRC -lsamplerate src_callback_new
check_lib DYNAMIC -ldl dlopen
# Creates config.mk.
VARS="ALSA OSS AL RSOUND ROAR JACK GLFW FILTER CG DYNAMIC"
create_config_make config.mk $VARS
create_config_header config.h $VARS

19
qb/config.params.sh Normal file
View File

@ -0,0 +1,19 @@
. qb/qb.params.sh
PACKAGE_NAME=ssnes
PACKAGE_VERSION=0.1
# 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_string LIBSNES "libsnes library used" "-lsnes"
add_command_line_enable FILTER "Disable CPU filter support" yes
add_command_line_enable CG "Enable CG shader support" auto
add_command_line_enable ALSA "Enable ALSA support" auto
add_command_line_enable OSS "Enable OSS support" auto
add_command_line_enable RSOUND "Enable RSound support" auto
add_command_line_enable ROAR "Enable RoarAudio support" auto
add_command_line_enable AL "Enable OpenAL support" auto
add_command_line_enable JACK "Enable JACK support" auto

68
qb/qb.comp.sh Normal file
View File

@ -0,0 +1,68 @@
. qb/conf.comp.sh
TEMP_C=.tmp.c
TEMP_CXX=.tmp.cxx
TEMP_EXE=.tmp
echo -n "Checking operating system ... "
OS="Win32" # whatever ;D
unamestr="`uname -o`"
if [ ! -z "`echo $unamestr | grep -i Linux`" ]; then
OS="Linux"
elif [ ! -z "`echo $unamestr | grep -i Darwin`" ]; then
OS="Darwin"
elif [ ! -z "`echo $unamestr | grep -i BSD`" ]; then
OS="BSD"
elif [ ! -z "`echo $unamestr | grep -i NT`" ]; then
OS="Cygwin"
fi
echo $OS
# Checking for working C compiler
if [ "$USE_LANG_C" = yes ]; then
echo "Checking for working C compiler ..."
if [ -z $CC ]; then
CC=`which gcc cc 2> /dev/null | grep ^/ | head -n 1`
fi
if [ -z $CC ]; then
echo "Could not find C compiler in path. Exiting ..."
exit 1
fi
echo -n "Checking if $CC is a suitable compiler ... "
answer=no
echo "#include <stdio.h>" > $TEMP_C
echo "int main(void) { puts(\"Hai world!\"); return 0; }" >> $TEMP_C
$CC -o $TEMP_EXE $TEMP_C 2>/dev/null >/dev/null && answer=yes
echo $answer
rm -rf $TEMP_C $TEMP_EXE
[ $answer = no ] && echo "Can't find suitable C compiler. Exiting ..." && exit 1
fi
# Checking for working C++ compiler
if [ "$USE_LANG_CXX" = "yes" ]; then
echo "Checking for working C++ compiler ..."
if [ -z $CXX ]; then
CXX=`which g++ c++ 2> /dev/null | grep ^/ | head -n 1`
fi
if [ -z $CXX ]; then
echo "Could not find C compiler in path. Exiting ..."
exit 1
fi
echo -n "Checking if $CXX is a suitable compiler ... "
answer=no
echo "#include <iostream>" > $TEMP_CXX
echo "int main() { std::cout << \"Hai guise\" << std::endl; return 0; }" >> $TEMP_CXX
$CXX -o $TEMP_EXE $TEMP_CXX 2>/dev/null >/dev/null && answer=yes
echo $answer
rm -rf $TEMP_CXX $TEMP_EXE
[ $answer = no ] && echo "Can't find suitable C++ compiler. Exiting ..." && exit 1
fi

313
qb/qb.libs.sh Normal file
View File

@ -0,0 +1,313 @@
PKG_CONF_PATH=""
PKG_CONF_USED=""
CONFIG_DEFINES=""
MAKEFILE_DEFINES=""
INCLUDE_DIRS=""
LIBRARY_DIRS=""
[ -z "$PREFIX" ] && PREFIX="/usr/local"
add_define_header()
{
CONFIG_DEFINES="$CONFIG_DEFINES:@$1@$2@:"
}
add_define_make()
{
MAKEFILE_DEFINES="$MAKEFILE_DEFINES:@$1@$2@:"
}
add_include_dirs()
{
while [ ! -z "$1" ]
do
INCLUDE_DIRS="$INCLUDE_DIRS -I$1"
shift
done
}
add_library_dirs()
{
while [ ! -z "$1" ]
do
LIBRARY_DIRS="$LIBRARY_DIRS -L$1"
shift
done
}
check_lib()
{
tmpval="HAVE_$1"
eval tmpval=\$$tmpval
[ "$tmpval" = "no" ] && return 0
echo -n "Checking function $3 in $2 ... "
echo "void $3(void); int main(void) { $3(); return 0; }" > $TEMP_C
eval HAVE_$1=no
answer=no
extralibs="$4"
$CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
echo $answer
rm -rf $TEMP_C $TEMP_EXE
if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
echo "Forced to build with library $2, but cannot locate. Exiting ..."
exit 1
fi
}
check_lib_cxx()
{
tmpval="HAVE_$1"
eval tmpval=\$$tmpval
[ "$tmpval" = "no" ] && return 0
echo -n "Checking function $3 in $2 ... "
echo "extern \"C\" { void $3(void); } int main() { $3(); }" > $TEMP_CXX
eval HAVE_$1=no
answer=no
extralibs="$4"
$CXX -o $TEMP_EXE $TEMP_CXX $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
echo $answer
rm -rf $TEMP_CXX $TEMP_EXE
if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
echo "Forced to build with library $2, but cannot locate. Exiting ..."
exit 1
fi
}
locate_pkg_conf()
{
echo -n "Checking for pkg-config ... "
PKG_CONF_PATH="`which pkg-config | grep ^/ | head -n1`"
if [ -z $PKG_CONF_PATH ]; then
echo "not found"
echo "Cannot locate pkg-config. Exiting ..."
exit 1
fi
echo "$PKG_CONF_PATH"
}
check_pkgconf()
{
[ -z "$PKG_CONF_PATH" ] && locate_pkg_conf
tmpval="HAVE_$1"
eval tmpval=\$$tmpval
[ "$tmpval" = "no" ] && return 0
echo -n "Checking presence of package $2 ... "
eval HAVE_$1=no
eval $1_CFLAGS=""
eval $1_LIBS=""
answer=no
minver=0.0
[ ! -z $3 ] && minver=$3
pkg-config --atleast-version=$minver --exists "$2" && eval HAVE_$1=yes && eval $1_CFLAGS='"`pkg-config $2 --cflags`"' && eval $1_LIBS='"`pkg-config $2 --libs`"' && answer=yes
echo $answer
PKG_CONF_USED="$PKG_CONF_USED $1"
if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
echo "Forced to build with package $2, but cannot locate. Exiting ..."
exit 1
fi
}
check_header()
{
tmpval="HAVE_$1"
eval tmpval=\$$tmpval
[ "$tmpval" = "no" ] && return 0
echo -n "Checking presence of header file $2 ... "
echo "#include<$2>" > $TEMP_C
echo "int main(void) { return 0; }" >> $TEMP_C
eval HAVE_$1=no
answer=no
$CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
echo $answer
rm -rf $TEMP_C $TEMP_EXE
if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
echo "Build assumed that $2 exists, but cannot locate. Exiting ..."
exit 1
fi
}
check_switch_c()
{
echo -n "Checking for availability of switch $2 in $CC ... "
if [ -z "$CC" ]; then
echo "No C compiler, cannot check ..."
exit 1
fi
echo "int main(void) { return 0; }" > $TEMP_C
eval HAVE_$1=no
answer=no
$CC -o $TEMP_EXE $TEMP_C $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
echo $answer
rm -rf $TEMP_C $TEMP_EXE
}
check_switch_cxx()
{
echo -n "Checking for availability of switch $2 in $CXX ... "
if [ -z "$CXX" ]; then
echo "No C++ compiler, cannot check ..."
exit 1
fi
echo "int main() { return 0; }" > $TEMP_CXX
eval HAVE_$1=no
answer=no
$CXX -o $TEMP_EXE $TEMP_CXX $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
echo $answer
rm -rf $TEMP_CXX $TEMP_EXE
}
check_critical()
{
val=HAVE_$1
eval val=\$$val
if [ "$val" != "yes" ]; then
echo "$2"
exit 1
fi
}
output_define_header()
{
arg1="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\1|'`"
arg2="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\2|'`"
echo "#define $arg1 $arg2" >> "$outfile"
}
create_config_header()
{
outfile="$1"
shift
echo "Creating config header: $outfile"
name="`echo __$outfile | sed 's|[\./]|_|g' | tr '[a-z]' '[A-Z]'`"
echo "#ifndef $name" > "$outfile"
echo "#define $name" >> "$outfile"
echo "" >> "$outfile"
echo "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >> "$outfile"
echo "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >> "$outfile"
while [ ! -z "$1" ]
do
tmpval="HAVE_$1"
eval tmpval=\$$tmpval
if [ "$tmpval" = "yes" ]; then
echo "#define HAVE_$1 1" >> "$outfile"
elif [ "$tmpval" = "no" ]; then
echo "/* #undef HAVE_$1 */" >> "$outfile"
fi
shift
done
echo "" >> "$outfile"
tmpdefs="$CONFIG_DEFINES"
while [ ! -z "$tmpdefs" ]
do
subdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):.*$|\1|'`"
tmpdefs="`echo $tmpdefs | sed 's|^\W*$||'`"
tmpdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):||'`"
output_define_header "$outfile" "$subdefs"
done
echo "#endif" >> "$outfile"
}
output_define_make()
{
arg1="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\1|'`"
arg2="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\2|'`"
echo "$arg1 = $arg2" >> "$outfile"
}
create_config_make()
{
outfile="$1"
shift
echo "Creating make config: $outfile"
rm -rf "$outfile"
touch "$outfile"
if [ "$USE_LANG_C" = "yes" ]; then
echo "CC = $CC" >> "$outfile"
echo "CFLAGS = $CFLAGS" >> "$outfile"
fi
if [ "$USE_LANG_CXX" = "yes" ]; then
echo "CXX = $CXX" >> "$outfile"
echo "CXXFLAGS = $CXXFLAGS" >> "$outfile"
fi
echo "LDFLAGS = $LDFLAGS" >> "$outfile"
echo "INCLUDE_DIRS = $INCLUDE_DIRS" >> "$outfile"
echo "LIBRARY_DIRS = $LIBRARY_DIRS" >> "$outfile"
echo "PACKAGE_NAME = $PACKAGE_NAME" >> "$outfile"
echo "PACKAGE_VERSION = $PACKAGE_VERSION" >> "$outfile"
echo "PREFIX = $PREFIX" >> "$outfile"
while [ ! -z "$1" ]
do
tmpval="HAVE_$1"
eval tmpval=\$$tmpval
if [ "$tmpval" = yes ]; then
echo "HAVE_$1 = 1" >> "$outfile"
elif [ "$tmpval" = no ]; then
echo "HAVE_$1 = 0" >> "$outfile"
fi
if [ ! -z "`echo $PKG_CONF_USED | grep $1`" ]; then
tmpval="$1_CFLAGS"
eval tmpval=\$$tmpval
echo "$1_CFLAGS = $tmpval" >> "$outfile"
tmpval="$1_LIBS"
eval tmpval=\$$tmpval
echo "$1_LIBS = $tmpval" >> "$outfile"
fi
shift
done
echo "" >> "$outfile"
tmpdefs="$MAKEFILE_DEFINES"
while [ ! -z "$tmpdefs" ]
do
subdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):.*$|\1|'`"
tmpdefs="`echo $tmpdefs | sed 's|^\W*$||'`"
tmpdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):||'`"
output_define_make "$outfile" "$subdefs"
done
}

150
qb/qb.params.sh Normal file
View File

@ -0,0 +1,150 @@
COMMAND_LINE_OPTS_ENABLE=""
add_command_line_enable()
{
COMMAND_LINE_OPTS_ENABLE="$COMMAND_LINE_OPTS_ENABLE:\"$1\" \"$2\" \"$3\":"
eval HAVE_$1=$3
}
add_command_line_string()
{
COMMAND_LINE_OPTS_STRINGS="$COMMAND_LINE_OPTS_STRINGS:\"$1\" \"$2\" \"$3\":"
eval $1=$3
}
## lvl. 43 regex dragon awaits thee.
print_help()
{
echo "===================="
echo " Quickbuild script"
echo "===================="
echo "Package: $PACKAGE_NAME"
echo "Version: $PACKAGE_VERSION"
echo ""
echo "General environment variables:"
echo "CC: C compiler"
echo "CFLAGS: C compiler flags"
echo "CXX: C++ compiler"
echo "CXXFLAGS: C++ compiler flags"
echo "LDFLAGS: Linker flags"
echo ""
echo "General options:"
echo "--prefix=\$path: Install path prefix"
echo "--help: Show this help"
echo ""
echo "Custom options:"
tmpopts="$COMMAND_LINE_OPTS_ENABLE"
while [ ! -z "$tmpopts" ]
do
subopts="`echo $tmpopts | sed 's|^:"\([^"]*\)"."\([^"]*\)"."\([^"]*\)":.*$|"\1":"\2":"\3"|'`"
tmpopts="`echo $tmpopts | sed 's|^\W*$||'`"
tmpopts="`echo $tmpopts | sed 's|^:"[^"]*"."[^"]*"."[^"]*":||'`"
print_sub_opt "$subopts"
done
echo ""
tmpopts="$COMMAND_LINE_OPTS_STRINGS"
while [ ! -z "$tmpopts" ]
do
subopts="`echo $tmpopts | sed 's|^:"\([^"]*\)"."\([^"]*\)"."\([^"]*\)":.*$|"\1":"\2":"\3"|'`"
tmpopts="`echo $tmpopts | sed 's|^\W*$||'`"
tmpopts="`echo $tmpopts | sed 's|^:"[^"]*"."[^"]*"."[^"]*":||'`"
print_sub_str_opt "$subopts"
done
}
print_sub_opt()
{
arg1="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\1|'`"
arg2="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\2|'`"
arg3="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\3|'`"
lowertext="`echo $arg1 | tr '[A-Z]' '[a-z]'`"
if [ "$arg3" = "auto" ]; then
echo -n "--enable-$lowertext: "
echo $arg2
echo "--disable-$lowertext"
elif [ "$arg3" = "yes" ]; then
echo "--disable-$lowertext: $arg2"
elif [ "$arg3" = "no" ]; then
echo "--enable-$lowertext: $arg2"
fi
}
print_sub_str_opt()
{
arg1="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\1|'`"
arg2="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\2|'`"
arg3="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\3|'`"
lowertext="`echo $arg1 | tr '[A-Z]' '[a-z]'`"
echo "--with-$lowertext: $arg2 (Defaults: $arg3)"
}
parse_input()
{
### Parse stuff :V
while [ ! -z "$1" ]
do
case "$1" in
--prefix=*)
prefix="`echo $1 | sed -e 's|^--prefix=\(\S\S*\)$|\1|' -e 's|\(\S\S*\)/$|\1|'`"
if [ "$prefix" != "$1" ]; then
PREFIX="$prefix"
fi
;;
--enable-*)
enable=`echo $1 | sed 's|^--enable-||'`
if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i $enable`" ]; then
print_help
exit 1
fi
eval HAVE_`echo $enable | tr '[a-z]' '[A-Z]'`=yes
;;
--disable-*)
disable=`echo $1 | sed 's|^--disable-||'`
if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i $disable`" ]; then
print_help
exit 1
fi
eval HAVE_`echo $disable | tr '[a-z]' '[A-Z]'`=no
;;
--with-*)
arg="`echo $1 | sed 's|^--with-\S\S*=||'`"
with=`echo $1 | sed 's|^--with-\(\S\S*\)=.*$|\1|'`
if [ -z "`echo $COMMAND_LINE_OPTS_STRINGS | grep -i $with`" ]; then
print_help
exit 1
fi
eval "`echo $with | tr '[a-z]' '[A-Z]'`=\"$arg\""
;;
-h|--help)
print_help
exit 0
;;
*)
print_help
exit 1
;;
esac
shift
done
}

461
settings.c Normal file
View File

@ -0,0 +1,461 @@
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "general.h"
#include "conf/config_file.h"
#include "config.def.h"
#include <assert.h>
#include <string.h>
#include "hqflt/filters.h"
#include "config.h"
#include <ctype.h>
struct settings g_settings;
static void read_keybinds(config_file_t *conf);
static void set_defaults(void)
{
const char *def_video = NULL;
const char *def_audio = NULL;
switch (VIDEO_DEFAULT_DRIVER)
{
case VIDEO_GL:
def_video = "glfw";
break;
default:
break;
}
switch (AUDIO_DEFAULT_DRIVER)
{
case AUDIO_RSOUND:
def_audio = "rsound";
break;
case AUDIO_OSS:
def_audio = "oss";
break;
case AUDIO_ALSA:
def_audio = "alsa";
break;
case AUDIO_ROAR:
def_audio = "roar";
break;
case AUDIO_AL:
def_audio = "openal";
break;
default:
break;
}
// No input atm ... It is in the GLFW driver.
if (def_video)
strncpy(g_settings.video.driver, def_video, sizeof(g_settings.video.driver) - 1);
if (def_audio)
strncpy(g_settings.audio.driver, def_audio, sizeof(g_settings.audio.driver) - 1);
g_settings.video.xscale = xscale;
g_settings.video.yscale = yscale;
g_settings.video.fullscreen = fullscreen;
g_settings.video.fullscreen_x = fullscreen_x;
g_settings.video.fullscreen_y = fullscreen_y;
g_settings.video.vsync = vsync;
g_settings.video.smooth = video_smooth;
g_settings.video.force_aspect = force_aspect;
g_settings.audio.enable = audio_enable;
g_settings.audio.out_rate = out_rate;
g_settings.audio.in_rate = in_rate;
if (audio_device)
strncpy(g_settings.audio.device, audio_device, sizeof(g_settings.audio.device));
g_settings.audio.latency = out_latency;
g_settings.audio.sync = audio_sync;
g_settings.audio.src_quality = SAMPLERATE_QUALITY;
assert(sizeof(g_settings.input.binds[0]) >= sizeof(snes_keybinds_1));
assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_2));
memcpy(g_settings.input.binds[0], snes_keybinds_1, sizeof(snes_keybinds_1));
memcpy(g_settings.input.binds[1], snes_keybinds_2, sizeof(snes_keybinds_2));
g_settings.input.save_state_key = SAVE_STATE_KEY;
g_settings.input.load_state_key = LOAD_STATE_KEY;
g_settings.input.toggle_fullscreen_key = TOGGLE_FULLSCREEN;
g_settings.input.axis_threshold = AXIS_THRESHOLD;
g_settings.input.exit_emulator_key = GLFW_KEY_ESC;
}
void parse_config(void)
{
memset(&g_settings, 0, sizeof(struct settings));
config_file_t *conf = NULL;
if (strlen(g_extern.config_path) > 0)
{
conf = config_file_new(g_extern.config_path);
if (!conf)
{
SSNES_ERR("Couldn't find config at path: \"%s\"\n", g_extern.config_path);
exit(1);
}
}
else
{
const char *xdg = getenv("XDG_CONFIG_HOME");
const char *home = getenv("HOME");
if (xdg)
{
char conf_path[strlen(xdg) + strlen("/ssnes/ssnes.cfg ")];
strcpy(conf_path, xdg);
strcat(conf_path, "/ssnes/ssnes.cfg");
conf = config_file_new(conf_path);
}
else if (home)
{
char conf_path[strlen(home) + strlen("/.ssnesrc ")];
strcpy(conf_path, xdg);
strcat(conf_path, "/.ssnesrc");
conf = config_file_new(conf_path);
}
// Try this as a last chance...
if (!conf)
conf = config_file_new("/etc/ssnes.cfg");
}
set_defaults();
if (conf == NULL)
return;
int tmp_int;
double tmp_double;
bool tmp_bool;
char *tmp_str;
// Video settings.
if (config_get_double(conf, "video_xscale", &tmp_double))
g_settings.video.xscale = tmp_double;
if (config_get_double(conf, "video_yscale", &tmp_double))
g_settings.video.yscale = tmp_double;
if (config_get_int(conf, "video_fullscreen_x", &tmp_int))
g_settings.video.fullscreen_x = tmp_int;
if (config_get_int(conf, "video_fullscreen_y", &tmp_int))
g_settings.video.fullscreen_y = tmp_int;
if (config_get_bool(conf, "video_fullscreen", &tmp_bool))
g_settings.video.fullscreen = tmp_bool;
if (config_get_bool(conf, "video_vsync", &tmp_bool))
g_settings.video.vsync = tmp_bool;
if (config_get_bool(conf, "video_smooth", &tmp_bool))
g_settings.video.smooth = tmp_bool;
if (config_get_bool(conf, "video_force_aspect", &tmp_bool))
g_settings.video.force_aspect = tmp_bool;
if (config_get_string(conf, "video_cg_shader", &tmp_str))
{
strncpy(g_settings.video.cg_shader_path, tmp_str, sizeof(g_settings.video.cg_shader_path) - 1);
free(tmp_str);
}
#ifdef HAVE_FILTER
if (config_get_string(conf, "video_filter", &tmp_str))
{
unsigned filter = 0;
if (strcasecmp(FILTER_HQ2X_STR, tmp_str) == 0)
filter = FILTER_HQ2X;
else if (strcasecmp(FILTER_HQ4X_STR, tmp_str) == 0)
filter = FILTER_HQ4X;
else if (strcasecmp(FILTER_GRAYSCALE_STR, tmp_str) == 0)
filter = FILTER_GRAYSCALE;
else if (strcasecmp(FILTER_BLEED_STR, tmp_str) == 0)
filter = FILTER_BLEED;
else if (strcasecmp(FILTER_NTSC_STR, tmp_str) == 0)
filter = FILTER_NTSC;
else
{
SSNES_ERR(
"Invalid filter... Valid filters are:\n"
"\t%s\n"
"\t%s\n"
"\t%s\n"
"\t%s\n"
"\t%s\n",
FILTER_HQ2X_STR, FILTER_HQ4X_STR, FILTER_GRAYSCALE_STR,
FILTER_BLEED_STR, FILTER_NTSC_STR);
exit(1);
}
free(tmp_str);
g_settings.video.filter = filter;
}
#endif
// Input Settings.
if (config_get_double(conf, "input_axis_threshold", &tmp_double))
g_settings.input.axis_threshold = tmp_double;
// Audio settings.
if (config_get_bool(conf, "audio_enable", &tmp_bool))
g_settings.audio.enable = tmp_bool;
if (config_get_int(conf, "audio_out_rate", &tmp_int))
g_settings.audio.out_rate = tmp_int;
if (config_get_int(conf, "audio_in_rate", &tmp_int))
g_settings.audio.in_rate = tmp_int;
if (config_get_string(conf, "audio_device", &tmp_str))
{
strncpy(g_settings.audio.device, tmp_str, sizeof(g_settings.audio.device) - 1);
free(tmp_str);
}
if (config_get_int(conf, "audio_latency", &tmp_int))
g_settings.audio.latency = tmp_int;
if (config_get_bool(conf, "audio_sync", &tmp_bool))
g_settings.audio.sync = tmp_bool;
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];
}
if (config_get_string(conf, "video_driver", &tmp_str))
{
strncpy(g_settings.video.driver, tmp_str, sizeof(g_settings.video.driver) - 1);
free(tmp_str);
}
if (config_get_string(conf, "audio_driver", &tmp_str))
{
strncpy(g_settings.audio.driver, tmp_str, sizeof(g_settings.audio.driver) - 1);
free(tmp_str);
}
if (config_get_string(conf, "libsnes_path", &tmp_str))
{
strncpy(g_settings.libsnes, tmp_str, sizeof(g_settings.libsnes) - 1);
free(tmp_str);
}
read_keybinds(conf);
// TODO: Keybinds.
config_file_free(conf);
}
struct bind_map
{
const char *key;
const char *btn;
const char *axis;
int snes_key;
};
// Big and nasty bind map... :)
static const struct bind_map bind_maps[2][13] = {
{
{ "input_player1_a", "input_player1_a_btn", NULL, SNES_DEVICE_ID_JOYPAD_A },
{ "input_player1_b", "input_player1_b_btn", NULL, SNES_DEVICE_ID_JOYPAD_B },
{ "input_player1_y", "input_player1_y_btn", NULL, SNES_DEVICE_ID_JOYPAD_Y },
{ "input_player1_x", "input_player1_x_btn", NULL, SNES_DEVICE_ID_JOYPAD_X },
{ "input_player1_start", "input_player1_start_btn", NULL, SNES_DEVICE_ID_JOYPAD_START },
{ "input_player1_select", "input_player1_select_btn", NULL, SNES_DEVICE_ID_JOYPAD_SELECT },
{ "input_player1_l", "input_player1_l_btn", NULL, SNES_DEVICE_ID_JOYPAD_L },
{ "input_player1_r", "input_player1_r_btn", NULL, SNES_DEVICE_ID_JOYPAD_R },
{ "input_player1_left", "input_player1_left_btn", "input_player1_left_axis", SNES_DEVICE_ID_JOYPAD_LEFT },
{ "input_player1_right", "input_player1_right_btn", "input_player1_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT },
{ "input_player1_up", "input_player1_up_btn", "input_player1_up_axis", SNES_DEVICE_ID_JOYPAD_UP },
{ "input_player1_down", "input_player1_down_btn", "input_player1_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN },
{ "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SNES_FAST_FORWARD_KEY }
},
{
{ "input_player2_a", "input_player2_a_btn", NULL, SNES_DEVICE_ID_JOYPAD_A },
{ "input_player2_b", "input_player2_b_btn", NULL, SNES_DEVICE_ID_JOYPAD_B },
{ "input_player2_y", "input_player2_y_btn", NULL, SNES_DEVICE_ID_JOYPAD_Y },
{ "input_player2_x", "input_player2_x_btn", NULL, SNES_DEVICE_ID_JOYPAD_X },
{ "input_player2_start", "input_player2_start_btn", NULL, SNES_DEVICE_ID_JOYPAD_START },
{ "input_player2_select", "input_player2_select_btn", NULL, SNES_DEVICE_ID_JOYPAD_SELECT },
{ "input_player2_l", "input_player2_l_btn", NULL, SNES_DEVICE_ID_JOYPAD_L },
{ "input_player2_r", "input_player2_r_btn", NULL, SNES_DEVICE_ID_JOYPAD_R },
{ "input_player2_left", "input_player2_left_btn", "input_player2_left_axis", SNES_DEVICE_ID_JOYPAD_LEFT },
{ "input_player2_right", "input_player2_right_btn", "input_player2_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT },
{ "input_player2_up", "input_player2_up_btn", "input_player2_up_axis", SNES_DEVICE_ID_JOYPAD_UP },
{ "input_player2_down", "input_player2_down_btn", "input_player2_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN },
{ "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SNES_FAST_FORWARD_KEY }
}
};
struct glfw_map
{
const char *str;
int key;
};
// Edit: Not portable to different input systems atm. Might move this map into the driver itself or something.
static const struct glfw_map glfw_map[] = {
{ "left", GLFW_KEY_LEFT },
{ "right", GLFW_KEY_RIGHT },
{ "up", GLFW_KEY_UP },
{ "down", GLFW_KEY_DOWN },
{ "enter", GLFW_KEY_ENTER },
{ "tab", GLFW_KEY_TAB },
{ "insert", GLFW_KEY_INSERT },
{ "del", GLFW_KEY_DEL },
{ "rshift", GLFW_KEY_RSHIFT },
{ "shift", GLFW_KEY_LSHIFT },
{ "ctrl", GLFW_KEY_LCTRL },
{ "alt", GLFW_KEY_LALT },
{ "space", GLFW_KEY_SPACE },
{ "escape", GLFW_KEY_ESC },
{ "f1", GLFW_KEY_F1 },
{ "f2", GLFW_KEY_F2 },
{ "f3", GLFW_KEY_F3 },
{ "f4", GLFW_KEY_F4 },
{ "f5", GLFW_KEY_F5 },
{ "f6", GLFW_KEY_F6 },
{ "f7", GLFW_KEY_F7 },
{ "f8", GLFW_KEY_F8 },
{ "f9", GLFW_KEY_F9 },
{ "f10", GLFW_KEY_F10 },
{ "f11", GLFW_KEY_F11 },
{ "f12", GLFW_KEY_F12 },
};
static struct snes_keybind *find_snes_bind(unsigned port, int id)
{
struct snes_keybind *binds = g_settings.input.binds[port];
for (int i = 0; binds[i].id != -1; i++)
{
if (id == binds[i].id)
return &binds[i];
}
return NULL;
}
static int find_glfw_bind(const char *str)
{
for (int i = 0; i < sizeof(glfw_map)/sizeof(struct glfw_map); i++)
{
if (strcasecmp(glfw_map[i].str, str) == 0)
return glfw_map[i].key;
}
return -1;
}
static int find_glfw_key(const char *str)
{
// If the bind is a normal key-press ...
if (strlen(str) == 1 && isalpha(*str))
return toupper(*str);
else // Check if we have a special mapping for it.
return find_glfw_bind(str);
}
static void read_keybinds(config_file_t *conf)
{
char *tmp_key = NULL;
int tmp_btn;
char *tmp_axis = NULL;
for (int j = 0; j < 1; j++)
{
for (int i = 0; i < sizeof(bind_maps[j])/sizeof(struct bind_map); i++)
{
struct snes_keybind *bind = find_snes_bind(j, bind_maps[j][i].snes_key);
if (!bind)
continue;
if (bind_maps[j][i].key && config_get_string(conf, bind_maps[j][i].key, &tmp_key))
{
int key = find_glfw_key(tmp_key);
if (key >= 0)
bind->key = key;
free(tmp_key);
tmp_key = NULL;
}
if (bind_maps[j][i].btn && config_get_int(conf, bind_maps[j][i].btn, &tmp_btn))
{
if (tmp_btn >= 0)
bind->joykey = tmp_btn;
}
if (bind_maps[j][i].axis && config_get_string(conf, bind_maps[j][i].axis, &tmp_axis))
{
if (strlen(tmp_axis) >= 2 && (*tmp_axis == '+' || *tmp_axis == '-'))
{
int axis = strtol(tmp_axis + 1, NULL, 0);
if (*tmp_axis == '+')
bind->joyaxis = AXIS_POS(axis);
else
bind->joyaxis = AXIS_NEG(axis);
}
free(tmp_axis);
tmp_axis = NULL;
}
}
}
char *tmp_str;
if (config_get_string(conf, "input_toggle_fullscreen", &tmp_str))
{
int key = find_glfw_key(tmp_str);
if (key >= 0)
g_settings.input.toggle_fullscreen_key = key;
free(tmp_str);
}
if (config_get_string(conf, "input_save_state", &tmp_str))
{
int key = find_glfw_key(tmp_str);
if (key >= 0)
g_settings.input.save_state_key = key;
free(tmp_str);
}
if (config_get_string(conf, "input_load_state", &tmp_str))
{
int key = find_glfw_key(tmp_str);
if (key >= 0)
g_settings.input.load_state_key = key;
free(tmp_str);
}
if (config_get_string(conf, "input_exit_emulator", &tmp_str))
{
int key = find_glfw_key(tmp_str);
if (key >= 0)
g_settings.input.exit_emulator_key = key;
free(tmp_str);
}
}

208
ssnes.c
View File

@ -24,15 +24,16 @@
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include "config.h"
#include "driver.h"
#include "file.h"
#include "hqflt/pastlib.h"
#include "hqflt/grayscale.h"
#include "hqflt/bleed.h"
#include "hqflt/ntsc.h"
#include "hqflt/filters.h"
#include "general.h"
#include "dynamic.h"
struct global g_extern = {
.video_active = true,
.audio_active = true,
};
// To avoid continous switching if we hold the button down, we require that the button must go from pressed, unpressed back to pressed to be able to toggle between then.
@ -46,10 +47,10 @@ void set_fast_forward_button(bool new_button_state)
if (new_button_state && !old_button_state)
{
syncing_state = !syncing_state;
if (video_active)
if (g_extern.video_active)
driver.video->set_nonblock_state(driver.video_data, syncing_state);
if (audio_active)
driver.audio->set_nonblock_state(driver.audio_data, (audio_sync) ? syncing_state : true);
if (g_extern.audio_active)
driver.audio->set_nonblock_state(driver.audio_data, (g_settings.audio.sync) ? syncing_state : true);
if (syncing_state)
audio_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING;
else
@ -58,7 +59,7 @@ void set_fast_forward_button(bool new_button_state)
old_button_state = new_button_state;
}
#if VIDEO_FILTER != FILTER_NONE
#ifdef HAVE_FILTER
static inline void process_frame (uint16_t * restrict out, const uint16_t * restrict in, unsigned width, unsigned height)
{
int pitch = 1024;
@ -79,53 +80,54 @@ static inline void process_frame (uint16_t * restrict out, const uint16_t * rest
// Format received is 16-bit 0RRRRRGGGGGBBBBB
static void video_frame(const uint16_t *data, unsigned width, unsigned height)
{
if ( !video_active )
if ( !g_extern.video_active )
return;
#if VIDEO_FILTER == FILTER_HQ2X
uint16_t outputHQ2x[width * height * 2 * 2];
#elif VIDEO_FILTER == FILTER_HQ4X
uint16_t outputHQ4x[width * height * 4 * 4];
#elif VIDEO_FILTER == FILTER_NTSC
uint16_t output_ntsc[SNES_NTSC_OUT_WIDTH(width) * height];
#endif
#if VIDEO_FILTER != FILTER_NONE
#ifdef HAVE_FILTER
uint16_t output_filter[width * height * 4 * 4];
uint16_t output[width * height];
process_frame(output, data, width, height);
#endif
#if VIDEO_FILTER == FILTER_HQ2X
ProcessHQ2x(output, outputHQ2x);
if ( !driver.video->frame(driver.video_data, outputHQ2x, width << 1, height << 1, width << 2) )
video_active = false;
#elif VIDEO_FILTER == FILTER_HQ4X
ProcessHQ4x(output, outputHQ4x);
if ( !driver.video->frame(driver.video_data, outputHQ4x, width << 2, height << 2, width << 3) )
video_active = false;
#elif VIDEO_FILTER == FILTER_GRAYSCALE
grayscale_filter(output, width, height);
if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) )
video_active = false;
#elif VIDEO_FILTER == FILTER_BLEED
bleed_filter(output, width, height);
if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) )
video_active = false;
#elif VIDEO_FILTER == FILTER_NTSC
ntsc_filter(output_ntsc, output, width, height);
if ( !driver.video->frame(driver.video_data, output_ntsc, SNES_NTSC_OUT_WIDTH(width), height, SNES_NTSC_OUT_WIDTH(width) << 1) )
video_active = false;
switch (g_settings.video.filter)
{
case FILTER_HQ2X:
ProcessHQ2x(output, output_filter);
if ( !driver.video->frame(driver.video_data, output_filter, width << 1, height << 1, width << 2) )
g_extern.video_active = false;
break;
case FILTER_HQ4X:
ProcessHQ4x(output, output_filter);
if ( !driver.video->frame(driver.video_data, output_filter, width << 2, height << 2, width << 3) )
g_extern.video_active = false;
break;
case FILTER_GRAYSCALE:
grayscale_filter(output, width, height);
if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) )
g_extern.video_active = false;
break;
case FILTER_BLEED:
bleed_filter(output, width, height);
if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) )
g_extern.video_active = false;
break;
case FILTER_NTSC:
ntsc_filter(output_filter, output, width, height);
if ( !driver.video->frame(driver.video_data, output_filter, SNES_NTSC_OUT_WIDTH(width), height, SNES_NTSC_OUT_WIDTH(width) << 1) )
g_extern.video_active = false;
break;
default:
if ( !driver.video->frame(driver.video_data, data, width, height, (height == 448 || height == 478) ? 1024 : 2048) )
g_extern.video_active = false;
}
#else
if ( !driver.video->frame(driver.video_data, data, width, height, (height == 448 || height == 478) ? 1024 : 2048) )
video_active = false;
g_extern.video_active = false;
#endif
}
SRC_STATE* source = NULL;
static void audio_sample(uint16_t left, uint16_t right)
{
if ( !audio_active )
if ( !g_extern.audio_active )
return;
static float data[AUDIO_CHUNK_SIZE_NONBLOCKING];
@ -146,16 +148,16 @@ static void audio_sample(uint16_t left, uint16_t right)
src_data.input_frames = audio_chunk_size / 2;
src_data.output_frames = audio_chunk_size * 8;
src_data.end_of_input = 0;
src_data.src_ratio = (double)out_rate / (double)in_rate;
src_data.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate;
src_process(source, &src_data);
src_process(g_extern.source, &src_data);
src_float_to_short_array(outsamples, temp_outsamples, src_data.output_frames_gen * 2);
if ( driver.audio->write(driver.audio_data, temp_outsamples, src_data.output_frames_gen * 4) < 0 )
{
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
audio_active = false;
g_extern.audio_active = false;
}
data_ptr = 0;
@ -169,7 +171,7 @@ static void input_poll(void)
static int16_t input_state(bool port, unsigned device, unsigned index, unsigned id)
{
const struct snes_keybind *binds[] = { snes_keybinds_1, snes_keybinds_2 };
const struct snes_keybind *binds[] = { g_settings.input.binds[0], g_settings.input.binds[1] };
return driver.input->input_state(driver.input_data, binds, port, device, index, id);
}
@ -193,20 +195,10 @@ static void print_help(void)
puts("Usage: ssnes [rom file] [-h/--help | -s/--save]");
puts("\t-h/--help: Show this help message");
puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin");
#ifdef HAVE_CG
puts("\t-f/--shader: Path to Cg shader. Will be compiled at runtime.\n");
#endif
puts("\t-c/--config: Path for config file. Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg");
puts("\t-v/--verbose: Verbose logging");
}
bool fullscreen = START_FULLSCREEN;
static FILE* rom_file = NULL;
static char savefile_name_srm[256] = {0};
bool verbose = false;
#ifdef HAVE_CG
char cg_shader_path[256] = DEFAULT_CG_SHADER;
#endif
static void parse_input(int argc, char *argv[])
{
if (argc < 2)
@ -219,18 +211,12 @@ static void parse_input(int argc, char *argv[])
{ "help", 0, NULL, 'h' },
{ "save", 1, NULL, 's' },
{ "verbose", 0, NULL, 'v' },
#ifdef HAVE_CG
{ "shader", 1, NULL, 'f' },
#endif
{ "config", 0, NULL, 'c' },
{ NULL, 0, NULL, 0 }
};
int option_index = 0;
#ifdef HAVE_CG
char optstring[] = "hs:vf:";
#else
char optstring[] = "hs:v";
#endif
char optstring[] = "hs:vc:";
for(;;)
{
int c = getopt_long(argc, argv, optstring, opts, &option_index);
@ -245,18 +231,16 @@ static void parse_input(int argc, char *argv[])
exit(0);
case 's':
strncpy(savefile_name_srm, optarg, sizeof(savefile_name_srm));
savefile_name_srm[sizeof(savefile_name_srm)-1] = '\0';
strncpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm));
g_extern.savefile_name_srm[sizeof(g_extern.savefile_name_srm)-1] = '\0';
break;
#ifdef HAVE_CG
case 'f':
strncpy(cg_shader_path, optarg, sizeof(cg_shader_path) - 1);
break;
#endif
case 'v':
verbose = true;
g_extern.verbose = true;
break;
case 'c':
strncpy(g_extern.config_path, optarg, sizeof(g_extern.config_path) - 1);
break;
case '?':
@ -275,24 +259,20 @@ static void parse_input(int argc, char *argv[])
strcpy(tmp, argv[optind]);
char *dst = strrchr(tmp, '.');
if (dst)
{
*dst = '\0';
snes_set_cartridge_basename(tmp);
}
else
snes_set_cartridge_basename(tmp);
strncpy(g_extern.basename, tmp, sizeof(g_extern.basename) - 1);
SSNES_LOG("Opening file: \"%s\"\n", argv[optind]);
rom_file = fopen(argv[optind], "rb");
if (rom_file == NULL)
g_extern.rom_file = fopen(argv[optind], "rb");
if (g_extern.rom_file == NULL)
{
SSNES_ERR("Could not open file: \"%s\"\n", optarg);
exit(1);
}
if (strlen(savefile_name_srm) == 0)
fill_pathname(savefile_name_srm, argv[optind], ".srm");
if (strlen(g_extern.savefile_name_srm) == 0)
fill_pathname(g_extern.savefile_name_srm, argv[optind], ".srm");
}
else if (strlen(savefile_name_srm) == 0)
else if (strlen(g_extern.savefile_name_srm) == 0)
{
SSNES_ERR("Need savefile argument when reading rom from stdin.\n");
print_help();
@ -302,35 +282,41 @@ static void parse_input(int argc, char *argv[])
int main(int argc, char *argv[])
{
snes_init();
parse_input(argc, argv);
parse_config();
init_dlsym();
psnes_init();
if (strlen(g_extern.basename) > 0)
psnes_set_cartridge_basename(g_extern.basename);
SSNES_LOG("Version of libsnes API: %u.%u\n", psnes_library_revision_major(), psnes_library_revision_minor());
void *rom_buf;
ssize_t rom_len = 0;
if ((rom_len = read_file(rom_file, &rom_buf)) == -1)
if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
{
SSNES_ERR("Could not read ROM file.\n");
exit(1);
}
SSNES_LOG("ROM size: %zi bytes\n", rom_len);
if (rom_file != NULL)
fclose(rom_file);
if (g_extern.rom_file != NULL)
fclose(g_extern.rom_file);
char statefile_name[strlen(savefile_name_srm)+strlen(".state")+1];
char savefile_name_rtc[strlen(savefile_name_srm)+strlen(".rtc")+1];
char statefile_name[strlen(g_extern.savefile_name_srm)+strlen(".state")+1];
char savefile_name_rtc[strlen(g_extern.savefile_name_srm)+strlen(".rtc")+1];
fill_pathname(statefile_name, argv[1], ".state");
fill_pathname(savefile_name_rtc, argv[1], ".rtc");
init_drivers();
snes_set_video_refresh(video_frame);
snes_set_audio_sample(audio_sample);
snes_set_input_poll(input_poll);
snes_set_input_state(input_state);
psnes_set_video_refresh(video_frame);
psnes_set_audio_sample(audio_sample);
psnes_set_input_poll(input_poll);
psnes_set_input_state(input_state);
if (!snes_load_cartridge_normal(NULL, rom_buf, rom_len))
if (!psnes_load_cartridge_normal(NULL, rom_buf, rom_len))
{
SSNES_ERR("ROM file is not valid!\n");
goto error;
@ -338,7 +324,7 @@ int main(int argc, char *argv[])
free(rom_buf);
unsigned serial_size = snes_serialize_size();
unsigned serial_size = psnes_serialize_size();
uint8_t *serial_data = malloc(serial_size);
if (serial_data == NULL)
{
@ -346,49 +332,51 @@ int main(int argc, char *argv[])
goto error;
}
load_save_file(savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
load_save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
///// TODO: Modular friendly!!!
for(;;)
{
bool quitting = glfwGetKey(GLFW_KEY_ESC) || !glfwGetWindowParam(GLFW_OPENED);
bool quitting = glfwGetKey(g_settings.input.exit_emulator_key) || !glfwGetWindowParam(GLFW_OPENED);
if ( quitting )
break;
if ( glfwGetKey( SAVE_STATE_KEY ))
if ( glfwGetKey( g_settings.input.save_state_key ))
{
write_file(statefile_name, serial_data, serial_size);
}
else if ( glfwGetKey( LOAD_STATE_KEY ) )
else if ( glfwGetKey( g_settings.input.load_state_key ) )
load_state(statefile_name, serial_data, serial_size);
else if ( glfwGetKey( TOGGLE_FULLSCREEN ) )
else if ( glfwGetKey( g_settings.input.toggle_fullscreen_key ) )
{
fullscreen = !fullscreen;
g_settings.video.fullscreen = !g_settings.video.fullscreen;
uninit_drivers();
init_drivers();
}
snes_run();
psnes_run();
}
save_file(savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
snes_unload_cartridge();
snes_term();
psnes_unload_cartridge();
psnes_term();
uninit_drivers();
free(serial_data);
uninit_dlsym();
return 0;
error:
snes_unload_cartridge();
snes_term();
psnes_unload_cartridge();
psnes_term();
uninit_drivers();
uninit_dlsym();
return 1;
}

148
ssnes.cfg Normal file
View File

@ -0,0 +1,148 @@
##### Config file for SSNES
## If enabled, load libsnes from a dynamic location.
# libsnes_path = "/path/to/libsnes.so"
#### Video
# Windowed xscale and yscale (Real x res: 296 * xscale, real y scale: 224 * xscale)
# video_xscale = 3.0
# video_yscale = 3.0
# Fullscreen resolution
# video_fullscreen_x = 1280
# video_fullscreen_y = 720
# Start in fullscreen. Can be changed at runtime.
# video_fullscreen = false
# Video vsync.
# video_vsync = true
# Smoothens picture with bilinear filtering. Should be disabled if using Cg shaders.
# video_smooth = true
# Forces rendering area to stay 4:3.
# video_force_aspect = true
# Path to Cg shader. If enabled
# video_cg_shader = "/path/to/cg/shader.cg"
# CPU-based filter. Valid ones are: hq2x, hq4x, grayscale, bleed, ntsc.
# video_filter = ntsc
#### Audio
# Enable audio.
# audio_enable = true
# Audio output samplerate.
# audio_out_rate = 48000
# Audio input samplerate from libsnes.
# Lower this (slightly) if you are experiencing frequent audio dropouts while vsync is enabled.
# Conversely, increase this slightly if you are experiencing good audio,
# but lots of dropped frames. Reasonable values for this is 32000 +/- 100 Hz.
# audio_in_rate = 31950
# Audio driver backend. Depending on configuration possible candidates are: alsa, oss, rsound, roar, openal
# audio_driver = alsa
# Override the default audio device the audio_driver uses.
# audio_device =
# Will sync (block) on audio. Recommended.
# audio_sync = true
# 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.
# audio_src_quality =
### Input
# Defines axis threshold. Possible values are [0.0, 1.0]
# input_axis_threshold = 0.6
# Keyboard input. Will recognize normal keypresses and special keys like "left", "right", and so on.
# input_player1_a = x
# input_player1_b = z
# input_player1_y = a
# input_player1_x = s
# input_player1_start = enter
# input_player1_select = rshift
# input_player1_l = q
# input_player1_r = w
# input_player1_left = left
# input_player1_right = right
# input_player1_up = up
# input_player1_down = down
# Joypad buttons. Figure these out by looking at jstest /dev/input/js0 output.
# input_player1_a_btn = 1
# input_player1_b_btn = 0
# input_player1_y_btn = 2
# input_player1_x_btn = 3
# input_player1_start_btn = 7
# input_player1_select_btn = 6
# input_player1_l_btn = 4
# input_player1_r_btn = 5
# input_player1_left_btn = 11
# input_player1_right_btn = 12
# input_player1_up_btn = 13
# input_player1_down_btn = 14
# Axis for DPAD. Needs to be either '+' or '-' in the first character signaling either positive or negative direction of the axis, then the axis number.
# input_player1_left_axis = -0
# input_player1_right_axis = +0
# input_player1_up_axis = +1
# input_player1_down_axis = -1
# Same stuff, just for player two.
# input_player2_a =
# input_player2_b =
# input_player2_y =
# input_player2_x =
# input_player2_start =
# input_player2_select =
# input_player2_l =
# input_player2_r =
# input_player2_left =
# input_player2_right =
# input_player2_up =
# input_player2_down =
# input_player2_a_btn = 1
# input_player2_b_btn = 0
# input_player2_y_btn = 2
# input_player2_x_btn = 3
# input_player2_start_btn = 7
# input_player2_select_btn = 6
# input_player2_l_btn = 4
# input_player2_r_btn = 5
# input_player2_left_btn = 11
# input_player2_right_btn = 12
# input_player2_up_btn = 13
# input_player2_down_btn = 14
# input_player2_left_axis = -0
# input_player2_right_axis = +0
# input_player2_up_axis = +1
# input_player2_down_axis = -1
# Toggles fullscreen.
# input_toggle_fullscreen = f
# Saves state.
# input_save_state = f2
# Loads state.
# input_load_state = f4
# Toggles between fast-forwarding and normal speed.
# input_toggle_fast_forward = space
# Same, just mapping to a joypad button.
# input_toggle_fast_forward_btn = 10
# Key to exit emulator cleanly.
# input_exit_emulator = escape