mirror of
https://github.com/libretro/RetroArch
synced 2025-01-28 14:54:03 +00:00
Merge git://github.com/Themaister/SSNES
This commit is contained in:
commit
b971957f41
36
Makefile
36
Makefile
@ -4,12 +4,19 @@ TARGET = ssnes
|
||||
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o
|
||||
|
||||
LIBS = -lsamplerate
|
||||
LIBS =
|
||||
DEFINES = -DHAVE_CONFIG_H
|
||||
|
||||
ifeq ($(HAVE_SRC), 1)
|
||||
LIBS += $(SRC_LIBS)
|
||||
DEFINES += $(SRC_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
LIBS += -lrsound
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_OSS), 1)
|
||||
OBJ += audio/oss.o
|
||||
endif
|
||||
@ -30,15 +37,23 @@ ifeq ($(HAVE_JACK),1)
|
||||
LIBS += -ljack
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_GLFW), 1)
|
||||
OBJ += gfx/gl.o
|
||||
LIBS += -lglfw
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
|
||||
LIBS += $(SDL_LIBS) -lGL
|
||||
DEFINES += $(SDL_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CG), 1)
|
||||
OBJ += gfx/shader_cg.o
|
||||
LIBS += -lCg -lCgGL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o
|
||||
LIBS += $(XML_LIBS)
|
||||
DEFINES += $(XML_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FILTER), 1)
|
||||
OBJ += hqflt/hq.o
|
||||
OBJ += hqflt/grayscale.o
|
||||
@ -47,6 +62,12 @@ ifeq ($(HAVE_FILTER), 1)
|
||||
OBJ += hqflt/snes_ntsc/snes_ntsc.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FFMPEG), 1)
|
||||
OBJ += record/ffemu.o
|
||||
LIBS += $(AVCODEC_LIBS) $(AVCORE_LIBS) $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS)
|
||||
DEFINES += $(AVCODEC_CFLAGS) $(AVCORE_CFLAGS) $(AVFORMAT_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYNAMIC), 1)
|
||||
LIBS += -ldl
|
||||
else
|
||||
@ -62,13 +83,13 @@ config.mk: configure qb/*
|
||||
@exit 1
|
||||
|
||||
ssnes: $(OBJ)
|
||||
$(CXX) -o $@ $(OBJ) $(LIBS) $(CFLAGS)
|
||||
$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
|
||||
|
||||
%.o: %.c config.h config.mk
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
|
||||
|
||||
install: $(TARGET)
|
||||
install -m755 $(TARGET) $(DESTDIR)/$(PREFIX)/bin
|
||||
install -m755 $(TARGET) $(DESTDIR)$(PREFIX)/bin
|
||||
install -m644 ssnes.cfg $(DESTDIR)/etc/ssnes.cfg
|
||||
|
||||
uninstall: $(TARGET)
|
||||
@ -79,6 +100,7 @@ clean:
|
||||
rm -f audio/*.o
|
||||
rm -f conf/*.o
|
||||
rm -f gfx/*.o
|
||||
rm -f record/*.o
|
||||
rm -f hqflt/*.o
|
||||
rm -f hqflt/snes_ntsc/*.o
|
||||
rm -f $(TARGET)
|
||||
|
59
Makefile.win32
Normal file
59
Makefile.win32
Normal file
@ -0,0 +1,59 @@
|
||||
TARGET = ssnes.exe
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o
|
||||
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
|
||||
HAVE_SRC = 1
|
||||
HAVE_SDL = 1
|
||||
HAVE_XML = 1
|
||||
libsnes ?= -lsnes
|
||||
|
||||
LIBS =
|
||||
DEFINES = -I.
|
||||
LDFLAGS = -L. -static-libgcc
|
||||
|
||||
SRC_LIBS = -lsamplerate-0
|
||||
SDL_LIBS = -lSDLmain -lSDL
|
||||
SDL_CFLAGS = -ISDL
|
||||
|
||||
ifeq ($(HAVE_SRC), 1)
|
||||
LIBS += $(SRC_LIBS)
|
||||
DEFINES += $(SRC_CFLAGS) -DHAVE_SRC
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
|
||||
LIBS += $(SDL_LIBS) -lopengl32
|
||||
DEFINES += $(SDL_CFLAGS) -DHAVE_SDL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o
|
||||
DEFINES += $(XML_CFLAGS) -DHAVE_XML
|
||||
LIBS += -lxml2
|
||||
endif
|
||||
|
||||
LIBS += $(libsnes)
|
||||
|
||||
CFLAGS = -Wall -O3 -g -std=gnu99 -I.
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f audio/*.o
|
||||
rm -f conf/*.o
|
||||
rm -f gfx/*.o
|
||||
rm -f record/*.o
|
||||
rm -f hqflt/*.o
|
||||
rm -f hqflt/snes_ntsc/*.o
|
||||
rm -f $(TARGET)
|
||||
|
||||
.PHONY: all install uninstall clean
|
12
README.md
12
README.md
@ -17,12 +17,13 @@ It is used through command-line.
|
||||
SSNES requires these libraries to build:
|
||||
|
||||
- [libsnes](http://byuu.org/bsnes/)
|
||||
- GLFW
|
||||
- SDL
|
||||
- libsamplerate
|
||||
|
||||
SSNES can utilize these libraries if enabled:
|
||||
|
||||
- nvidia-cg-toolkit
|
||||
- libxml2 (bSNES XML shaders)
|
||||
|
||||
SSNES needs one of these audio driver libraries:
|
||||
|
||||
@ -67,13 +68,14 @@ Do note that these two options are mutually exclusive.
|
||||
|
||||
|
||||
|
||||
# Filters and Cg shader support
|
||||
# Filters, bSNES XML shaders and Cg shader support
|
||||
|
||||
This is not strictly not necessary for an emulator, but it can be enabled if desired.
|
||||
For best performance, Cg shaders are recommended as they do not eat up valuable CPU time.
|
||||
Cg shaders are compiled at run-time, and shaders could be dropped in.
|
||||
For best performance, Cg shaders or bSNES XML shaders are recommended as they do not eat up valuable CPU time (assuming your GPU can handle the shaders).
|
||||
Cg shaders and XML shaders (GLSL) are compiled at run-time.
|
||||
All shaders share a common interface to pass some essential arguments such as texture size and viewport size. (Common for pixel art scalers)
|
||||
Some Cg shaders are included in hqflt/cg/ and could be used as an example.
|
||||
bSNES XML shaders can be found on various places on the net. Best place to start looking are the bSNES forums.
|
||||
|
||||
While these shaders are Cg, they closely resemble the GLSL shaders found in bSNES shader pack, so porting them is trivial.
|
||||
The Cg shaders closely resemble the GLSL shaders found in bSNES shader pack, so porting them is trivial if desired.
|
||||
|
||||
|
121
audio/buffer.c
Normal file
121
audio/buffer.c
Normal file
@ -0,0 +1,121 @@
|
||||
/* RSound - A PCM audio client/server
|
||||
* Copyright (C) 2010 - Hans-Kristian Arntzen
|
||||
*
|
||||
* RSound is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RSound is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RSound.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
struct rsound_fifo_buffer
|
||||
{
|
||||
char *buffer;
|
||||
size_t bufsize;
|
||||
size_t first;
|
||||
size_t end;
|
||||
};
|
||||
|
||||
rsound_fifo_buffer_t* rsnd_fifo_new(size_t size)
|
||||
{
|
||||
rsound_fifo_buffer_t *buf = calloc(1, sizeof(*buf));
|
||||
if (buf == NULL)
|
||||
return NULL;
|
||||
|
||||
buf->buffer = calloc(1, size + 1);
|
||||
if (buf->buffer == NULL)
|
||||
{
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
buf->bufsize = size + 1;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
void rsnd_fifo_free(rsound_fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
|
||||
free(buffer->buffer);
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
|
||||
size_t first = buffer->first;
|
||||
size_t end = buffer->end;
|
||||
if (end < first)
|
||||
end += buffer->bufsize;
|
||||
return end - first;
|
||||
}
|
||||
|
||||
size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
|
||||
size_t first = buffer->first;
|
||||
size_t end = buffer->end;
|
||||
if (end < first)
|
||||
end += buffer->bufsize;
|
||||
|
||||
return (buffer->bufsize - 1) - (end - first);
|
||||
}
|
||||
|
||||
void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t size)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
assert(in_buf);
|
||||
assert(rsnd_fifo_write_avail(buffer) >= size);
|
||||
|
||||
size_t first_write = size;
|
||||
size_t rest_write = 0;
|
||||
if (buffer->end + size > buffer->bufsize)
|
||||
{
|
||||
first_write = buffer->bufsize - buffer->end;
|
||||
rest_write = size - first_write;
|
||||
}
|
||||
|
||||
memcpy(buffer->buffer + buffer->end, in_buf, first_write);
|
||||
if (rest_write > 0)
|
||||
memcpy(buffer->buffer, (const char*)in_buf + first_write, rest_write);
|
||||
|
||||
buffer->end = (buffer->end + size) % buffer->bufsize;
|
||||
}
|
||||
|
||||
|
||||
void rsnd_fifo_read(rsound_fifo_buffer_t* buffer, void* in_buf, size_t size)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
assert(in_buf);
|
||||
assert(rsnd_fifo_read_avail(buffer) >= size);
|
||||
|
||||
size_t first_read = size;
|
||||
size_t rest_read = 0;
|
||||
if (buffer->first + size > buffer->bufsize)
|
||||
{
|
||||
first_read = buffer->bufsize - buffer->first;
|
||||
rest_read = size - first_read;
|
||||
}
|
||||
|
||||
memcpy(in_buf, (const char*)buffer->buffer + buffer->first, first_read);
|
||||
if (rest_read > 0)
|
||||
memcpy((char*)in_buf + first_read, buffer->buffer, rest_read);
|
||||
|
||||
buffer->first = (buffer->first + size) % buffer->bufsize;
|
||||
}
|
||||
|
36
audio/buffer.h
Normal file
36
audio/buffer.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* RSound - A PCM audio client/server
|
||||
* Copyright (C) 2010 - Hans-Kristian Arntzen
|
||||
*
|
||||
* RSound is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RSound is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RSound.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __BUFFER_H
|
||||
#define __BUFFER_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef RSD_FIFO_BUF_TYPEDEF
|
||||
#define RSD_FIFO_BUF_TYPEDEF
|
||||
typedef struct rsound_fifo_buffer rsound_fifo_buffer_t;
|
||||
#endif
|
||||
|
||||
rsound_fifo_buffer_t* rsnd_fifo_new(size_t size);
|
||||
void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t size);
|
||||
void rsnd_fifo_read(rsound_fifo_buffer_t* buffer, void* in_buf, size_t size);
|
||||
void rsnd_fifo_free(rsound_fifo_buffer_t* buffer);
|
||||
size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer);
|
||||
size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer);
|
||||
|
||||
#endif
|
217
audio/sdl.c
Normal file
217
audio/sdl.c
Normal file
@ -0,0 +1,217 @@
|
||||
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "driver.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_audio.h"
|
||||
#include "SDL_thread.h"
|
||||
#include "general.h"
|
||||
#include "buffer.h"
|
||||
|
||||
typedef struct sdl_audio
|
||||
{
|
||||
bool nonblock;
|
||||
|
||||
SDL_mutex *lock;
|
||||
SDL_cond *cond;
|
||||
rsound_fifo_buffer_t *buffer;
|
||||
} sdl_audio_t;
|
||||
|
||||
static void sdl_audio_cb(void *data, Uint8 *stream, int len)
|
||||
{
|
||||
sdl_audio_t *sdl = data;
|
||||
|
||||
size_t avail = rsnd_fifo_read_avail(sdl->buffer);
|
||||
size_t write_size = len > avail ? avail : len;
|
||||
rsnd_fifo_read(sdl->buffer, stream, write_size);
|
||||
SDL_CondSignal(sdl->cond);
|
||||
|
||||
// If underrun, fill rest with silence.
|
||||
memset(stream + write_size, 0, len - write_size);
|
||||
}
|
||||
|
||||
// Interesting hack from http://www-graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
static inline uint32_t next_pow2(uint32_t v)
|
||||
{
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline int find_num_frames(int rate, int latency)
|
||||
{
|
||||
int frames = (rate * latency) / 1000;
|
||||
// SDL only likes 2^n sized buffers.
|
||||
return next_pow2(frames);
|
||||
}
|
||||
|
||||
static void* sdl_audio_init(const char* device, int rate, int latency)
|
||||
{
|
||||
(void)device;
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
|
||||
return NULL;
|
||||
|
||||
sdl_audio_t *sdl = calloc(1, sizeof(*sdl));
|
||||
if (!sdl)
|
||||
return NULL;
|
||||
|
||||
// We have to buffer up some data ourselves, so we let SDL carry approx half of the latency. SDL double buffers audio and we do as well.
|
||||
int frames = find_num_frames(rate, latency / 4);
|
||||
|
||||
SDL_AudioSpec spec = {
|
||||
.freq = rate,
|
||||
.format = AUDIO_S16SYS,
|
||||
.channels = 2,
|
||||
.samples = frames, // This is in audio frames, not samples ... :(
|
||||
.callback = sdl_audio_cb,
|
||||
.userdata = sdl
|
||||
};
|
||||
|
||||
SDL_AudioSpec out;
|
||||
|
||||
if (SDL_OpenAudio(&spec, &out) < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to open SDL audio: %s\n", SDL_GetError());
|
||||
free(sdl);
|
||||
return 0;
|
||||
}
|
||||
g_settings.audio.out_rate = out.freq;
|
||||
|
||||
sdl->lock = SDL_CreateMutex();
|
||||
sdl->cond = SDL_CreateCond();
|
||||
|
||||
SSNES_LOG("SDL audio: Requested %d ms latency, got %d ms\n", latency, (int)(out.samples * 4 * 1000 / g_settings.audio.out_rate));
|
||||
|
||||
// Create a buffer twice as big as needed and prefill the buffer.
|
||||
size_t bufsize = out.samples * 4 * sizeof(int16_t);
|
||||
void *tmp = calloc(1, bufsize);
|
||||
sdl->buffer = rsnd_fifo_new(bufsize);
|
||||
if (tmp)
|
||||
{
|
||||
rsnd_fifo_write(sdl->buffer, tmp, bufsize);
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
SDL_PauseAudio(0);
|
||||
return sdl;
|
||||
}
|
||||
|
||||
static ssize_t sdl_audio_write(void* data, const void* buf, size_t size)
|
||||
{
|
||||
sdl_audio_t *sdl = data;
|
||||
|
||||
ssize_t ret = 0;
|
||||
if (sdl->nonblock)
|
||||
{
|
||||
SDL_LockAudio();
|
||||
size_t avail = rsnd_fifo_write_avail(sdl->buffer);
|
||||
size_t write_amt = avail > size ? size : avail;
|
||||
rsnd_fifo_write(sdl->buffer, buf, write_amt);
|
||||
SDL_UnlockAudio();
|
||||
ret = write_amt;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t written = 0;
|
||||
while (written < size)
|
||||
{
|
||||
SDL_LockAudio();
|
||||
size_t avail = rsnd_fifo_write_avail(sdl->buffer);
|
||||
|
||||
if (avail == 0)
|
||||
{
|
||||
SDL_UnlockAudio();
|
||||
SDL_mutexP(sdl->lock);
|
||||
SDL_CondWait(sdl->cond, sdl->lock);
|
||||
SDL_mutexV(sdl->lock);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t write_amt = size - written > avail ? avail : size - written;
|
||||
rsnd_fifo_write(sdl->buffer, (const char*)buf + written, write_amt);
|
||||
SDL_UnlockAudio();
|
||||
written += write_amt;
|
||||
}
|
||||
}
|
||||
ret = written;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool sdl_audio_stop(void *data)
|
||||
{
|
||||
(void)data;
|
||||
SDL_PauseAudio(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sdl_audio_start(void *data)
|
||||
{
|
||||
(void)data;
|
||||
SDL_PauseAudio(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void sdl_audio_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
sdl_audio_t *sdl = data;
|
||||
sdl->nonblock = state;
|
||||
}
|
||||
|
||||
static void sdl_audio_free(void *data)
|
||||
{
|
||||
SDL_CloseAudio();
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
|
||||
sdl_audio_t *sdl = data;
|
||||
if (sdl)
|
||||
{
|
||||
rsnd_fifo_free(sdl->buffer);
|
||||
SDL_DestroyMutex(sdl->lock);
|
||||
SDL_DestroyCond(sdl->cond);
|
||||
}
|
||||
free(sdl);
|
||||
}
|
||||
|
||||
const audio_driver_t audio_sdl = {
|
||||
.init = sdl_audio_init,
|
||||
.write = sdl_audio_write,
|
||||
.stop = sdl_audio_stop,
|
||||
.start = sdl_audio_start,
|
||||
.set_nonblock_state = sdl_audio_set_nonblock_state,
|
||||
.free = sdl_audio_free,
|
||||
.ident = "sdl"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -132,7 +132,7 @@ static void print_config(config_file_t *conf)
|
||||
struct entry_list *tmp = conf->entries;
|
||||
while (tmp != NULL)
|
||||
{
|
||||
printf("Key: \"%s\", Value: \"%s\"\n", tmp->key, tmp->value);
|
||||
SSNES_LOG("Config => Key: \"%s\", Value: \"%s\"\n", tmp->key, tmp->value);
|
||||
tmp = tmp->next;
|
||||
}
|
||||
}
|
||||
|
103
config.def.h
103
config.def.h
@ -19,14 +19,28 @@
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef __CONFIG_H
|
||||
#define __CONFIG_H
|
||||
#ifndef __CONFIG_DEF_H
|
||||
#define __CONFIG_DEF_H
|
||||
|
||||
#include <GL/glfw.h>
|
||||
#include <stdbool.h>
|
||||
#include "libsnes.hpp"
|
||||
#include "driver.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
#include <SDL/SDL.h>
|
||||
#else
|
||||
#error HAVE_SDL is not defined!
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
#else
|
||||
#error HAVE_SRC is not defined!
|
||||
#endif
|
||||
|
||||
|
||||
///////////////// Drivers
|
||||
@ -38,10 +52,30 @@
|
||||
#define AUDIO_ROAR 4
|
||||
#define AUDIO_AL 5
|
||||
#define AUDIO_JACK 6
|
||||
#define AUDIO_SDL 8
|
||||
////////////////////////
|
||||
#define INPUT_SDL 7
|
||||
////////////////////////
|
||||
|
||||
#define VIDEO_DEFAULT_DRIVER VIDEO_GL
|
||||
|
||||
#if HAVE_ALSA
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_ALSA
|
||||
#elif HAVE_OSS
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_OSS
|
||||
#elif HAVE_JACK
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_JACK
|
||||
#elif HAVE_RSOUND
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_RSOUND
|
||||
#elif HAVE_ROAR
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_ROAR
|
||||
#elif HAVE_AL
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_AL
|
||||
#elif HAVE_SDL
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_SDL
|
||||
#endif
|
||||
|
||||
#define INPUT_DEFAULT_DRIVER INPUT_SDL
|
||||
|
||||
|
||||
////////////////
|
||||
@ -66,6 +100,8 @@ static const bool video_smooth = true;
|
||||
// On resize and fullscreen, rendering area will stay 4:3
|
||||
static const bool force_aspect = true;
|
||||
|
||||
#define SNES_ASPECT_RATIO (4.0/3)
|
||||
|
||||
////////////////
|
||||
// Audio
|
||||
////////////////
|
||||
@ -78,7 +114,7 @@ static const unsigned out_rate = 48000;
|
||||
|
||||
// Input samplerate from libSNES.
|
||||
// Lower this (slightly) if you are experiencing frequent audio dropouts while vsync is enabled.
|
||||
static const unsigned in_rate = 31950;
|
||||
static const unsigned in_rate = 31980;
|
||||
|
||||
// Audio device (e.g. hw:0,0 or /dev/audio). If NULL, will use defaults.
|
||||
static const char* audio_device = NULL;
|
||||
@ -101,7 +137,7 @@ static const bool audio_sync = true;
|
||||
|
||||
// Axis threshold (between 0.0 and 1.0)
|
||||
// How far an axis must be tilted to result in a button press
|
||||
#define AXIS_THRESHOLD 0.8
|
||||
#define AXIS_THRESHOLD 0.5
|
||||
|
||||
#define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF)
|
||||
#define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U)
|
||||
@ -115,48 +151,47 @@ static const bool audio_sync = true;
|
||||
// Player 1
|
||||
static const struct snes_keybind snes_keybinds_1[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, 'X', 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, 'Z', 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, 'S', 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, 'A', 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, 'Q', 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, 'W', 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, GLFW_KEY_LEFT, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, GLFW_KEY_RIGHT, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, GLFW_KEY_UP, 13, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, GLFW_KEY_DOWN, 14, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, GLFW_KEY_ENTER, 7, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, GLFW_KEY_RSHIFT, 6, AXIS_NONE },
|
||||
{ SNES_FAST_FORWARD_KEY, GLFW_KEY_SPACE, 10, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_x, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_z, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_s, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_a, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_q, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_w, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_LEFT, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_RIGHT, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_UP, 13, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_DOWN, 14, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_RETURN, 7, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_RSHIFT, 6, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_KEY, SDLK_SPACE, 10, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
// Player 2
|
||||
static const struct snes_keybind snes_keybinds_2[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, 'B', 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, 'V', 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, 'G', 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, 'F', 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, 'R', 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, 'T', 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, 'J', 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, 'L', 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, 'I', 13, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, 'K', 14, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, 'P', 6, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, 'O', 7, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
///// Save state
|
||||
#define SAVE_STATE_KEY GLFW_KEY_F2
|
||||
#define SAVE_STATE_KEY SDLK_F2
|
||||
///// Load state
|
||||
#define LOAD_STATE_KEY GLFW_KEY_F4
|
||||
#define LOAD_STATE_KEY SDLK_F4
|
||||
|
||||
//// Toggles between fullscreen and windowed mode.
|
||||
#define TOGGLE_FULLSCREEN 'F'
|
||||
|
||||
#define TOGGLE_FULLSCREEN SDLK_f
|
||||
|
||||
|
||||
#endif
|
||||
|
45
driver.c
45
driver.c
@ -21,7 +21,10 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "hqflt/filters.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
static const audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_ALSA
|
||||
@ -42,14 +45,23 @@ static const audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_JACK
|
||||
&audio_jack,
|
||||
#endif
|
||||
#ifdef HAVE_SDL
|
||||
&audio_sdl,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const video_driver_t *video_drivers[] = {
|
||||
#ifdef HAVE_GLFW
|
||||
#ifdef HAVE_SDL
|
||||
&video_gl,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const input_driver_t *input_drivers[] = {
|
||||
#ifdef HAVE_SDL
|
||||
&input_sdl,
|
||||
#endif
|
||||
};
|
||||
|
||||
static void find_audio_driver(void)
|
||||
{
|
||||
for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
|
||||
@ -86,6 +98,24 @@ static void find_video_driver(void)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void find_input_driver(void)
|
||||
{
|
||||
for (int i = 0; i < sizeof(input_drivers) / sizeof(input_driver_t*); i++)
|
||||
{
|
||||
if (strcasecmp(g_settings.input.driver, input_drivers[i]->ident) == 0)
|
||||
{
|
||||
driver.input = input_drivers[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
SSNES_ERR("Couldn't find any input driver named \"%s\"\n", g_settings.input.driver);
|
||||
fprintf(stderr, "Available input drivers are:\n");
|
||||
for (int i = 0; i < sizeof(input_drivers) / sizeof(input_driver_t*); i++)
|
||||
fprintf(stderr, "\t%s\n", video_drivers[i]->ident);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void init_drivers(void)
|
||||
{
|
||||
init_video_input();
|
||||
@ -141,6 +171,7 @@ void init_video_input(void)
|
||||
int scale = 2;
|
||||
|
||||
find_video_driver();
|
||||
find_input_driver();
|
||||
|
||||
// We multiply scales with 2 to allow for hi-res games.
|
||||
#if HAVE_FILTER
|
||||
@ -169,7 +200,7 @@ void init_video_input(void)
|
||||
};
|
||||
|
||||
const input_driver_t *tmp = driver.input;
|
||||
driver.video_data = driver.video->init(&video, &driver.input);
|
||||
driver.video_data = driver.video->init(&video, &driver.input, &driver.input_data);
|
||||
|
||||
if ( driver.video_data == NULL )
|
||||
{
|
||||
@ -177,18 +208,18 @@ void init_video_input(void)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ( driver.input != NULL )
|
||||
{
|
||||
driver.input_data = driver.video_data;
|
||||
}
|
||||
else
|
||||
// Video driver didn't provide an input driver so we use configured one.
|
||||
if (driver.input == NULL)
|
||||
{
|
||||
driver.input = tmp;
|
||||
if (driver.input != NULL)
|
||||
{
|
||||
driver.input_data = driver.input->init();
|
||||
if ( driver.input_data == NULL )
|
||||
{
|
||||
SSNES_ERR("Cannot init input driver. Exiting ...\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
12
driver.h
12
driver.h
@ -19,12 +19,13 @@
|
||||
#ifndef __DRIVER__H
|
||||
#define __DRIVER__H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define SNES_FAST_FORWARD_KEY 0x666 // Hurr, durr
|
||||
#define SSNES_FAST_FORWARD_KEY 0x666 // Hurr, durr
|
||||
void set_fast_forward_button(bool state);
|
||||
|
||||
struct snes_keybind
|
||||
@ -65,16 +66,19 @@ typedef struct input_driver
|
||||
void* (*init)(void);
|
||||
void (*poll)(void* data);
|
||||
int16_t (*input_state)(void* data, const struct snes_keybind **snes_keybinds, bool port, unsigned device, unsigned index, unsigned id);
|
||||
bool (*key_pressed)(void* data, int key);
|
||||
void (*free)(void* data);
|
||||
const char *ident;
|
||||
} input_driver_t;
|
||||
|
||||
typedef struct video_driver
|
||||
{
|
||||
void* (*init)(video_info_t *video, const input_driver_t **input);
|
||||
// Should the video driver act as an input driver as well? :)
|
||||
void* (*init)(video_info_t *video, const input_driver_t **input, void **input_data);
|
||||
// Should the video driver act as an input driver as well? :) The video init might preinitialize an input driver to override the settings in case the video driver relies on input driver for event handling, e.g.
|
||||
bool (*frame)(void* data, const uint16_t* frame, int width, int height, int pitch);
|
||||
void (*set_nonblock_state)(void* data, bool toggle); // Should we care about syncing to vblank? Fast forwarding.
|
||||
// Is the window still active?
|
||||
bool (*alive)(void *data);
|
||||
void (*free)(void* data);
|
||||
const char *ident;
|
||||
} video_driver_t;
|
||||
@ -107,7 +111,9 @@ extern const audio_driver_t audio_alsa;
|
||||
extern const audio_driver_t audio_roar;
|
||||
extern const audio_driver_t audio_openal;
|
||||
extern const audio_driver_t audio_jack;
|
||||
extern const audio_driver_t audio_sdl;
|
||||
extern const video_driver_t video_gl;
|
||||
extern const input_driver_t input_sdl;
|
||||
////////////////////////////////////////////////
|
||||
|
||||
#endif
|
||||
|
@ -18,7 +18,12 @@
|
||||
#include "dynamic.h"
|
||||
#include "general.h"
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <libsnes.hpp>
|
||||
|
||||
#ifdef HAVE_DYNAMIC
|
||||
#include <dlfcn.h>
|
||||
|
1
file.h
1
file.h
@ -22,6 +22,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
ssize_t read_file(FILE *file, void **buf);
|
||||
|
||||
|
27
general.h
27
general.h
@ -20,9 +20,17 @@
|
||||
#define __SSNES_GENERAL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <samplerate.h>
|
||||
#include "driver.h"
|
||||
#include <stdio.h>
|
||||
#include "record/ffemu.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
#endif
|
||||
|
||||
|
||||
#define MAX_PLAYERS 2
|
||||
@ -40,7 +48,9 @@ struct settings
|
||||
bool vsync;
|
||||
bool smooth;
|
||||
bool force_aspect;
|
||||
float aspect_ratio;
|
||||
char cg_shader_path[256];
|
||||
char bsnes_shader_path[256];
|
||||
unsigned filter;
|
||||
} video;
|
||||
|
||||
@ -81,6 +91,12 @@ struct global
|
||||
char savefile_name_srm[256];
|
||||
char config_path[256];
|
||||
char basename[256];
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
ffemu_t *rec;
|
||||
char record_path[256];
|
||||
bool recording;
|
||||
#endif
|
||||
};
|
||||
|
||||
void parse_config(void);
|
||||
@ -91,10 +107,17 @@ extern struct global g_extern;
|
||||
#define SSNES_LOG(msg, args...) do { \
|
||||
if (g_extern.verbose) \
|
||||
fprintf(stderr, "SSNES: " msg, ##args); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
|
||||
#define SSNES_ERR(msg, args...) do { \
|
||||
fprintf(stderr, "SSNES [ERROR] :: " msg, ##args); \
|
||||
fprintf(stderr, "SSNES [ERROR] :: " msg, ##args); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
|
||||
#define SSNES_WARN(msg, args...) do { \
|
||||
fprintf(stderr, "SSNES [WARN] :: " msg, ##args); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
|
||||
#endif
|
||||
|
391
gfx/gl.c
391
gfx/gl.c
@ -15,23 +15,38 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
|
||||
#include "driver.h"
|
||||
#include <GL/glfw.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include "libsnes.hpp"
|
||||
#include <stdio.h>
|
||||
#include <sys/time.h>
|
||||
#include <string.h>
|
||||
#include "general.h"
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define NO_SDL_GLEXT
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
#include "input/ssnes_sdl_input.h"
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/glext.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <GL/glx.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CG
|
||||
#include <Cg/cg.h>
|
||||
#include <Cg/cgGL.h>
|
||||
#include "shader_cg.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XML
|
||||
#include "shader_glsl.h"
|
||||
#endif
|
||||
|
||||
static const GLfloat vertexes[] = {
|
||||
@ -49,26 +64,20 @@ static const GLfloat tex_coords[] = {
|
||||
};
|
||||
|
||||
static bool keep_aspect = true;
|
||||
#ifdef HAVE_CG
|
||||
static CGparameter cg_mvp_matrix;
|
||||
static bool cg_active = false;
|
||||
#endif
|
||||
static GLuint gl_width = 0, gl_height = 0;
|
||||
typedef struct gl
|
||||
{
|
||||
bool vsync;
|
||||
#ifdef HAVE_CG
|
||||
CGcontext cgCtx;
|
||||
CGprogram cgFPrg;
|
||||
CGprogram cgVPrg;
|
||||
CGprofile cgFProf;
|
||||
CGprofile cgVProf;
|
||||
CGparameter cg_video_size, cg_texture_size, cg_output_size;
|
||||
CGparameter cg_Vvideo_size, cg_Vtexture_size, cg_Voutput_size; // Vertexes
|
||||
#endif
|
||||
GLuint texture;
|
||||
GLuint tex_filter;
|
||||
|
||||
bool should_resize;
|
||||
bool quitting;
|
||||
|
||||
unsigned win_width;
|
||||
unsigned win_height;
|
||||
unsigned vp_width;
|
||||
unsigned vp_height;
|
||||
unsigned last_width;
|
||||
unsigned last_height;
|
||||
unsigned tex_w, tex_h;
|
||||
@ -76,151 +85,99 @@ typedef struct gl
|
||||
} gl_t;
|
||||
|
||||
|
||||
static void glfw_input_poll(void *data)
|
||||
static inline bool gl_shader_init(void)
|
||||
{
|
||||
(void)data;
|
||||
glfwPollEvents();
|
||||
if (strlen(g_settings.video.cg_shader_path) > 0 && strlen(g_settings.video.bsnes_shader_path) > 0)
|
||||
SSNES_WARN("Both Cg and bSNES XML shader are defined in config file. Cg shader will be selected by default.\n");
|
||||
|
||||
#ifdef HAVE_CG
|
||||
if (strlen(g_settings.video.cg_shader_path) > 0)
|
||||
return gl_cg_init(g_settings.video.cg_shader_path);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XML
|
||||
if (strlen(g_settings.video.bsnes_shader_path) > 0)
|
||||
return gl_glsl_init(g_settings.video.bsnes_shader_path);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define BUTTONS_MAX 128
|
||||
#define AXES_MAX 128
|
||||
|
||||
static unsigned joypad_id[2];
|
||||
static unsigned joypad_buttons[2];
|
||||
static unsigned joypad_axes[2];
|
||||
static bool joypad_inited = false;
|
||||
static unsigned joypad_count = 0;
|
||||
|
||||
static int init_joypads(int max_pads)
|
||||
static inline void gl_shader_deinit(void)
|
||||
{
|
||||
// Finds the first (two) joypads that are alive
|
||||
int count = 0;
|
||||
for ( int i = GLFW_JOYSTICK_1; (i <= GLFW_JOYSTICK_LAST) && (count < max_pads); i++ )
|
||||
{
|
||||
if ( glfwGetJoystickParam(i, GLFW_PRESENT) == GL_TRUE )
|
||||
{
|
||||
joypad_id[count] = i;
|
||||
joypad_buttons[count] = glfwGetJoystickParam(i, GLFW_BUTTONS);
|
||||
if (joypad_buttons[count] > BUTTONS_MAX)
|
||||
joypad_buttons[count] = BUTTONS_MAX;
|
||||
joypad_axes[count] = glfwGetJoystickParam(i, GLFW_AXES);
|
||||
if (joypad_axes[count] > AXES_MAX)
|
||||
joypad_axes[count] = AXES_MAX;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
joypad_inited = true;
|
||||
return count;
|
||||
#ifdef HAVE_CG
|
||||
gl_cg_deinit();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XML
|
||||
gl_glsl_deinit();
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool glfw_is_pressed(int port_num, const struct snes_keybind *key, unsigned char *buttons, float *axes)
|
||||
static inline void gl_shader_set_proj_matrix(void)
|
||||
{
|
||||
if (glfwGetKey(key->key))
|
||||
return true;
|
||||
if (port_num >= joypad_count)
|
||||
return false;
|
||||
if (key->joykey < joypad_buttons[port_num] && buttons[key->joykey] == GLFW_PRESS)
|
||||
return true;
|
||||
#ifdef HAVE_CG
|
||||
gl_cg_set_proj_matrix();
|
||||
#endif
|
||||
|
||||
if (key->joyaxis != AXIS_NONE)
|
||||
{
|
||||
if (AXIS_NEG_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_NEG_GET(key->joyaxis)] <= -g_settings.input.axis_threshold)
|
||||
return true;
|
||||
if (AXIS_POS_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_POS_GET(key->joyaxis)] >= g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#ifdef HAVE_XML
|
||||
gl_glsl_set_proj_matrix();
|
||||
#endif
|
||||
}
|
||||
|
||||
static int16_t glfw_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id)
|
||||
static inline void gl_shader_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height)
|
||||
{
|
||||
if ( device != SNES_DEVICE_JOYPAD )
|
||||
return 0;
|
||||
#ifdef HAVE_CG
|
||||
gl_cg_set_params(width, height, tex_width, tex_height, out_width, out_height);
|
||||
#endif
|
||||
|
||||
if ( !joypad_inited )
|
||||
joypad_count = init_joypads(2);
|
||||
|
||||
int port_num = port ? 1 : 0;
|
||||
unsigned char buttons[BUTTONS_MAX];
|
||||
float axes[AXES_MAX];
|
||||
|
||||
if ( joypad_count > port_num )
|
||||
{
|
||||
glfwGetJoystickButtons(joypad_id[port_num], buttons, joypad_buttons[port_num]);
|
||||
glfwGetJoystickPos(joypad_id[port_num], axes, joypad_axes[port_num]);
|
||||
}
|
||||
|
||||
|
||||
const struct snes_keybind *snes_keybinds;
|
||||
if (port == SNES_PORT_1)
|
||||
snes_keybinds = binds[0];
|
||||
else
|
||||
snes_keybinds = binds[1];
|
||||
|
||||
// Checks if button is pressed, and sets fast-forwarding state
|
||||
bool pressed = false;
|
||||
for ( int i = 0; snes_keybinds[i].id != -1; i++ )
|
||||
if ( snes_keybinds[i].id == SNES_FAST_FORWARD_KEY )
|
||||
set_fast_forward_button(glfw_is_pressed(port_num, &snes_keybinds[i], buttons, axes));
|
||||
else if ( !pressed && snes_keybinds[i].id == (int)id )
|
||||
pressed = glfw_is_pressed(port_num, &snes_keybinds[i], buttons, axes);
|
||||
|
||||
return pressed;
|
||||
#ifdef HAVE_XML
|
||||
gl_glsl_set_params(width, height, tex_width, tex_height, out_width, out_height);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void glfw_free_input(void *data)
|
||||
{
|
||||
free(data);
|
||||
}
|
||||
|
||||
static const input_driver_t input_glfw = {
|
||||
.poll = glfw_input_poll,
|
||||
.input_state = glfw_input_state,
|
||||
.free = glfw_free_input,
|
||||
.ident = "glfw"
|
||||
};
|
||||
|
||||
static void GLFWCALL resize(int width, int height)
|
||||
static void set_viewport(gl_t *gl)
|
||||
{
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
GLuint out_width = width, out_height = height;
|
||||
GLuint out_width = gl->win_width, out_height = gl->win_height;
|
||||
|
||||
if ( keep_aspect )
|
||||
{
|
||||
float desired_aspect = 4.0/3;
|
||||
float device_aspect = (float)width / height;
|
||||
float desired_aspect = g_settings.video.aspect_ratio;
|
||||
float device_aspect = (float)gl->win_width / gl->win_height;
|
||||
|
||||
// If the aspect ratios of screen and desired aspect ratio are sufficiently equal (floating point stuff),
|
||||
// assume they are actually equal.
|
||||
if ( (int)(device_aspect*1000) > (int)(desired_aspect*1000) )
|
||||
{
|
||||
float delta = (desired_aspect / device_aspect - 1.0) / 2.0 + 0.5;
|
||||
glViewport(width * (0.5 - delta), 0, 2.0 * width * delta, height);
|
||||
out_width = (int)(2.0 * width * delta);
|
||||
glViewport(gl->win_width * (0.5 - delta), 0, 2.0 * gl->win_width * delta, gl->win_height);
|
||||
out_width = (int)(2.0 * gl->win_width * delta);
|
||||
}
|
||||
|
||||
else if ( (int)(device_aspect*1000) < (int)(desired_aspect*1000) )
|
||||
{
|
||||
float delta = (device_aspect / desired_aspect - 1.0) / 2.0 + 0.5;
|
||||
glViewport(0, height * (0.5 - delta), width, 2.0 * height * delta);
|
||||
out_height = (int)(2.0 * height * delta);
|
||||
glViewport(0, gl->win_height * (0.5 - delta), gl->win_width, 2.0 * gl->win_height * delta);
|
||||
out_height = (int)(2.0 * gl->win_height * delta);
|
||||
}
|
||||
else
|
||||
glViewport(0, 0, width, height);
|
||||
glViewport(0, 0, gl->win_width, gl->win_height);
|
||||
}
|
||||
else
|
||||
glViewport(0, 0, width, height);
|
||||
glViewport(0, 0, gl->win_width, gl->win_height);
|
||||
|
||||
glOrtho(0, 1, 0, 1, -1, 1);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
#ifdef HAVE_CG
|
||||
if (cg_active)
|
||||
cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
|
||||
#endif
|
||||
gl_width = out_width;
|
||||
gl_height = out_height;
|
||||
|
||||
gl_shader_set_proj_matrix();
|
||||
|
||||
gl->vp_width = out_width;
|
||||
gl->vp_height = out_height;
|
||||
}
|
||||
|
||||
static float tv_to_fps(const struct timeval *tv, const struct timeval *new_tv, int frames)
|
||||
@ -229,7 +186,7 @@ static float tv_to_fps(const struct timeval *tv, const struct timeval *new_tv, i
|
||||
return frames/time;
|
||||
}
|
||||
|
||||
static inline void show_fps(void)
|
||||
static void show_fps(void)
|
||||
{
|
||||
// Shows FPS in taskbar.
|
||||
static int frames = 0;
|
||||
@ -248,8 +205,8 @@ static inline void show_fps(void)
|
||||
|
||||
float fps = tv_to_fps(&tmp_tv, &new_tv, 180);
|
||||
|
||||
snprintf(tmpstr, sizeof(tmpstr) - 1, "SSNES || FPS: %6.1f || Frames: %d", fps, frames);
|
||||
glfwSetWindowTitle(tmpstr);
|
||||
snprintf(tmpstr, sizeof(tmpstr), "SSNES || FPS: %6.1f || Frames: %d", fps, frames);
|
||||
SDL_WM_SetCaption(tmpstr, NULL);
|
||||
}
|
||||
frames++;
|
||||
}
|
||||
@ -258,20 +215,16 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i
|
||||
{
|
||||
gl_t *gl = data;
|
||||
|
||||
if (gl->should_resize)
|
||||
{
|
||||
gl->should_resize = false;
|
||||
SDL_SetVideoMode(gl->win_width, gl->win_height, 32, SDL_OPENGL | SDL_RESIZABLE | (g_settings.video.fullscreen ? SDL_FULLSCREEN : 0));
|
||||
set_viewport(gl);
|
||||
}
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
#if HAVE_CG
|
||||
if (cg_active)
|
||||
{
|
||||
cgGLSetParameter2f(gl->cg_video_size, width, height);
|
||||
cgGLSetParameter2f(gl->cg_texture_size, gl->tex_w, gl->tex_h);
|
||||
cgGLSetParameter2f(gl->cg_output_size, gl_width, gl_height);
|
||||
|
||||
cgGLSetParameter2f(gl->cg_Vvideo_size, width, height);
|
||||
cgGLSetParameter2f(gl->cg_Vtexture_size, gl->tex_w, gl->tex_h);
|
||||
cgGLSetParameter2f(gl->cg_Voutput_size, gl_width, gl_height);
|
||||
}
|
||||
#endif
|
||||
gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl_width, gl_height);
|
||||
|
||||
if (width != gl->last_width || height != gl->last_height) // res change. need to clear out texture.
|
||||
{
|
||||
@ -302,7 +255,8 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
|
||||
show_fps();
|
||||
glfwSwapBuffers();
|
||||
glFlush();
|
||||
SDL_GL_SwapBuffers();
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -310,14 +264,12 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i
|
||||
static void gl_free(void *data)
|
||||
{
|
||||
gl_t *gl = data;
|
||||
#ifdef HAVE_CG
|
||||
if (cg_active)
|
||||
cgDestroyContext(gl->cgCtx);
|
||||
#endif
|
||||
|
||||
gl_shader_deinit();
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glDeleteTextures(1, &gl->texture);
|
||||
glfwTerminate();
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
}
|
||||
|
||||
static void gl_set_nonblock_state(void *data, bool state)
|
||||
@ -325,43 +277,62 @@ static void gl_set_nonblock_state(void *data, bool state)
|
||||
gl_t *gl = data;
|
||||
if (gl->vsync)
|
||||
{
|
||||
if (state)
|
||||
glfwSwapInterval(0);
|
||||
else
|
||||
glfwSwapInterval(1);
|
||||
SSNES_LOG("GL VSync => %s\n", state ? "off" : "on");
|
||||
#ifdef _WIN32
|
||||
static BOOL (APIENTRY *wgl_swap_interval)(int) = NULL;
|
||||
if (!wgl_swap_interval)
|
||||
SSNES_WARN("SDL VSync toggling seems to be broken, attempting to use WGL VSync call directly instead.\n");
|
||||
if (!wgl_swap_interval) wgl_swap_interval = (BOOL (APIENTRY*)(int)) wglGetProcAddress("wglSwapIntervalEXT");
|
||||
if (wgl_swap_interval) wgl_swap_interval(state ? 0 : 1);
|
||||
#else
|
||||
static int (*glx_swap_interval)(int) = NULL;
|
||||
if (!glx_swap_interval)
|
||||
SSNES_WARN("SDL VSync toggling seems to be broken, attempting to use GLX VSync call directly instead.\n");
|
||||
if (!glx_swap_interval) glx_swap_interval = (int (*)(int))glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalSGI");
|
||||
if (!glx_swap_interval) glx_swap_interval = (int (*)(int))glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalMESA");
|
||||
if (glx_swap_interval) glx_swap_interval(state ? 0 : 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void* gl_init(video_info_t *video, const input_driver_t **input)
|
||||
static void* gl_init(video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
gl_t *gl = calloc(1, sizeof(gl_t));
|
||||
if ( gl == NULL )
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
return NULL;
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, video->vsync ? 1 : 0);
|
||||
|
||||
if (!SDL_SetVideoMode(video->width, video->height, 32, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
|
||||
return NULL;
|
||||
|
||||
int attr = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &attr);
|
||||
if (attr <= 0 && video->vsync)
|
||||
SSNES_WARN("GL VSync has not been enabled!\n");
|
||||
attr = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &attr);
|
||||
if (attr <= 0)
|
||||
SSNES_WARN("GL double buffer has not been enabled!\n");
|
||||
|
||||
// Remove that ugly mouse :D
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
|
||||
gl_t *gl = calloc(1, sizeof(gl_t));
|
||||
if (!gl)
|
||||
return NULL;
|
||||
|
||||
gl->win_width = video->width;
|
||||
gl->win_height = video->height;
|
||||
gl->vsync = video->vsync;
|
||||
|
||||
keep_aspect = video->force_aspect;
|
||||
if ( video->smooth )
|
||||
gl->tex_filter = GL_LINEAR;
|
||||
else
|
||||
gl->tex_filter = GL_NEAREST;
|
||||
|
||||
glfwInit();
|
||||
|
||||
int res;
|
||||
res = glfwOpenWindow(video->width, video->height, 0, 0, 0, 0, 0, 0, (video->fullscreen) ? GLFW_FULLSCREEN : GLFW_WINDOW);
|
||||
|
||||
if (!res)
|
||||
{
|
||||
glfwTerminate();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
glfwSetWindowSizeCallback(resize);
|
||||
|
||||
if ( video->vsync )
|
||||
glfwSwapInterval(1); // Force vsync
|
||||
else
|
||||
glfwSwapInterval(0);
|
||||
gl->vsync = video->vsync;
|
||||
set_viewport(gl);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glDisable(GL_DITHER);
|
||||
@ -369,7 +340,7 @@ static void* gl_init(video_info_t *video, const input_driver_t **input)
|
||||
glColor3f(1, 1, 1);
|
||||
glClearColor(0, 0, 0, 0);
|
||||
|
||||
glfwSetWindowTitle("SSNES");
|
||||
SDL_WM_SetCaption("SSNES", NULL);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
@ -378,8 +349,8 @@ static void* gl_init(video_info_t *video, const input_driver_t **input)
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl->tex_filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl->tex_filter);
|
||||
|
||||
@ -400,68 +371,38 @@ static void* gl_init(video_info_t *video, const input_driver_t **input)
|
||||
gl->last_width = gl->tex_w;
|
||||
gl->last_height = gl->tex_h;
|
||||
|
||||
#ifdef HAVE_CG
|
||||
cg_active = false;
|
||||
if (strlen(g_settings.video.cg_shader_path) > 0)
|
||||
gl_shader_init();
|
||||
|
||||
// Hook up SDL input driver to get SDL_QUIT events and RESIZE.
|
||||
sdl_input_t *sdl_input = input_sdl.init();
|
||||
if (sdl_input)
|
||||
{
|
||||
SSNES_LOG("Loading Cg file: %s\n", g_settings.video.cg_shader_path);
|
||||
gl->cgCtx = cgCreateContext();
|
||||
if (gl->cgCtx == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to create Cg context\n");
|
||||
goto error;
|
||||
}
|
||||
gl->cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT);
|
||||
gl->cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX);
|
||||
if (gl->cgFProf == CG_PROFILE_UNKNOWN || gl->cgVProf == CG_PROFILE_UNKNOWN)
|
||||
{
|
||||
fprintf(stderr, "Invalid profile type\n");
|
||||
goto error;
|
||||
}
|
||||
cgGLSetOptimalOptions(gl->cgFProf);
|
||||
cgGLSetOptimalOptions(gl->cgVProf);
|
||||
gl->cgFPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, g_settings.video.cg_shader_path, gl->cgFProf, "main_fragment", 0);
|
||||
gl->cgVPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, g_settings.video.cg_shader_path, gl->cgVProf, "main_vertex", 0);
|
||||
if (gl->cgFPrg == NULL || gl->cgVPrg == NULL)
|
||||
{
|
||||
CGerror err = cgGetError();
|
||||
fprintf(stderr, "CG error: %s\n", cgGetErrorString(err));
|
||||
goto error;
|
||||
}
|
||||
cgGLLoadProgram(gl->cgFPrg);
|
||||
cgGLLoadProgram(gl->cgVPrg);
|
||||
cgGLEnableProfile(gl->cgFProf);
|
||||
cgGLEnableProfile(gl->cgVProf);
|
||||
cgGLBindProgram(gl->cgFPrg);
|
||||
cgGLBindProgram(gl->cgVPrg);
|
||||
|
||||
gl->cg_video_size = cgGetNamedParameter(gl->cgFPrg, "IN.video_size");
|
||||
gl->cg_texture_size = cgGetNamedParameter(gl->cgFPrg, "IN.texture_size");
|
||||
gl->cg_output_size = cgGetNamedParameter(gl->cgFPrg, "IN.output_size");
|
||||
gl->cg_Vvideo_size = cgGetNamedParameter(gl->cgVPrg, "IN.video_size");
|
||||
gl->cg_Vtexture_size = cgGetNamedParameter(gl->cgVPrg, "IN.texture_size");
|
||||
gl->cg_Voutput_size = cgGetNamedParameter(gl->cgVPrg, "IN.output_size");
|
||||
cg_mvp_matrix = cgGetNamedParameter(gl->cgVPrg, "modelViewProj");
|
||||
cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
|
||||
cg_active = true;
|
||||
sdl_input->quitting = &gl->quitting;
|
||||
sdl_input->should_resize = &gl->should_resize;
|
||||
sdl_input->new_width = &gl->win_width;
|
||||
sdl_input->new_height = &gl->win_height;
|
||||
*input = &input_sdl;
|
||||
*input_data = sdl_input;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
*input = NULL;
|
||||
|
||||
*input = &input_glfw;
|
||||
return gl;
|
||||
#ifdef HAVE_CG
|
||||
error:
|
||||
free(gl);
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool gl_alive(void *data)
|
||||
{
|
||||
gl_t *gl = data;
|
||||
return !gl->quitting;
|
||||
}
|
||||
|
||||
const video_driver_t video_gl = {
|
||||
.init = gl_init,
|
||||
.frame = gl_frame,
|
||||
.alive = gl_alive,
|
||||
.set_nonblock_state = gl_set_nonblock_state,
|
||||
.free = gl_free,
|
||||
.ident = "glfw"
|
||||
.ident = "gl"
|
||||
};
|
||||
|
||||
|
||||
|
104
gfx/shader_cg.c
Normal file
104
gfx/shader_cg.c
Normal file
@ -0,0 +1,104 @@
|
||||
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "shader_cg.h"
|
||||
#include <Cg/cg.h>
|
||||
#include <Cg/cgGL.h>
|
||||
#include "general.h"
|
||||
|
||||
static CGcontext cgCtx;
|
||||
static CGprogram cgFPrg;
|
||||
static CGprogram cgVPrg;
|
||||
static CGprofile cgFProf;
|
||||
static CGprofile cgVProf;
|
||||
static CGparameter cg_video_size, cg_texture_size, cg_output_size;
|
||||
static CGparameter cg_Vvideo_size, cg_Vtexture_size, cg_Voutput_size; // Vertexes
|
||||
static CGparameter cg_mvp_matrix;
|
||||
static bool cg_active = false;
|
||||
|
||||
void gl_cg_set_proj_matrix(void)
|
||||
{
|
||||
if (cg_active)
|
||||
cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
|
||||
}
|
||||
|
||||
void gl_cg_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height)
|
||||
{
|
||||
if (cg_active)
|
||||
{
|
||||
cgGLSetParameter2f(cg_video_size, width, height);
|
||||
cgGLSetParameter2f(cg_texture_size, tex_width, tex_height);
|
||||
cgGLSetParameter2f(cg_output_size, out_width, out_height);
|
||||
|
||||
cgGLSetParameter2f(cg_Vvideo_size, width, height);
|
||||
cgGLSetParameter2f(cg_Vtexture_size, tex_width, tex_height);
|
||||
cgGLSetParameter2f(cg_Voutput_size, out_width, out_height);
|
||||
}
|
||||
}
|
||||
|
||||
void gl_cg_deinit(void)
|
||||
{
|
||||
if (cg_active)
|
||||
cgDestroyContext(cgCtx);
|
||||
}
|
||||
|
||||
bool gl_cg_init(const char *path)
|
||||
{
|
||||
SSNES_LOG("Loading Cg file: %s\n", path);
|
||||
cgCtx = cgCreateContext();
|
||||
if (cgCtx == NULL)
|
||||
{
|
||||
SSNES_ERR("Failed to create Cg context\n");
|
||||
return false;
|
||||
}
|
||||
cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT);
|
||||
cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX);
|
||||
if (cgFProf == CG_PROFILE_UNKNOWN || cgVProf == CG_PROFILE_UNKNOWN)
|
||||
{
|
||||
SSNES_ERR("Invalid profile type\n");
|
||||
return false;
|
||||
}
|
||||
cgGLSetOptimalOptions(cgFProf);
|
||||
cgGLSetOptimalOptions(cgVProf);
|
||||
cgFPrg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, path, cgFProf, "main_fragment", 0);
|
||||
cgVPrg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, path, cgVProf, "main_vertex", 0);
|
||||
if (cgFPrg == NULL || cgVPrg == NULL)
|
||||
{
|
||||
CGerror err = cgGetError();
|
||||
SSNES_ERR("CG error: %s\n", cgGetErrorString(err));
|
||||
return false;
|
||||
}
|
||||
cgGLLoadProgram(cgFPrg);
|
||||
cgGLLoadProgram(cgVPrg);
|
||||
cgGLEnableProfile(cgFProf);
|
||||
cgGLEnableProfile(cgVProf);
|
||||
cgGLBindProgram(cgFPrg);
|
||||
cgGLBindProgram(cgVPrg);
|
||||
|
||||
cg_video_size = cgGetNamedParameter(cgFPrg, "IN.video_size");
|
||||
cg_texture_size = cgGetNamedParameter(cgFPrg, "IN.texture_size");
|
||||
cg_output_size = cgGetNamedParameter(cgFPrg, "IN.output_size");
|
||||
cg_Vvideo_size = cgGetNamedParameter(cgVPrg, "IN.video_size");
|
||||
cg_Vtexture_size = cgGetNamedParameter(cgVPrg, "IN.texture_size");
|
||||
cg_Voutput_size = cgGetNamedParameter(cgVPrg, "IN.output_size");
|
||||
cg_mvp_matrix = cgGetNamedParameter(cgVPrg, "modelViewProj");
|
||||
cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
|
||||
cg_active = true;
|
||||
return true;
|
||||
}
|
34
gfx/shader_cg.h
Normal file
34
gfx/shader_cg.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __SSNES_CG_H
|
||||
#define __SSNES_CG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool gl_cg_init(const char *path);
|
||||
|
||||
void gl_cg_deinit(void);
|
||||
|
||||
void gl_cg_set_proj_matrix(void);
|
||||
|
||||
void gl_cg_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height);
|
||||
|
||||
#endif
|
253
gfx/shader_glsl.c
Normal file
253
gfx/shader_glsl.c
Normal file
@ -0,0 +1,253 @@
|
||||
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//
|
||||
// GLSL code here is mostly copypasted from bSNES.
|
||||
//
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include "general.h"
|
||||
|
||||
#define NO_SDL_GLEXT
|
||||
#include <GL/gl.h>
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
#include <stdlib.h>
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/tree.h>
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/glext.h>
|
||||
|
||||
static PFNGLCREATEPROGRAMPROC pglCreateProgram = NULL;
|
||||
static PFNGLUSEPROGRAMPROC pglUseProgram = NULL;
|
||||
static PFNGLCREATESHADERPROC pglCreateShader = NULL;
|
||||
static PFNGLDELETESHADERPROC pglDeleteShader = NULL;
|
||||
static PFNGLSHADERSOURCEPROC pglShaderSource = NULL;
|
||||
static PFNGLCOMPILESHADERPROC pglCompileShader = NULL;
|
||||
static PFNGLATTACHSHADERPROC pglAttachShader = NULL;
|
||||
static PFNGLDETACHSHADERPROC pglDetachShader = NULL;
|
||||
static PFNGLLINKPROGRAMPROC pglLinkProgram = NULL;
|
||||
static PFNGLGETUNIFORMLOCATIONPROC pglGetUniformLocation = NULL;
|
||||
static PFNGLUNIFORM1IPROC pglUniform1i = NULL;
|
||||
static PFNGLUNIFORM2FVPROC pglUniform2fv = NULL;
|
||||
static PFNGLUNIFORM4FVPROC pglUniform4fv = NULL;
|
||||
static PFNGLGETSHADERIVPROC pglGetShaderiv = NULL;
|
||||
static PFNGLGETSHADERINFOLOGPROC pglGetShaderInfoLog = NULL;
|
||||
|
||||
static bool glsl_enable = false;
|
||||
static GLuint gl_program;
|
||||
static GLuint fragment_shader;
|
||||
static GLuint vertex_shader;
|
||||
|
||||
static bool get_xml_shaders(const char *path, char **vertex_shader, char **fragment_shader)
|
||||
{
|
||||
LIBXML_TEST_VERSION;
|
||||
|
||||
xmlParserCtxtPtr ctx = xmlNewParserCtxt();
|
||||
if (!ctx)
|
||||
{
|
||||
SSNES_ERR("Failed to load libxml2 context.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
SSNES_LOG("Loading XML shader: %s\n", path);
|
||||
xmlDocPtr doc = xmlCtxtReadFile(ctx, path, NULL, 0);
|
||||
if (!doc)
|
||||
{
|
||||
SSNES_ERR("Failed to parse XML file: %s\n", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ctx->valid == 0)
|
||||
{
|
||||
SSNES_ERR("Cannot validate XML shader: %s\n", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
xmlNodePtr head = xmlDocGetRootElement(doc);
|
||||
xmlNodePtr cur = NULL;
|
||||
for (cur = head; cur; cur = cur->next)
|
||||
{
|
||||
if (cur->type == XML_ELEMENT_NODE && strcmp((const char*)cur->name, "shader") == 0)
|
||||
{
|
||||
xmlChar *attr;
|
||||
if ((attr = xmlGetProp(cur, (const xmlChar*)"language")) && strcmp((const char*)attr, "GLSL") == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cur) // We couldn't find any GLSL shader :(
|
||||
goto error;
|
||||
|
||||
bool vertex_found = false;
|
||||
bool fragment_found = false;
|
||||
// Iterate to check if we find fragment and/or vertex shaders.
|
||||
for (cur = cur->children; cur; cur = cur->next)
|
||||
{
|
||||
if (cur->type != XML_ELEMENT_NODE)
|
||||
continue;
|
||||
|
||||
xmlChar *content = xmlNodeGetContent(cur);
|
||||
if (!content)
|
||||
continue;
|
||||
|
||||
if (strcmp((const char*)cur->name, "vertex") == 0 && !vertex_found)
|
||||
{
|
||||
*vertex_shader = malloc(xmlStrlen(content) + 1);
|
||||
strcpy(*vertex_shader, (const char*)content);
|
||||
vertex_found = true;
|
||||
}
|
||||
else if (strcmp((const char*)cur->name, "fragment") == 0 && !fragment_found)
|
||||
{
|
||||
*fragment_shader = malloc(xmlStrlen(content) + 1);
|
||||
strcpy(*fragment_shader, (const char*)content);
|
||||
fragment_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vertex_found && !fragment_found)
|
||||
{
|
||||
SSNES_ERR("Couldn't find vertex shader nor fragment shader in XML file.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
xmlFreeDoc(doc);
|
||||
xmlFreeParserCtxt(ctx);
|
||||
return true;
|
||||
|
||||
error:
|
||||
SSNES_ERR("Failed to load XML shader ...\n");
|
||||
if (doc)
|
||||
xmlFreeDoc(doc);
|
||||
xmlFreeParserCtxt(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void print_shader_log(GLuint obj)
|
||||
{
|
||||
int info_len = 0;
|
||||
int max_len;
|
||||
|
||||
pglGetShaderiv(obj, GL_INFO_LOG_LENGTH, &max_len);
|
||||
|
||||
char info_log[max_len];
|
||||
pglGetShaderInfoLog(obj, max_len, &info_len, info_log);
|
||||
|
||||
if (info_len > 0)
|
||||
SSNES_LOG("Shader log: %s\n", info_log);
|
||||
}
|
||||
|
||||
bool gl_glsl_init(const char *path)
|
||||
{
|
||||
// Load shader functions.
|
||||
pglCreateProgram = SDL_GL_GetProcAddress("glCreateProgram");
|
||||
pglUseProgram = SDL_GL_GetProcAddress("glUseProgram");
|
||||
pglCreateShader = SDL_GL_GetProcAddress("glCreateShader");
|
||||
pglDeleteShader = SDL_GL_GetProcAddress("glDeleteShader");
|
||||
pglShaderSource = SDL_GL_GetProcAddress("glShaderSource");
|
||||
pglCompileShader = SDL_GL_GetProcAddress("glCompileShader");
|
||||
pglAttachShader = SDL_GL_GetProcAddress("glAttachShader");
|
||||
pglDetachShader = SDL_GL_GetProcAddress("glDetachShader");
|
||||
pglLinkProgram = SDL_GL_GetProcAddress("glLinkProgram");
|
||||
pglGetUniformLocation = SDL_GL_GetProcAddress("glGetUniformLocation");
|
||||
pglUniform1i = SDL_GL_GetProcAddress("glUniform1i");
|
||||
pglUniform2fv = SDL_GL_GetProcAddress("glUniform2fv");
|
||||
pglUniform4fv = SDL_GL_GetProcAddress("glUniform4fv");
|
||||
pglGetShaderiv = SDL_GL_GetProcAddress("glGetShaderiv");
|
||||
pglGetShaderInfoLog = SDL_GL_GetProcAddress("glGetShaderInfoLog");
|
||||
|
||||
SSNES_LOG("Checking GLSL shader support ...\n");
|
||||
bool shader_support = pglCreateProgram && pglUseProgram && pglCreateShader
|
||||
&& pglDeleteShader && pglShaderSource && pglCompileShader && pglAttachShader
|
||||
&& pglDetachShader && pglLinkProgram && pglGetUniformLocation
|
||||
&& pglUniform1i && pglUniform2fv && pglUniform4fv
|
||||
&& pglGetShaderiv && pglGetShaderInfoLog;
|
||||
|
||||
if (!shader_support)
|
||||
{
|
||||
SSNES_ERR("GLSL shaders aren't supported by your GL driver.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
gl_program = pglCreateProgram();
|
||||
|
||||
char *vertex_prog = NULL;
|
||||
char *fragment_prog = NULL;
|
||||
if (!get_xml_shaders(path, &vertex_prog, &fragment_prog))
|
||||
return false;
|
||||
|
||||
if (vertex_prog)
|
||||
{
|
||||
vertex_shader = pglCreateShader(GL_VERTEX_SHADER);
|
||||
pglShaderSource(vertex_shader, 1, (const char**)&vertex_prog, 0);
|
||||
pglCompileShader(vertex_shader);
|
||||
print_shader_log(vertex_shader);
|
||||
|
||||
pglAttachShader(gl_program, vertex_shader);
|
||||
free(vertex_prog);
|
||||
}
|
||||
if (fragment_prog)
|
||||
{
|
||||
fragment_shader = pglCreateShader(GL_FRAGMENT_SHADER);
|
||||
pglShaderSource(fragment_shader, 1, (const char**)&fragment_prog, 0);
|
||||
pglCompileShader(fragment_shader);
|
||||
print_shader_log(fragment_shader);
|
||||
|
||||
pglAttachShader(gl_program, fragment_shader);
|
||||
free(fragment_prog);
|
||||
}
|
||||
|
||||
if (vertex_prog || fragment_prog)
|
||||
{
|
||||
pglLinkProgram(gl_program);
|
||||
pglUseProgram(gl_program);
|
||||
}
|
||||
|
||||
glsl_enable = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void gl_glsl_deinit(void)
|
||||
{}
|
||||
|
||||
void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height)
|
||||
{
|
||||
if (glsl_enable)
|
||||
{
|
||||
GLint location;
|
||||
|
||||
float inputSize[2] = {width, height};
|
||||
location = pglGetUniformLocation(gl_program, "rubyInputSize");
|
||||
pglUniform2fv(location, 1, inputSize);
|
||||
|
||||
float outputSize[2] = {out_width, out_height};
|
||||
location = pglGetUniformLocation(gl_program, "rubyOutputSize");
|
||||
pglUniform2fv(location, 1, outputSize);
|
||||
|
||||
float textureSize[2] = {tex_width, tex_height};
|
||||
location = pglGetUniformLocation(gl_program, "rubyTextureSize");
|
||||
pglUniform2fv(location, 1, textureSize);
|
||||
}
|
||||
}
|
||||
|
||||
void gl_glsl_set_proj_matrix(void)
|
||||
{}
|
34
gfx/shader_glsl.h
Normal file
34
gfx/shader_glsl.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __SSNES_GLSL_H
|
||||
#define __SSNES_GLSL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool gl_glsl_init(const char *path);
|
||||
|
||||
void gl_glsl_deinit(void);
|
||||
|
||||
void gl_glsl_set_proj_matrix(void);
|
||||
|
||||
void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height);
|
||||
|
||||
#endif
|
@ -19,7 +19,9 @@
|
||||
#ifndef __FILTERS_H
|
||||
#define __FILTERS_H
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FILTER
|
||||
|
||||
|
177
input/sdl.c
Normal file
177
input/sdl.c
Normal file
@ -0,0 +1,177 @@
|
||||
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "driver.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include <stdbool.h>
|
||||
#include "general.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <libsnes.hpp>
|
||||
#include "ssnes_sdl_input.h"
|
||||
|
||||
static void* sdl_input_init(void)
|
||||
{
|
||||
sdl_input_t *sdl = calloc(1, sizeof(*sdl));
|
||||
if (!sdl)
|
||||
return NULL;
|
||||
|
||||
if (SDL_Init(SDL_INIT_JOYSTICK) < 0)
|
||||
return NULL;
|
||||
|
||||
sdl->num_joysticks = SDL_NumJoysticks();
|
||||
if (sdl->num_joysticks > 2)
|
||||
sdl->num_joysticks = 2;
|
||||
for (unsigned i = 0; i < sdl->num_joysticks; i++)
|
||||
{
|
||||
sdl->joysticks[i] = SDL_JoystickOpen(i);
|
||||
if (!sdl->joysticks[i])
|
||||
{
|
||||
SSNES_ERR("Couldn't open SDL joystick %d\n", i);
|
||||
free(sdl);
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SSNES_LOG("Opened Joystick: %s\n", SDL_JoystickName(i));
|
||||
sdl->num_axes[i] = SDL_JoystickNumAxes(sdl->joysticks[i]);
|
||||
sdl->num_buttons[i] = SDL_JoystickNumButtons(sdl->joysticks[i]);
|
||||
}
|
||||
|
||||
return sdl;
|
||||
}
|
||||
|
||||
static bool sdl_key_pressed(void *data, int key)
|
||||
{
|
||||
int num_keys;
|
||||
Uint8 *keymap = SDL_GetKeyState(&num_keys);
|
||||
|
||||
if (key >= num_keys)
|
||||
return false;
|
||||
|
||||
return keymap[key];
|
||||
}
|
||||
|
||||
static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_keybind *key)
|
||||
{
|
||||
if (sdl_key_pressed(sdl, key->key))
|
||||
return true;
|
||||
if (port_num >= sdl->num_joysticks)
|
||||
return false;
|
||||
if (key->joykey < sdl->num_buttons[port_num] && SDL_JoystickGetButton(sdl->joysticks[port_num], key->joykey))
|
||||
return true;
|
||||
|
||||
if (key->joyaxis != AXIS_NONE)
|
||||
{
|
||||
if (AXIS_NEG_GET(key->joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(key->joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled < -g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
if (AXIS_POS_GET(key->joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(key->joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled > g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int16_t sdl_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
sdl_input_t *sdl = data;
|
||||
if (device != SNES_DEVICE_JOYPAD)
|
||||
return 0;
|
||||
|
||||
const struct snes_keybind *snes_keybinds = binds[port == SNES_PORT_1 ? 0 : 1];
|
||||
|
||||
// Checks if button is pressed, and sets fast-forwarding state
|
||||
bool pressed = false;
|
||||
int port_num = port == SNES_PORT_1 ? 0 : 1;
|
||||
for (int i = 0; snes_keybinds[i].id != -1; i++)
|
||||
{
|
||||
if (snes_keybinds[i].id == SSNES_FAST_FORWARD_KEY)
|
||||
set_fast_forward_button(sdl_is_pressed(sdl, port_num, &snes_keybinds[i]));
|
||||
else if (!pressed && snes_keybinds[i].id == (int)id)
|
||||
pressed = sdl_is_pressed(sdl, port_num, &snes_keybinds[i]);
|
||||
}
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
static void sdl_input_free(void *data)
|
||||
{
|
||||
if (data)
|
||||
{
|
||||
sdl_input_t *sdl = data;
|
||||
for (int i = 0; i < sdl->num_joysticks; i++)
|
||||
SDL_JoystickClose(sdl->joysticks[i]);
|
||||
|
||||
free(data);
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
}
|
||||
|
||||
static void sdl_input_poll(void *data)
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
SDL_Event event;
|
||||
|
||||
sdl_input_t *sdl = data;
|
||||
// Search for events...
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
if (sdl->quitting)
|
||||
{
|
||||
*sdl->quitting = true;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_VIDEORESIZE:
|
||||
if (sdl->should_resize)
|
||||
{
|
||||
*sdl->new_width = event.resize.w;
|
||||
*sdl->new_height = event.resize.h;
|
||||
*sdl->should_resize = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const input_driver_t input_sdl = {
|
||||
.init = sdl_input_init,
|
||||
.poll = sdl_input_poll,
|
||||
.input_state = sdl_input_state,
|
||||
.key_pressed = sdl_key_pressed,
|
||||
.free = sdl_input_free,
|
||||
.ident = "sdl"
|
||||
};
|
||||
|
36
input/ssnes_sdl_input.h
Normal file
36
input/ssnes_sdl_input.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SSNES_SDL_INPUT_H
|
||||
#define __SSNES_SDL_INPUT_H
|
||||
|
||||
#include "SDL.h"
|
||||
typedef struct sdl_input
|
||||
{
|
||||
SDL_Joystick *joysticks[2];
|
||||
unsigned num_axes[2];
|
||||
unsigned num_buttons[2];
|
||||
unsigned num_joysticks;
|
||||
|
||||
// A video driver could pre-init with the SDL driver and have it handle resizing events...
|
||||
bool *quitting;
|
||||
bool *should_resize;
|
||||
unsigned *new_width;
|
||||
unsigned *new_height;
|
||||
} sdl_input_t;
|
||||
|
||||
#endif
|
@ -16,17 +16,29 @@ check_lib RSOUND -lrsound rsd_init
|
||||
check_lib ROAR -lroar roar_vs_new
|
||||
check_lib JACK -ljack jack_client_open
|
||||
|
||||
check_lib GLFW -lglfw glfwInit
|
||||
check_critical GLFW "Cannot find GLFW library."
|
||||
check_pkgconf SDL sdl 1.2.10
|
||||
check_critical SDL "Cannot find SDL library."
|
||||
|
||||
check_lib CG -lCg cgCreateContext
|
||||
check_pkgconf XML libxml-2.0
|
||||
|
||||
check_lib SRC -lsamplerate src_callback_new
|
||||
if [ $HAVE_FFMPEG != no ]; then
|
||||
check_pkgconf AVCODEC libavcodec
|
||||
check_pkgconf AVFORMAT libavformat
|
||||
check_pkgconf AVCORE libavcore
|
||||
check_pkgconf AVUTIL libavutil
|
||||
check_pkgconf SWSCALE libswscale
|
||||
|
||||
( [ $HAVE_FFMPEG = auto ] && ( [ $HAVE_AVCODEC = no ] || [ $HAVE_AVFORMAT = no ] || [ $HAVE_AVCORE = no ] || [ $HAVE_AVUTIL = no ] || [ $HAVE_SWSCALE = no ] ) && HAVE_FFMPEG=no ) || HAVE_FFMPEG=yes
|
||||
fi
|
||||
|
||||
check_pkgconf SRC samplerate
|
||||
check_critical SRC "Cannot find libsamplerate."
|
||||
|
||||
check_lib DYNAMIC -ldl dlopen
|
||||
|
||||
# Creates config.mk.
|
||||
VARS="ALSA OSS AL RSOUND ROAR JACK GLFW FILTER CG DYNAMIC"
|
||||
# Creates config.mk and config.h.
|
||||
VARS="ALSA OSS AL RSOUND ROAR JACK SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC"
|
||||
create_config_make config.mk $VARS
|
||||
create_config_header config.h $VARS
|
||||
|
||||
|
@ -9,8 +9,10 @@ PACKAGE_VERSION=0.1
|
||||
# $3: Default arg. auto implies that HAVE_ALSA will be set according to library checks later on.
|
||||
add_command_line_enable DYNAMIC "Enable dynamic loading of libsnes library." no
|
||||
add_command_line_string LIBSNES "libsnes library used" "-lsnes"
|
||||
add_command_line_enable FFMPEG "Enable FFmpeg recording support" auto
|
||||
add_command_line_enable FILTER "Disable CPU filter support" yes
|
||||
add_command_line_enable CG "Enable CG shader support" auto
|
||||
add_command_line_enable CG "Enable Cg shader support" auto
|
||||
add_command_line_enable XML "Enable bSNES-style XML shader support" auto
|
||||
add_command_line_enable ALSA "Enable ALSA support" auto
|
||||
add_command_line_enable OSS "Enable OSS support" auto
|
||||
add_command_line_enable RSOUND "Enable RSound support" auto
|
||||
|
@ -106,14 +106,15 @@ check_pkgconf()
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
echo -n "Checking presence of package $2 ... "
|
||||
echo -n "Checking presence of package $2"
|
||||
eval HAVE_$1=no
|
||||
eval $1_CFLAGS=""
|
||||
eval $1_LIBS=""
|
||||
answer=no
|
||||
minver=0.0
|
||||
[ ! -z $3 ] && minver=$3
|
||||
pkg-config --atleast-version=$minver --exists "$2" && eval HAVE_$1=yes && eval $1_CFLAGS='"`pkg-config $2 --cflags`"' && eval $1_LIBS='"`pkg-config $2 --libs`"' && answer=yes
|
||||
[ ! -z $3 ] && minver=$3 && echo -n " with minimum version $minver"
|
||||
echo -n " ... "
|
||||
pkg-config --atleast-version=$minver "$2" && eval HAVE_$1=yes && eval $1_CFLAGS='"`pkg-config $2 --cflags`"' && eval $1_LIBS='"`pkg-config $2 --libs`"' && answer=yes
|
||||
echo $answer
|
||||
|
||||
PKG_CONF_USED="$PKG_CONF_USED $1"
|
||||
|
412
record/ffemu.c
Normal file
412
record/ffemu.c
Normal file
@ -0,0 +1,412 @@
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/avstring.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "ffemu.h"
|
||||
|
||||
struct video_info
|
||||
{
|
||||
bool enabled;
|
||||
AVCodecContext *codec;
|
||||
|
||||
AVFrame *conv_frame;
|
||||
uint8_t *conv_frame_buf;
|
||||
int64_t frame_cnt;
|
||||
|
||||
uint8_t *outbuf;
|
||||
size_t outbuf_size;
|
||||
|
||||
AVFormatContext *format;
|
||||
|
||||
} video;
|
||||
|
||||
struct audio_info
|
||||
{
|
||||
bool enabled;
|
||||
AVCodecContext *codec;
|
||||
|
||||
int16_t *buffer;
|
||||
size_t frames_in_buffer;
|
||||
|
||||
int64_t frame_cnt;
|
||||
|
||||
void *outbuf;
|
||||
size_t outbuf_size;
|
||||
} audio;
|
||||
|
||||
struct muxer_info
|
||||
{
|
||||
AVFormatContext *ctx;
|
||||
AVStream *astream;
|
||||
AVStream *vstream;
|
||||
};
|
||||
|
||||
struct ffemu
|
||||
{
|
||||
struct video_info video;
|
||||
struct audio_info audio;
|
||||
struct muxer_info muxer;
|
||||
|
||||
struct ffemu_params params;
|
||||
};
|
||||
|
||||
// Currently hardcoded atm. :)
|
||||
static int map_audio_codec(ffemu_audio_codec codec)
|
||||
{
|
||||
(void)codec;
|
||||
return CODEC_ID_VORBIS;
|
||||
}
|
||||
|
||||
// Currently hardcoded atm. :)
|
||||
static int map_video_codec(ffemu_video_codec codec)
|
||||
{
|
||||
(void)codec;
|
||||
return CODEC_ID_H264;
|
||||
}
|
||||
|
||||
static int init_audio(struct audio_info *audio, struct ffemu_params *param)
|
||||
{
|
||||
AVCodec *codec = avcodec_find_encoder(map_audio_codec(param->acodec));
|
||||
if (!codec)
|
||||
return -1;
|
||||
|
||||
audio->codec = avcodec_alloc_context();
|
||||
|
||||
avcodec_get_context_defaults(audio->codec);
|
||||
|
||||
// Hardcode this atm.
|
||||
audio->codec->global_quality = 100000;
|
||||
audio->codec->flags |= CODEC_FLAG_QSCALE;
|
||||
audio->codec->sample_rate = param->samplerate;
|
||||
audio->codec->time_base = (AVRational) { 1, param->samplerate };
|
||||
audio->codec->channels = param->channels;
|
||||
audio->codec->sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
if (avcodec_open(audio->codec, codec) != 0)
|
||||
return -1;
|
||||
|
||||
|
||||
audio->buffer = av_malloc(audio->codec->frame_size * param->channels * sizeof(int16_t));
|
||||
if (!audio->buffer)
|
||||
return -1;
|
||||
|
||||
audio->outbuf_size = 50000;
|
||||
audio->outbuf = av_malloc(audio->outbuf_size);
|
||||
if (!audio->outbuf)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Hardcode.
|
||||
static void init_x264_param(AVCodecContext *c)
|
||||
{
|
||||
c->coder_type = 1; // coder = 1
|
||||
c->flags|=CODEC_FLAG_LOOP_FILTER; // flags=+loop
|
||||
c->me_cmp|= 1; // cmp=+chroma, where CHROMA = 1
|
||||
c->partitions|=X264_PART_I8X8+X264_PART_I4X4+X264_PART_P8X8+X264_PART_B8X8; // partitions=+parti8x8+parti4x4+partp8x8+partb8x8
|
||||
c->me_method=ME_HEX; // me_method=hex
|
||||
c->me_subpel_quality = 7; // subq=7
|
||||
c->me_range = 16; // me_range=16
|
||||
c->gop_size = 250; // g=250
|
||||
c->keyint_min = 25; // keyint_min=25
|
||||
c->scenechange_threshold = 40; // sc_threshold=40
|
||||
c->i_quant_factor = 0.71; // i_qfactor=0.71
|
||||
c->b_frame_strategy = 1; // b_strategy=1
|
||||
c->qcompress = 0.6; // qcomp=0.6
|
||||
c->qmin = 10; // qmin=10
|
||||
c->qmax = 51; // qmax=51
|
||||
c->max_qdiff = 4; // qdiff=4
|
||||
c->max_b_frames = 3; // bf=3
|
||||
c->refs = 3; // refs=3
|
||||
c->directpred = 1; // directpred=1
|
||||
c->trellis = 1; // trellis=1
|
||||
c->flags2|=CODEC_FLAG2_BPYRAMID+CODEC_FLAG2_MIXED_REFS+CODEC_FLAG2_WPRED+CODEC_FLAG2_8X8DCT+CODEC_FLAG2_FASTPSKIP; // flags2=+bpyramid+mixed_refs+wpred+dct8x8+fastpskip
|
||||
c->weighted_p_pred = 2; // wpredp=2
|
||||
|
||||
// libx264-main.ffpreset preset
|
||||
c->flags2|=CODEC_FLAG2_8X8DCT;
|
||||
c->flags2^=CODEC_FLAG2_8X8DCT;
|
||||
}
|
||||
|
||||
static int init_video(struct video_info *video, struct ffemu_params *param)
|
||||
{
|
||||
AVCodec *codec = avcodec_find_encoder(map_video_codec(param->vcodec));
|
||||
if (!codec)
|
||||
return -1;
|
||||
|
||||
video->codec = avcodec_alloc_context();
|
||||
video->codec->width = param->out_width;
|
||||
video->codec->height = param->out_height;
|
||||
video->codec->time_base = (AVRational) {param->fps.den, param->fps.num};
|
||||
video->codec->crf = 25;
|
||||
// Might have to change this later to account for RGB codecs.
|
||||
video->codec->pix_fmt = PIX_FMT_YUV420P;
|
||||
///// Is this element in all recent ffmpeg versions?
|
||||
video->codec->thread_count = 4;
|
||||
/////
|
||||
video->codec->sample_aspect_ratio = av_d2q(param->aspect_ratio * param->out_height / param->out_width, 255);
|
||||
init_x264_param(video->codec);
|
||||
|
||||
if (avcodec_open(video->codec, codec) != 0)
|
||||
return -1;
|
||||
|
||||
// Allocate a big buffer :p ffmpeg API doesn't seem to give us some clues how big this buffer should be.
|
||||
video->outbuf_size = 1000000;
|
||||
video->outbuf = av_malloc(video->outbuf_size);
|
||||
|
||||
int size = avpicture_get_size(PIX_FMT_YUV420P, param->out_width, param->out_height);
|
||||
video->conv_frame_buf = av_malloc(size);
|
||||
video->conv_frame = avcodec_alloc_frame();
|
||||
avpicture_fill((AVPicture*)video->conv_frame, video->conv_frame_buf, PIX_FMT_YUV420P, param->out_width, param->out_height);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init_muxer(ffemu_t *handle)
|
||||
{
|
||||
AVFormatContext *ctx = avformat_alloc_context();
|
||||
av_strlcpy(ctx->filename, handle->params.filename, sizeof(ctx->filename));
|
||||
ctx->oformat = av_guess_format(NULL, ctx->filename, NULL);
|
||||
if (url_fopen(&ctx->pb, ctx->filename, URL_WRONLY) < 0)
|
||||
{
|
||||
av_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int stream_cnt = 0;
|
||||
if (handle->video.enabled)
|
||||
{
|
||||
AVStream *stream = av_new_stream(ctx, stream_cnt++);
|
||||
stream->codec = handle->video.codec;
|
||||
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
handle->video.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
handle->muxer.vstream = stream;
|
||||
handle->muxer.vstream->sample_aspect_ratio = handle->video.codec->sample_aspect_ratio;
|
||||
}
|
||||
|
||||
if (handle->audio.enabled)
|
||||
{
|
||||
AVStream *stream = av_new_stream(ctx, stream_cnt++);
|
||||
stream->codec = handle->audio.codec;
|
||||
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
handle->audio.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
handle->muxer.astream = stream;
|
||||
}
|
||||
|
||||
if (av_write_header(ctx) < 0)
|
||||
return -1;
|
||||
|
||||
handle->muxer.ctx = ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ffemu_t *ffemu_new(const struct ffemu_params *params)
|
||||
{
|
||||
avcodec_init();
|
||||
av_register_all();
|
||||
|
||||
ffemu_t *handle = calloc(1, sizeof(*handle));
|
||||
if (!handle)
|
||||
goto error;
|
||||
|
||||
handle->params = *params;
|
||||
if (handle->params.vcodec != FFEMU_VIDEO_NONE)
|
||||
handle->video.enabled = true;
|
||||
if (handle->params.acodec != FFEMU_AUDIO_NONE)
|
||||
handle->audio.enabled = true;
|
||||
|
||||
if (handle->video.enabled)
|
||||
if (init_video(&handle->video, &handle->params) < 0)
|
||||
goto error;
|
||||
|
||||
if (handle->audio.enabled)
|
||||
if (init_audio(&handle->audio, &handle->params) < 0)
|
||||
goto error;
|
||||
|
||||
if (init_muxer(handle) < 0)
|
||||
goto error;
|
||||
|
||||
return handle;
|
||||
|
||||
error:
|
||||
ffemu_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ffemu_free(ffemu_t *handle)
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
if (handle->audio.codec)
|
||||
{
|
||||
avcodec_close(handle->audio.codec);
|
||||
av_free(handle->audio.codec);
|
||||
}
|
||||
|
||||
if (handle->audio.buffer)
|
||||
av_free(handle->audio.buffer);
|
||||
|
||||
if (handle->video.codec)
|
||||
{
|
||||
avcodec_close(handle->video.codec);
|
||||
av_free(handle->video.codec);
|
||||
}
|
||||
|
||||
if (handle->video.conv_frame)
|
||||
av_free(handle->video.conv_frame);
|
||||
|
||||
if (handle->video.conv_frame_buf)
|
||||
av_free(handle->video.conv_frame_buf);
|
||||
|
||||
free(handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Need to make this thread based, but hey.
|
||||
int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data)
|
||||
{
|
||||
if (!handle->video.enabled)
|
||||
return -1;
|
||||
|
||||
// This is deprecated, can't find a replacement... :(
|
||||
// Hardcode pixel format for now (SNES)
|
||||
struct SwsContext *conv_ctx = sws_getContext(data->width, data->height, PIX_FMT_RGB555LE,
|
||||
handle->params.out_width, handle->params.out_height, PIX_FMT_YUV420P, handle->params.rescaler == FFEMU_RESCALER_LANCZOS ? SWS_LANCZOS : SWS_POINT,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
int linesize = data->pitch;
|
||||
|
||||
sws_scale(conv_ctx, (const uint8_t* const*)&data->data, &linesize, 0, handle->params.out_width, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
|
||||
|
||||
handle->video.conv_frame->pts = handle->video.frame_cnt;
|
||||
handle->video.conv_frame->display_picture_number = handle->video.frame_cnt;
|
||||
|
||||
int outsize = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, handle->video.conv_frame);
|
||||
|
||||
if (outsize < 0)
|
||||
return -1;
|
||||
|
||||
sws_freeContext(conv_ctx);
|
||||
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.stream_index = handle->muxer.vstream->index;
|
||||
pkt.data = handle->video.outbuf;
|
||||
pkt.size = outsize;
|
||||
|
||||
pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base, handle->muxer.vstream->time_base);
|
||||
|
||||
if (handle->video.codec->coded_frame->key_frame)
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
|
||||
if (pkt.size > 0)
|
||||
{
|
||||
if (av_interleaved_write_frame(handle->muxer.ctx, &pkt) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
handle->video.frame_cnt++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
{
|
||||
if (!handle->audio.enabled)
|
||||
return -1;
|
||||
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.stream_index = handle->muxer.astream->index;
|
||||
pkt.data = handle->audio.outbuf;
|
||||
|
||||
size_t written_frames = 0;
|
||||
while (written_frames < data->frames)
|
||||
{
|
||||
size_t can_write = handle->audio.codec->frame_size - handle->audio.frames_in_buffer;
|
||||
size_t write_frames = data->frames > can_write ? can_write : data->frames;
|
||||
|
||||
memcpy(handle->audio.buffer + handle->audio.frames_in_buffer * handle->params.channels,
|
||||
data->data + written_frames * handle->params.channels,
|
||||
write_frames * handle->params.channels * sizeof(int16_t));
|
||||
|
||||
written_frames += write_frames;
|
||||
handle->audio.frames_in_buffer += write_frames;
|
||||
|
||||
if (handle->audio.frames_in_buffer == (size_t)handle->audio.codec->frame_size)
|
||||
{
|
||||
|
||||
int out_size = avcodec_encode_audio(handle->audio.codec, handle->audio.outbuf, handle->audio.outbuf_size, handle->audio.buffer);
|
||||
if (out_size < 0)
|
||||
return -1;
|
||||
|
||||
pkt.size = out_size;
|
||||
if (handle->audio.codec->coded_frame && handle->audio.codec->coded_frame->pts != AV_NOPTS_VALUE)
|
||||
{
|
||||
pkt.pts = av_rescale_q(handle->audio.codec->coded_frame->pts, handle->audio.codec->time_base, handle->muxer.astream->time_base);
|
||||
}
|
||||
else
|
||||
{
|
||||
pkt.pts = av_rescale_q(handle->audio.frame_cnt, handle->audio.codec->time_base, handle->muxer.astream->time_base);
|
||||
}
|
||||
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
handle->audio.frames_in_buffer = 0;
|
||||
handle->audio.frame_cnt += handle->audio.codec->frame_size;
|
||||
|
||||
if (pkt.size > 0)
|
||||
{
|
||||
if (av_interleaved_write_frame(handle->muxer.ctx, &pkt) < 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ffemu_finalize(ffemu_t *handle)
|
||||
{
|
||||
// Push out delayed frames. (MPEG codecs)
|
||||
if (handle->video.enabled)
|
||||
{
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.stream_index = handle->muxer.vstream->index;
|
||||
pkt.data = handle->video.outbuf;
|
||||
|
||||
int out_size = 0;
|
||||
do
|
||||
{
|
||||
out_size = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, NULL);
|
||||
pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base, handle->muxer.vstream->time_base);
|
||||
|
||||
if (handle->video.codec->coded_frame->key_frame)
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
|
||||
pkt.size = out_size;
|
||||
|
||||
if (pkt.size > 0)
|
||||
{
|
||||
int err = av_interleaved_write_frame(handle->muxer.ctx, &pkt);
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
} while (out_size > 0);
|
||||
}
|
||||
|
||||
// Write final data.
|
||||
av_write_trailer(handle->muxer.ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
127
record/ffemu.h
Normal file
127
record/ffemu.h
Normal file
@ -0,0 +1,127 @@
|
||||
#ifndef __FFEMU_H
|
||||
#define __FFEMU_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Available video codecs
|
||||
typedef enum ffemu_video_codec
|
||||
{
|
||||
FFEMU_VIDEO_NONE,
|
||||
FFEMU_VIDEO_H264,
|
||||
FFEMU_VIDEO_MPEG4,
|
||||
} ffemu_video_codec;
|
||||
|
||||
// Available audio codecs
|
||||
typedef enum ffemu_audio_codec
|
||||
{
|
||||
FFEMU_AUDIO_NONE,
|
||||
FFEMU_AUDIO_VORBIS,
|
||||
FFEMU_AUDIO_MP3,
|
||||
FFEMU_AUDIO_AAC,
|
||||
} ffemu_audio_codec;
|
||||
|
||||
// Available pixel formats
|
||||
typedef enum ffemu_pixel_format
|
||||
{
|
||||
FFEMU_FMT_XBGR1555,
|
||||
FFEMU_FMT_RGB888,
|
||||
} ffemu_pixel_format;
|
||||
|
||||
typedef enum ffemu_rescaler
|
||||
{
|
||||
FFEMU_RESCALER_LANCZOS,
|
||||
FFEMU_RESCALER_POINT
|
||||
} ffemu_rescaler;
|
||||
|
||||
struct ffemu_rational
|
||||
{
|
||||
unsigned num;
|
||||
unsigned den;
|
||||
};
|
||||
|
||||
// Parameters passed to ffemu_new()
|
||||
struct ffemu_params
|
||||
{
|
||||
// Video codec to use. If not recording video, select FFEMU_VIDEO_NONE.
|
||||
ffemu_video_codec vcodec;
|
||||
|
||||
// Desired output resolution.
|
||||
unsigned out_width;
|
||||
unsigned out_height;
|
||||
float aspect_ratio;
|
||||
|
||||
// Rescaler for video.
|
||||
ffemu_rescaler rescaler;
|
||||
|
||||
// Pixel format for video input.
|
||||
ffemu_pixel_format format;
|
||||
|
||||
// FPS of video input.
|
||||
struct ffemu_rational fps;
|
||||
|
||||
// Relative video quality. 0 is lossless (if available), 10 is very low quality.
|
||||
// A value over 10 is codec defined if it will give even worse quality.
|
||||
unsigned videoq;
|
||||
|
||||
// Define some video codec dependent option. (E.g. h264 profiles)
|
||||
uint64_t video_opt;
|
||||
|
||||
// Audio codec. If not recording audio, select FFEMU_AUDIO_NONE.
|
||||
ffemu_audio_codec acodec;
|
||||
|
||||
// Audio sample rate.
|
||||
unsigned samplerate;
|
||||
|
||||
// Audio channels.
|
||||
unsigned channels;
|
||||
|
||||
// Audio bits. Sample format is always signed PCM in native byte order.
|
||||
//unsigned bits;
|
||||
|
||||
// Relative audio quality. 0 is lossless (if available), 10 is very low quality.
|
||||
// A value over 10 is codec defined if it will give even worse quality.
|
||||
// Some codecs might ignore this (lossless codecs such as FLAC).
|
||||
unsigned audioq;
|
||||
|
||||
// Define some audio codec dependent option.
|
||||
uint64_t audio_opt;
|
||||
|
||||
// Filename to dump to.
|
||||
const char *filename;
|
||||
};
|
||||
|
||||
struct ffemu_video_data
|
||||
{
|
||||
const void *data;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned pitch;
|
||||
};
|
||||
|
||||
struct ffemu_audio_data
|
||||
{
|
||||
const int16_t *data;
|
||||
size_t frames;
|
||||
};
|
||||
|
||||
typedef struct ffemu ffemu_t;
|
||||
|
||||
ffemu_t *ffemu_new(const struct ffemu_params *params);
|
||||
void ffemu_free(ffemu_t* handle);
|
||||
|
||||
int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data);
|
||||
int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data);
|
||||
int ffemu_finalize(ffemu_t *handle);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
131
settings.c
131
settings.c
@ -21,7 +21,11 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "hqflt/filters.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
struct settings g_settings;
|
||||
@ -32,11 +36,12 @@ static void set_defaults(void)
|
||||
{
|
||||
const char *def_video = NULL;
|
||||
const char *def_audio = NULL;
|
||||
const char *def_input = NULL;
|
||||
|
||||
switch (VIDEO_DEFAULT_DRIVER)
|
||||
{
|
||||
case VIDEO_GL:
|
||||
def_video = "glfw";
|
||||
def_video = "gl";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -59,16 +64,28 @@ static void set_defaults(void)
|
||||
case AUDIO_AL:
|
||||
def_audio = "openal";
|
||||
break;
|
||||
case AUDIO_SDL:
|
||||
def_audio = "sdl";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// No input atm ... It is in the GLFW driver.
|
||||
switch (INPUT_DEFAULT_DRIVER)
|
||||
{
|
||||
case INPUT_SDL:
|
||||
def_input = "sdl";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (def_video)
|
||||
strncpy(g_settings.video.driver, def_video, sizeof(g_settings.video.driver) - 1);
|
||||
if (def_audio)
|
||||
strncpy(g_settings.audio.driver, def_audio, sizeof(g_settings.audio.driver) - 1);
|
||||
if (def_input)
|
||||
strncpy(g_settings.input.driver, def_input, sizeof(g_settings.input.driver) - 1);
|
||||
|
||||
g_settings.video.xscale = xscale;
|
||||
g_settings.video.yscale = yscale;
|
||||
@ -78,6 +95,7 @@ static void set_defaults(void)
|
||||
g_settings.video.vsync = vsync;
|
||||
g_settings.video.smooth = video_smooth;
|
||||
g_settings.video.force_aspect = force_aspect;
|
||||
g_settings.video.aspect_ratio = SNES_ASPECT_RATIO;
|
||||
|
||||
g_settings.audio.enable = audio_enable;
|
||||
g_settings.audio.out_rate = out_rate;
|
||||
@ -97,7 +115,7 @@ static void set_defaults(void)
|
||||
g_settings.input.load_state_key = LOAD_STATE_KEY;
|
||||
g_settings.input.toggle_fullscreen_key = TOGGLE_FULLSCREEN;
|
||||
g_settings.input.axis_threshold = AXIS_THRESHOLD;
|
||||
g_settings.input.exit_emulator_key = GLFW_KEY_ESC;
|
||||
g_settings.input.exit_emulator_key = SDLK_ESCAPE;
|
||||
}
|
||||
|
||||
void parse_config(void)
|
||||
@ -116,6 +134,10 @@ void parse_config(void)
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Just do something for now.
|
||||
conf = config_file_new("ssnes.cfg");
|
||||
#else
|
||||
const char *xdg = getenv("XDG_CONFIG_HOME");
|
||||
const char *home = getenv("HOME");
|
||||
if (xdg)
|
||||
@ -128,13 +150,14 @@ void parse_config(void)
|
||||
else if (home)
|
||||
{
|
||||
char conf_path[strlen(home) + strlen("/.ssnesrc ")];
|
||||
strcpy(conf_path, xdg);
|
||||
strcpy(conf_path, home);
|
||||
strcat(conf_path, "/.ssnesrc");
|
||||
conf = config_file_new(conf_path);
|
||||
}
|
||||
// Try this as a last chance...
|
||||
if (!conf)
|
||||
conf = config_file_new("/etc/ssnes.cfg");
|
||||
#endif
|
||||
}
|
||||
|
||||
set_defaults();
|
||||
@ -171,12 +194,21 @@ void parse_config(void)
|
||||
if (config_get_bool(conf, "video_force_aspect", &tmp_bool))
|
||||
g_settings.video.force_aspect = tmp_bool;
|
||||
|
||||
if (config_get_double(conf, "video_aspect_ratio", &tmp_double))
|
||||
g_settings.video.aspect_ratio = tmp_double;
|
||||
|
||||
if (config_get_string(conf, "video_cg_shader", &tmp_str))
|
||||
{
|
||||
strncpy(g_settings.video.cg_shader_path, tmp_str, sizeof(g_settings.video.cg_shader_path) - 1);
|
||||
free(tmp_str);
|
||||
}
|
||||
|
||||
if (config_get_string(conf, "video_bsnes_shader", &tmp_str))
|
||||
{
|
||||
strncpy(g_settings.video.bsnes_shader_path, tmp_str, sizeof(g_settings.video.bsnes_shader_path));
|
||||
free(tmp_str);
|
||||
}
|
||||
|
||||
#ifdef HAVE_FILTER
|
||||
if (config_get_string(conf, "video_filter", &tmp_str))
|
||||
{
|
||||
@ -255,6 +287,11 @@ void parse_config(void)
|
||||
strncpy(g_settings.audio.driver, tmp_str, sizeof(g_settings.audio.driver) - 1);
|
||||
free(tmp_str);
|
||||
}
|
||||
if (config_get_string(conf, "input_driver", &tmp_str))
|
||||
{
|
||||
strncpy(g_settings.input.driver, tmp_str, sizeof(g_settings.input.driver) - 1);
|
||||
free(tmp_str);
|
||||
}
|
||||
if (config_get_string(conf, "libsnes_path", &tmp_str))
|
||||
{
|
||||
strncpy(g_settings.libsnes, tmp_str, sizeof(g_settings.libsnes) - 1);
|
||||
@ -263,8 +300,6 @@ void parse_config(void)
|
||||
|
||||
read_keybinds(conf);
|
||||
|
||||
// TODO: Keybinds.
|
||||
|
||||
config_file_free(conf);
|
||||
}
|
||||
|
||||
@ -291,7 +326,7 @@ static const struct bind_map bind_maps[2][13] = {
|
||||
{ "input_player1_right", "input_player1_right_btn", "input_player1_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT },
|
||||
{ "input_player1_up", "input_player1_up_btn", "input_player1_up_axis", SNES_DEVICE_ID_JOYPAD_UP },
|
||||
{ "input_player1_down", "input_player1_down_btn", "input_player1_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN },
|
||||
{ "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SNES_FAST_FORWARD_KEY }
|
||||
{ "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SSNES_FAST_FORWARD_KEY }
|
||||
},
|
||||
{
|
||||
{ "input_player2_a", "input_player2_a_btn", NULL, SNES_DEVICE_ID_JOYPAD_A },
|
||||
@ -306,44 +341,44 @@ static const struct bind_map bind_maps[2][13] = {
|
||||
{ "input_player2_right", "input_player2_right_btn", "input_player2_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT },
|
||||
{ "input_player2_up", "input_player2_up_btn", "input_player2_up_axis", SNES_DEVICE_ID_JOYPAD_UP },
|
||||
{ "input_player2_down", "input_player2_down_btn", "input_player2_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN },
|
||||
{ "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SNES_FAST_FORWARD_KEY }
|
||||
{ "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SSNES_FAST_FORWARD_KEY }
|
||||
}
|
||||
};
|
||||
|
||||
struct glfw_map
|
||||
struct key_map
|
||||
{
|
||||
const char *str;
|
||||
int key;
|
||||
};
|
||||
|
||||
// Edit: Not portable to different input systems atm. Might move this map into the driver itself or something.
|
||||
static const struct glfw_map glfw_map[] = {
|
||||
{ "left", GLFW_KEY_LEFT },
|
||||
{ "right", GLFW_KEY_RIGHT },
|
||||
{ "up", GLFW_KEY_UP },
|
||||
{ "down", GLFW_KEY_DOWN },
|
||||
{ "enter", GLFW_KEY_ENTER },
|
||||
{ "tab", GLFW_KEY_TAB },
|
||||
{ "insert", GLFW_KEY_INSERT },
|
||||
{ "del", GLFW_KEY_DEL },
|
||||
{ "rshift", GLFW_KEY_RSHIFT },
|
||||
{ "shift", GLFW_KEY_LSHIFT },
|
||||
{ "ctrl", GLFW_KEY_LCTRL },
|
||||
{ "alt", GLFW_KEY_LALT },
|
||||
{ "space", GLFW_KEY_SPACE },
|
||||
{ "escape", GLFW_KEY_ESC },
|
||||
{ "f1", GLFW_KEY_F1 },
|
||||
{ "f2", GLFW_KEY_F2 },
|
||||
{ "f3", GLFW_KEY_F3 },
|
||||
{ "f4", GLFW_KEY_F4 },
|
||||
{ "f5", GLFW_KEY_F5 },
|
||||
{ "f6", GLFW_KEY_F6 },
|
||||
{ "f7", GLFW_KEY_F7 },
|
||||
{ "f8", GLFW_KEY_F8 },
|
||||
{ "f9", GLFW_KEY_F9 },
|
||||
{ "f10", GLFW_KEY_F10 },
|
||||
{ "f11", GLFW_KEY_F11 },
|
||||
{ "f12", GLFW_KEY_F12 },
|
||||
static const struct key_map sdlk_map[] = {
|
||||
{ "left", SDLK_LEFT },
|
||||
{ "right", SDLK_RIGHT },
|
||||
{ "up", SDLK_UP },
|
||||
{ "down", SDLK_DOWN },
|
||||
{ "enter", SDLK_RETURN },
|
||||
{ "tab", SDLK_TAB },
|
||||
{ "insert", SDLK_INSERT },
|
||||
{ "del", SDLK_DELETE },
|
||||
{ "rshift", SDLK_RSHIFT },
|
||||
{ "shift", SDLK_LSHIFT },
|
||||
{ "ctrl", SDLK_LCTRL },
|
||||
{ "alt", SDLK_LALT },
|
||||
{ "space", SDLK_SPACE },
|
||||
{ "escape", SDLK_ESCAPE },
|
||||
{ "f1", SDLK_F1 },
|
||||
{ "f2", SDLK_F2 },
|
||||
{ "f3", SDLK_F3 },
|
||||
{ "f4", SDLK_F4 },
|
||||
{ "f5", SDLK_F5 },
|
||||
{ "f6", SDLK_F6 },
|
||||
{ "f7", SDLK_F7 },
|
||||
{ "f8", SDLK_F8 },
|
||||
{ "f9", SDLK_F9 },
|
||||
{ "f10", SDLK_F10 },
|
||||
{ "f11", SDLK_F11 },
|
||||
{ "f12", SDLK_F12 },
|
||||
};
|
||||
|
||||
static struct snes_keybind *find_snes_bind(unsigned port, int id)
|
||||
@ -358,23 +393,23 @@ static struct snes_keybind *find_snes_bind(unsigned port, int id)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int find_glfw_bind(const char *str)
|
||||
static int find_sdlk_bind(const char *str)
|
||||
{
|
||||
for (int i = 0; i < sizeof(glfw_map)/sizeof(struct glfw_map); i++)
|
||||
for (int i = 0; i < sizeof(sdlk_map)/sizeof(struct key_map); i++)
|
||||
{
|
||||
if (strcasecmp(glfw_map[i].str, str) == 0)
|
||||
return glfw_map[i].key;
|
||||
if (strcasecmp(sdlk_map[i].str, str) == 0)
|
||||
return sdlk_map[i].key;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int find_glfw_key(const char *str)
|
||||
static int find_sdlk_key(const char *str)
|
||||
{
|
||||
// If the bind is a normal key-press ...
|
||||
if (strlen(str) == 1 && isalpha(*str))
|
||||
return toupper(*str);
|
||||
return (int)SDLK_a + (tolower(*str) - (int)'a');
|
||||
else // Check if we have a special mapping for it.
|
||||
return find_glfw_bind(str);
|
||||
return find_sdlk_bind(str);
|
||||
}
|
||||
|
||||
static void read_keybinds(config_file_t *conf)
|
||||
@ -393,7 +428,7 @@ static void read_keybinds(config_file_t *conf)
|
||||
|
||||
if (bind_maps[j][i].key && config_get_string(conf, bind_maps[j][i].key, &tmp_key))
|
||||
{
|
||||
int key = find_glfw_key(tmp_key);
|
||||
int key = find_sdlk_key(tmp_key);
|
||||
|
||||
if (key >= 0)
|
||||
bind->key = key;
|
||||
@ -428,28 +463,28 @@ static void read_keybinds(config_file_t *conf)
|
||||
char *tmp_str;
|
||||
if (config_get_string(conf, "input_toggle_fullscreen", &tmp_str))
|
||||
{
|
||||
int key = find_glfw_key(tmp_str);
|
||||
int key = find_sdlk_key(tmp_str);
|
||||
if (key >= 0)
|
||||
g_settings.input.toggle_fullscreen_key = key;
|
||||
free(tmp_str);
|
||||
}
|
||||
if (config_get_string(conf, "input_save_state", &tmp_str))
|
||||
{
|
||||
int key = find_glfw_key(tmp_str);
|
||||
int key = find_sdlk_key(tmp_str);
|
||||
if (key >= 0)
|
||||
g_settings.input.save_state_key = key;
|
||||
free(tmp_str);
|
||||
}
|
||||
if (config_get_string(conf, "input_load_state", &tmp_str))
|
||||
{
|
||||
int key = find_glfw_key(tmp_str);
|
||||
int key = find_sdlk_key(tmp_str);
|
||||
if (key >= 0)
|
||||
g_settings.input.load_state_key = key;
|
||||
free(tmp_str);
|
||||
}
|
||||
if (config_get_string(conf, "input_exit_emulator", &tmp_str))
|
||||
{
|
||||
int key = find_glfw_key(tmp_str);
|
||||
int key = find_sdlk_key(tmp_str);
|
||||
if (key >= 0)
|
||||
g_settings.input.exit_emulator_key = key;
|
||||
free(tmp_str);
|
||||
|
123
ssnes.c
123
ssnes.c
@ -17,8 +17,6 @@
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <GL/glfw.h>
|
||||
#include <samplerate.h>
|
||||
#include <libsnes.hpp>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -29,6 +27,11 @@
|
||||
#include "hqflt/filters.h"
|
||||
#include "general.h"
|
||||
#include "dynamic.h"
|
||||
#include "record/ffemu.h"
|
||||
#include <assert.h>
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
#endif
|
||||
|
||||
struct global g_extern = {
|
||||
.video_active = true,
|
||||
@ -83,6 +86,19 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
||||
if ( !g_extern.video_active )
|
||||
return;
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
if (g_extern.recording)
|
||||
{
|
||||
struct ffemu_video_data ffemu_data = {
|
||||
.data = data,
|
||||
.pitch = height == 448 || height == 478 ? 1024 : 2048,
|
||||
.width = width,
|
||||
.height = height
|
||||
};
|
||||
ffemu_push_video(g_extern.rec, &ffemu_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FILTER
|
||||
uint16_t output_filter[width * height * 4 * 4];
|
||||
uint16_t output[width * height];
|
||||
@ -130,6 +146,20 @@ static void audio_sample(uint16_t left, uint16_t right)
|
||||
if ( !g_extern.audio_active )
|
||||
return;
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
if (g_extern.recording)
|
||||
{
|
||||
static int16_t static_data[2];
|
||||
static_data[0] = left;
|
||||
static_data[1] = right;
|
||||
struct ffemu_audio_data ffemu_data = {
|
||||
.data = static_data,
|
||||
.frames = 1
|
||||
};
|
||||
ffemu_push_audio(g_extern.rec, &ffemu_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
static float data[AUDIO_CHUNK_SIZE_NONBLOCKING];
|
||||
static int data_ptr = 0;
|
||||
|
||||
@ -187,15 +217,31 @@ static void fill_pathname(char *out_path, char *in_path, const char *replace)
|
||||
strcat(out_path, replace);
|
||||
}
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
#define FFMPEG_HELP_QUARK " | -r/--record "
|
||||
#else
|
||||
#define FFMPEG_HELP_QUARK
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define SSNES_DEFAULT_CONF_PATH_STR "\n\tDefaults to ssnes.cfg in same directory as ssnes.exe"
|
||||
#else
|
||||
#define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg"
|
||||
#endif
|
||||
|
||||
static void print_help(void)
|
||||
{
|
||||
puts("=================================================");
|
||||
puts("ssnes: Simple Super Nintendo Emulator (libsnes)");
|
||||
puts("=================================================");
|
||||
puts("Usage: ssnes [rom file] [-h/--help | -s/--save]");
|
||||
puts("Usage: ssnes [rom file] [-h/--help | -s/--save" FFMPEG_HELP_QUARK "]");
|
||||
puts("\t-h/--help: Show this help message");
|
||||
puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin");
|
||||
puts("\t-c/--config: Path for config file. Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg");
|
||||
puts("\t-c/--config: Path for config file." SSNES_DEFAULT_CONF_PATH_STR);
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
puts("\t-r/--record: Path to record video file. Settings for video/audio codecs are found in config file.");
|
||||
#endif
|
||||
puts("\t-v/--verbose: Verbose logging");
|
||||
}
|
||||
|
||||
@ -210,13 +256,23 @@ static void parse_input(int argc, char *argv[])
|
||||
struct option opts[] = {
|
||||
{ "help", 0, NULL, 'h' },
|
||||
{ "save", 1, NULL, 's' },
|
||||
#ifdef HAVE_FFMPEG
|
||||
{ "record", 1, NULL, 'r' },
|
||||
#endif
|
||||
{ "verbose", 0, NULL, 'v' },
|
||||
{ "config", 0, NULL, 'c' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
int option_index = 0;
|
||||
char optstring[] = "hs:vc:";
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
#define FFMPEG_RECORD_ARG "r:"
|
||||
#else
|
||||
#define FFMPEG_RECORD_ARG
|
||||
#endif
|
||||
|
||||
char optstring[] = "hs:vc:" FFMPEG_RECORD_ARG;
|
||||
for(;;)
|
||||
{
|
||||
int c = getopt_long(argc, argv, optstring, opts, &option_index);
|
||||
@ -243,6 +299,13 @@ static void parse_input(int argc, char *argv[])
|
||||
strncpy(g_extern.config_path, optarg, sizeof(g_extern.config_path) - 1);
|
||||
break;
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
case 'r':
|
||||
strncpy(g_extern.record_path, optarg, sizeof(g_extern.record_path) - 1);
|
||||
g_extern.recording = true;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case '?':
|
||||
print_help();
|
||||
exit(1);
|
||||
@ -298,7 +361,7 @@ int main(int argc, char *argv[])
|
||||
SSNES_ERR("Could not read ROM file.\n");
|
||||
exit(1);
|
||||
}
|
||||
SSNES_LOG("ROM size: %zi bytes\n", rom_len);
|
||||
SSNES_LOG("ROM size: %d bytes\n", (int)rom_len);
|
||||
|
||||
if (g_extern.rom_file != NULL)
|
||||
fclose(g_extern.rom_file);
|
||||
@ -335,23 +398,49 @@ int main(int argc, char *argv[])
|
||||
load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
|
||||
load_save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
|
||||
|
||||
///// TODO: Modular friendly!!!
|
||||
#ifdef HAVE_FFMPEG
|
||||
// Hardcode these options at the moment. Should be specificed in the config file later on.
|
||||
if (g_extern.recording)
|
||||
{
|
||||
struct ffemu_rational ntsc_fps = {60000, 1001};
|
||||
struct ffemu_rational pal_fps = {50000, 1001};
|
||||
struct ffemu_params params = {
|
||||
.vcodec = FFEMU_VIDEO_H264,
|
||||
.acodec = FFEMU_AUDIO_VORBIS,
|
||||
.rescaler = FFEMU_RESCALER_POINT,
|
||||
.out_width = 512,
|
||||
.out_height = 448,
|
||||
.channels = 2,
|
||||
.samplerate = 32040,
|
||||
.filename = g_extern.record_path,
|
||||
.fps = snes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps,
|
||||
.aspect_ratio = 4.0/3
|
||||
};
|
||||
SSNES_LOG("Recording with FFmpeg to %s.\n", g_extern.record_path);
|
||||
g_extern.rec = ffemu_new(¶ms);
|
||||
if (!g_extern.rec)
|
||||
{
|
||||
SSNES_ERR("Failed to start FFmpeg recording.\n");
|
||||
g_extern.recording = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for(;;)
|
||||
{
|
||||
bool quitting = glfwGetKey(g_settings.input.exit_emulator_key) || !glfwGetWindowParam(GLFW_OPENED);
|
||||
|
||||
if ( quitting )
|
||||
if (driver.input->key_pressed(driver.input_data, g_settings.input.exit_emulator_key) ||
|
||||
!driver.video->alive(driver.video_data))
|
||||
break;
|
||||
|
||||
if ( glfwGetKey( g_settings.input.save_state_key ))
|
||||
if (driver.input->key_pressed(driver.input_data, g_settings.input.save_state_key))
|
||||
{
|
||||
write_file(statefile_name, serial_data, serial_size);
|
||||
}
|
||||
|
||||
else if ( glfwGetKey( g_settings.input.load_state_key ) )
|
||||
else if (driver.input->key_pressed(driver.input_data, g_settings.input.load_state_key))
|
||||
load_state(statefile_name, serial_data, serial_size);
|
||||
|
||||
else if ( glfwGetKey( g_settings.input.toggle_fullscreen_key ) )
|
||||
else if (driver.input->key_pressed(driver.input_data, g_settings.input.toggle_fullscreen_key))
|
||||
{
|
||||
g_settings.video.fullscreen = !g_settings.video.fullscreen;
|
||||
uninit_drivers();
|
||||
@ -361,6 +450,14 @@ int main(int argc, char *argv[])
|
||||
psnes_run();
|
||||
}
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
if (g_extern.recording)
|
||||
{
|
||||
ffemu_finalize(g_extern.rec);
|
||||
ffemu_free(g_extern.rec);
|
||||
}
|
||||
#endif
|
||||
|
||||
save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
|
||||
save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
|
||||
|
||||
|
23
ssnes.cfg
23
ssnes.cfg
@ -10,8 +10,8 @@
|
||||
# video_yscale = 3.0
|
||||
|
||||
# Fullscreen resolution
|
||||
# video_fullscreen_x = 1280
|
||||
# video_fullscreen_y = 720
|
||||
# video_fullscreen_x = 1920
|
||||
# video_fullscreen_y = 1200
|
||||
|
||||
# Start in fullscreen. Can be changed at runtime.
|
||||
# video_fullscreen = false
|
||||
@ -22,12 +22,18 @@
|
||||
# Smoothens picture with bilinear filtering. Should be disabled if using Cg shaders.
|
||||
# video_smooth = true
|
||||
|
||||
# Forces rendering area to stay 4:3.
|
||||
# Forces rendering area to stay equal to SNES aspect ratio 4:3 or as defined in video_aspect_ratio.
|
||||
# video_force_aspect = true
|
||||
|
||||
# A floating point value for video aspect ratio (width / height)
|
||||
# video_aspect_ratio = 1.333
|
||||
|
||||
# Path to Cg shader. If enabled
|
||||
# video_cg_shader = "/path/to/cg/shader.cg"
|
||||
|
||||
# Path to bSNES-style XML shader. If both Cg shader path and XML shader path are defined, Cg shader will take priority.
|
||||
# video_bsnes_shader = "/path/to/bsnes/xml/shader.shader"
|
||||
|
||||
# CPU-based filter. Valid ones are: hq2x, hq4x, grayscale, bleed, ntsc.
|
||||
# video_filter = ntsc
|
||||
|
||||
@ -43,10 +49,10 @@
|
||||
# Lower this (slightly) if you are experiencing frequent audio dropouts while vsync is enabled.
|
||||
# Conversely, increase this slightly if you are experiencing good audio,
|
||||
# but lots of dropped frames. Reasonable values for this is 32000 +/- 100 Hz.
|
||||
# audio_in_rate = 31950
|
||||
# audio_in_rate = 31980
|
||||
|
||||
# Audio driver backend. Depending on configuration possible candidates are: alsa, oss, rsound, roar, openal
|
||||
# audio_driver = alsa
|
||||
# Audio driver backend. Depending on configuration possible candidates are: alsa, oss, jack, rsound, roar, openal and sdl
|
||||
# audio_driver =
|
||||
|
||||
# Override the default audio device the audio_driver uses.
|
||||
# audio_device =
|
||||
@ -62,8 +68,11 @@
|
||||
|
||||
### Input
|
||||
|
||||
# Input driver. Depending on video driver, it might force a different input driver.
|
||||
# input_driver = sdl
|
||||
|
||||
# Defines axis threshold. Possible values are [0.0, 1.0]
|
||||
# input_axis_threshold = 0.6
|
||||
# input_axis_threshold = 0.5
|
||||
|
||||
# Keyboard input. Will recognize normal keypresses and special keys like "left", "right", and so on.
|
||||
# input_player1_a = x
|
||||
|
Loading…
x
Reference in New Issue
Block a user