mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b06a92c5 | ||
|
|
3901934d97 | ||
|
|
a76b25aff9 | ||
|
|
343852af26 | ||
|
|
4395f124ca | ||
|
|
b16ff3024c | ||
|
|
bc23b421e1 | ||
|
|
d9cb85a8d5 | ||
|
|
7d1cc23d16 | ||
|
|
4496ed3c13 | ||
|
|
3f5b6a450e | ||
|
|
9e24474047 | ||
|
|
7c17ede1ef | ||
|
|
5d9805c3e5 | ||
|
|
b17617bed5 | ||
|
|
a405708f1c | ||
|
|
594e66704d | ||
|
|
50579aaeea | ||
|
|
38eb855b48 | ||
|
|
12cc85c8a5 | ||
|
|
5b12595c85 | ||
|
|
3753f0bfca | ||
|
|
14ae5e397a | ||
|
|
657b4ce8c7 | ||
|
|
777e6a5334 | ||
|
|
a2ec78320b | ||
|
|
1fb53627ac | ||
|
|
fc2a39cd58 | ||
|
|
7425e1f879 | ||
|
|
6a260a57dd | ||
|
|
dadd794ece | ||
|
|
706a3738b5 | ||
|
|
9aef33aabd | ||
|
|
ac4219bcde | ||
|
|
b337856f80 | ||
|
|
079a0100c0 | ||
|
|
014c3c90e0 | ||
|
|
4759e3411f | ||
|
|
4a05e50636 | ||
|
|
72c070f338 | ||
|
|
776291a137 | ||
|
|
f3cdb95b04 | ||
|
|
033860ceb9 | ||
|
|
ab601ee3dc | ||
|
|
783ab7acd5 | ||
|
|
19a46b1f8b | ||
|
|
508cfdbda7 | ||
|
|
d81b9ee0a7 | ||
|
|
36c6c21194 | ||
|
|
1631e940f6 | ||
|
|
2eeb1fa6d1 | ||
|
|
26ac00ad76 | ||
|
|
bafc4f596f | ||
|
|
eee89dddb1 |
No files matched your search
@@ -3,3 +3,6 @@ Hans-Kristian Arntzen - <[email protected]>
|
||||
|
||||
Devin J. Pohly - <[email protected]>
|
||||
- Joypad axis support
|
||||
|
||||
Chris Moeller - <[email protected]>
|
||||
- CoreAudio audio driver
|
||||
@@ -40,7 +40,8 @@ endif
|
||||
|
||||
ifeq ($(HAVE_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
LIBS += -lrsound
|
||||
LIBS += $(RSOUND_LIBS)
|
||||
DEFINES += $(RSOUND_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_OSS), 1)
|
||||
@@ -82,6 +83,11 @@ ifeq ($(HAVE_PULSE), 1)
|
||||
DEFINES += $(PULSE_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_COREAUDIO), 1)
|
||||
OBJ += audio/coreaudio.o
|
||||
LIBS += -framework CoreServices -framework CoreAudio -framework AudioUnit
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o fifo_buffer.o
|
||||
DEFINES += $(SDL_CFLAGS) $(BSD_LOCAL_INC)
|
||||
|
||||
+8
-1
@@ -13,6 +13,7 @@ HAVE_SDL_IMAGE = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_DSOUND = 1
|
||||
HAVE_RSOUND = 1
|
||||
HAVE_DYLIB = 1
|
||||
HAVE_NETPLAY = 1
|
||||
@@ -59,6 +60,12 @@ ifeq ($(HAVE_XAUDIO), 1)
|
||||
DEFINES += -DHAVE_XAUDIO
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DSOUND), 1)
|
||||
OBJ += audio/dsound.o
|
||||
DEFINES += -DHAVE_DSOUND
|
||||
LIBS += -ldxguid -ldsound
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
DEFINES += -DHAVE_RSOUND
|
||||
@@ -155,7 +162,7 @@ clean:
|
||||
rm -f tools/*.o
|
||||
|
||||
dist: all
|
||||
zip -r ssnes-win32-0.6.1.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll freetype6.dll xaudio-c.dll rsound.dll pthreadGC2.dll cg.dll cgGL.dll libjpeg-8.dll libpng15-15.dll python32.dll SDL_image.dll $(JTARGET)
|
||||
zip -r ssnes-win32-0.7.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll freetype6.dll xaudio-c.dll rsound.dll pthreadGC2.dll cg.dll cgGL.dll libjpeg-8.dll libpng15-15.dll python32.dll SDL_image.dll $(JTARGET)
|
||||
|
||||
libs:
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win32-libs.zip --no-check-certificate
|
||||
|
||||
+8
-1
@@ -12,6 +12,7 @@ HAVE_SDL = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_DSOUND = 1
|
||||
HAVE_RSOUND = 0
|
||||
HAVE_DYLIB = 1
|
||||
HAVE_NETPLAY = 1
|
||||
@@ -52,6 +53,12 @@ ifeq ($(HAVE_XAUDIO), 1)
|
||||
DEFINES += -DHAVE_XAUDIO
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DSOUND), 1)
|
||||
OBJ += audio/dsound.o
|
||||
DEFINES += -DHAVE_DSOUND
|
||||
LIBS += -ldxguid -ldsound
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
DEFINES += -DHAVE_RSOUND
|
||||
@@ -142,7 +149,7 @@ clean:
|
||||
rm -f tools/*.o
|
||||
|
||||
dist: all
|
||||
zip -r ssnes-win64-0.6.1.zip $(TARGET) ssnes.cfg snes.dll xaudio-c.dll README.win32.txt $(JTARGET)
|
||||
zip -r ssnes-win64-0.7.zip $(TARGET) ssnes.cfg snes.dll xaudio-c.dll README.win32.txt $(JTARGET)
|
||||
|
||||
libs:
|
||||
wget https://github.com/downloads/Themaister/SSNES/ssnes-win64-libs.zip --no-check-certificate
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
* Copyright (C) 2011 - Chris Moeller
|
||||
*
|
||||
* 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 "general.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioUnit/AUComponent.h>
|
||||
|
||||
typedef struct coreaudio
|
||||
{
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond;
|
||||
|
||||
ComponentInstance dev;
|
||||
bool dev_alive;
|
||||
|
||||
fifo_buffer_t *buffer;
|
||||
bool nonblock;
|
||||
} coreaudio_t;
|
||||
|
||||
static void coreaudio_free(void *data)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
if (dev->dev_alive)
|
||||
{
|
||||
AudioOutputUnitStop(dev->dev);
|
||||
CloseComponent(dev->dev);
|
||||
}
|
||||
|
||||
if (dev->buffer)
|
||||
fifo_free(dev->buffer);
|
||||
|
||||
pthread_mutex_destroy(&dev->lock);
|
||||
pthread_cond_destroy(&dev->cond);
|
||||
|
||||
free(dev);
|
||||
}
|
||||
|
||||
static OSStatus audio_cb(void *userdata, AudioUnitRenderActionFlags *action_flags,
|
||||
const AudioTimeStamp *time_stamp, UInt32 bus_number,
|
||||
UInt32 number_frames, AudioBufferList *io_data)
|
||||
{
|
||||
coreaudio_t *dev = userdata;
|
||||
(void)time_stamp;
|
||||
(void)bus_number;
|
||||
(void)number_frames;
|
||||
|
||||
if (!io_data)
|
||||
return noErr;
|
||||
if (io_data->mNumberBuffers != 1)
|
||||
return noErr;
|
||||
|
||||
unsigned write_avail = io_data->mBuffers[0].mDataByteSize;
|
||||
void *outbuf = io_data->mBuffers[0].mData;
|
||||
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
if (fifo_read_avail(dev->buffer) < write_avail)
|
||||
{
|
||||
*action_flags = kAudioUnitRenderAction_OutputIsSilence;
|
||||
memset(outbuf, 0, write_avail); // Seems to be needed.
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
pthread_cond_signal(&dev->cond); // Technically possible to deadlock without.
|
||||
return noErr;
|
||||
}
|
||||
|
||||
fifo_read(dev->buffer, outbuf, write_avail);
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
pthread_cond_signal(&dev->cond);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static void* coreaudio_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
(void)device;
|
||||
|
||||
coreaudio_t *dev = calloc(1, sizeof(*dev));
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
CFRunLoopRef run_loop = NULL;
|
||||
AudioObjectPropertyAddress addr = {
|
||||
kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
pthread_mutex_init(&dev->lock, NULL);
|
||||
pthread_cond_init(&dev->cond, NULL);
|
||||
AudioObjectSetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL,
|
||||
sizeof(CFRunLoopRef), &run_loop);
|
||||
|
||||
ComponentDescription desc = {
|
||||
.componentType = kAudioUnitType_Output,
|
||||
.componentSubType = kAudioUnitSubType_HALOutput,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple,
|
||||
};
|
||||
|
||||
Component comp = FindNextComponent(NULL, &desc);
|
||||
if (comp == NULL)
|
||||
goto error;
|
||||
|
||||
OSStatus res = OpenAComponent(comp, &dev->dev);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
dev->dev_alive = true;
|
||||
|
||||
AudioStreamBasicDescription stream_desc = {
|
||||
.mSampleRate = rate,
|
||||
.mBitsPerChannel = sizeof(float) * CHAR_BIT,
|
||||
.mChannelsPerFrame = 2,
|
||||
.mBytesPerPacket = 2 * sizeof(float),
|
||||
.mBytesPerFrame = 2 * sizeof(float),
|
||||
.mFramesPerPacket = 1,
|
||||
.mFormatID = kAudioFormatLinearPCM,
|
||||
.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | (is_little_endian() ? 0 : kAudioFormatFlagIsBigEndian),
|
||||
};
|
||||
|
||||
res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &stream_desc, sizeof(stream_desc));
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
AudioStreamBasicDescription real_desc;
|
||||
UInt32 i_size = sizeof(real_desc);
|
||||
res = AudioUnitGetProperty(dev->dev, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &real_desc, &i_size);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
SSNES_LOG("[CoreAudio]: Using output sample rate of %.1f Hz\n", (float)real_desc.mSampleRate);
|
||||
g_settings.audio.out_rate = real_desc.mSampleRate;
|
||||
|
||||
if (real_desc.mChannelsPerFrame != stream_desc.mChannelsPerFrame)
|
||||
goto error;
|
||||
if (real_desc.mBitsPerChannel != stream_desc.mBitsPerChannel)
|
||||
goto error;
|
||||
if (real_desc.mFormatFlags != stream_desc.mFormatFlags)
|
||||
goto error;
|
||||
if (real_desc.mFormatID != stream_desc.mFormatID)
|
||||
goto error;
|
||||
|
||||
AudioChannelLayout layout = {
|
||||
.mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap,
|
||||
.mChannelBitmap = (1 << 2) - 1,
|
||||
};
|
||||
|
||||
res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_AudioChannelLayout,
|
||||
kAudioUnitScope_Input, 0, &layout, sizeof(layout));
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
AURenderCallbackStruct cb = {
|
||||
.inputProc = audio_cb,
|
||||
.inputProcRefCon = dev,
|
||||
};
|
||||
|
||||
res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &cb, sizeof(cb));
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
res = AudioUnitInitialize(dev->dev);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
size_t fifo_size = (latency * g_settings.audio.out_rate) / 1000;
|
||||
fifo_size *= 2 * sizeof(float);
|
||||
|
||||
dev->buffer = fifo_new(fifo_size);
|
||||
if (!dev->buffer)
|
||||
goto error;
|
||||
|
||||
SSNES_LOG("[CoreAudio]: Using buffer size of %u bytes: (latency = %u ms)\n", (unsigned)fifo_size, latency);
|
||||
|
||||
res = AudioOutputUnitStart(dev->dev);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
return dev;
|
||||
|
||||
error:
|
||||
SSNES_ERR("[CoreAudio]: Failed to initialize driver ...\n");
|
||||
coreaudio_free(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t coreaudio_write(void* data, const void* buf_, size_t size)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
|
||||
const uint8_t *buf = buf_;
|
||||
size_t written = 0;
|
||||
|
||||
while (size > 0)
|
||||
{
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
|
||||
size_t write_avail = fifo_write_avail(dev->buffer);
|
||||
if (write_avail > size)
|
||||
write_avail = size;
|
||||
|
||||
fifo_write(dev->buffer, buf, write_avail);
|
||||
buf += write_avail;
|
||||
written += write_avail;
|
||||
size -= write_avail;
|
||||
|
||||
if (dev->nonblock)
|
||||
{
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
break;
|
||||
}
|
||||
|
||||
if (write_avail == 0)
|
||||
pthread_cond_wait(&dev->cond, &dev->lock);
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
static bool coreaudio_stop(void *data)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
return AudioOutputUnitStop(dev->dev) == noErr;
|
||||
}
|
||||
|
||||
static void coreaudio_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
dev->nonblock = state;
|
||||
}
|
||||
|
||||
static bool coreaudio_start(void *data)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
return AudioOutputUnitStart(dev->dev) == noErr;
|
||||
}
|
||||
|
||||
static bool coreaudio_use_float(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
const audio_driver_t audio_coreaudio = {
|
||||
.init = coreaudio_init,
|
||||
.write = coreaudio_write,
|
||||
.stop = coreaudio_stop,
|
||||
.start = coreaudio_start,
|
||||
.set_nonblock_state = coreaudio_set_nonblock_state,
|
||||
.free = coreaudio_free,
|
||||
.use_float = coreaudio_use_float,
|
||||
.ident = "coreaudio"
|
||||
};
|
||||
|
||||
+387
@@ -0,0 +1,387 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "driver.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <dsound.h>
|
||||
#include "fifo_buffer.h"
|
||||
#include "general.h"
|
||||
|
||||
typedef struct dsound
|
||||
{
|
||||
LPDIRECTSOUND ds;
|
||||
LPDIRECTSOUNDBUFFER dsb;
|
||||
HANDLE event;
|
||||
bool nonblock;
|
||||
|
||||
fifo_buffer_t *buffer;
|
||||
CRITICAL_SECTION crit;
|
||||
|
||||
volatile bool thread_alive;
|
||||
HANDLE thread;
|
||||
unsigned buffer_size;
|
||||
} dsound_t;
|
||||
|
||||
static inline unsigned write_avail(unsigned read_ptr, unsigned write_ptr, unsigned buffer_size)
|
||||
{
|
||||
return (read_ptr + buffer_size - write_ptr) % buffer_size;
|
||||
}
|
||||
|
||||
static inline void get_positions(dsound_t *ds, DWORD *read_ptr, DWORD *write_ptr)
|
||||
{
|
||||
IDirectSoundBuffer_GetCurrentPosition(ds->dsb, read_ptr, write_ptr);
|
||||
}
|
||||
|
||||
#define CHUNK_SIZE 256
|
||||
|
||||
struct audio_lock
|
||||
{
|
||||
void *chunk1;
|
||||
DWORD size1;
|
||||
void *chunk2;
|
||||
DWORD size2;
|
||||
};
|
||||
|
||||
static inline bool grab_region(dsound_t *ds, DWORD write_ptr, struct audio_lock *region)
|
||||
{
|
||||
HRESULT res = IDirectSoundBuffer_Lock(ds->dsb, write_ptr, CHUNK_SIZE, ®ion->chunk1, ®ion->size1, ®ion->chunk2, ®ion->size2, 0);
|
||||
if (res == DSERR_BUFFERLOST)
|
||||
{
|
||||
res = IDirectSoundBuffer_Restore(ds->dsb);
|
||||
if (res != DS_OK)
|
||||
return false;
|
||||
|
||||
res = IDirectSoundBuffer_Lock(ds->dsb, write_ptr, CHUNK_SIZE, ®ion->chunk1, ®ion->size1, ®ion->chunk2, ®ion->size2, 0);
|
||||
if (res != DS_OK)
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *err;
|
||||
switch (res)
|
||||
{
|
||||
case DSERR_BUFFERLOST:
|
||||
err = "DSERR_BUFFERLOST";
|
||||
break;
|
||||
case DSERR_INVALIDCALL:
|
||||
err = "DSERR_INVALIDCALL";
|
||||
break;
|
||||
case DSERR_INVALIDPARAM:
|
||||
err = "DSERR_INVALIDPARAM";
|
||||
break;
|
||||
case DSERR_PRIOLEVELNEEDED:
|
||||
err = "DSERR_PRIOLEVELNEEDED";
|
||||
break;
|
||||
|
||||
default:
|
||||
err = NULL;
|
||||
}
|
||||
|
||||
if (err)
|
||||
{
|
||||
SSNES_WARN("[DirectSound error]: %s\n", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void release_region(dsound_t *ds, const struct audio_lock *region)
|
||||
{
|
||||
IDirectSoundBuffer_Unlock(ds->dsb, region->chunk1, region->size1, region->chunk2, region->size2);
|
||||
}
|
||||
|
||||
static DWORD CALLBACK dsound_thread(PVOID data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||||
|
||||
DWORD write_ptr;
|
||||
get_positions(ds, NULL, &write_ptr);
|
||||
write_ptr = (write_ptr + ds->buffer_size / 2) % ds->buffer_size;
|
||||
|
||||
while (ds->thread_alive)
|
||||
{
|
||||
DWORD read_ptr;
|
||||
get_positions(ds, &read_ptr, NULL);
|
||||
|
||||
DWORD avail = write_avail(read_ptr, write_ptr, ds->buffer_size);
|
||||
|
||||
EnterCriticalSection(&ds->crit);
|
||||
DWORD fifo_avail = fifo_read_avail(ds->buffer);
|
||||
LeaveCriticalSection(&ds->crit);
|
||||
|
||||
// No space to write, or we don't have data in our fifo, but we can wait some time before it underruns ...
|
||||
if (avail < CHUNK_SIZE || ((fifo_avail < CHUNK_SIZE) && (avail < ds->buffer_size / 2)))
|
||||
{
|
||||
Sleep(1);
|
||||
// We could opt for using the notification interface,
|
||||
// but it is not guaranteed to work, so use high priority sleeping patterns. :(
|
||||
}
|
||||
else if (fifo_avail < CHUNK_SIZE) // Got space to write, but nothing in FIFO (underrun), fill block with silence.
|
||||
{
|
||||
struct audio_lock region;
|
||||
if (!grab_region(ds, write_ptr, ®ion))
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
SetEvent(ds->event);
|
||||
break;
|
||||
}
|
||||
|
||||
memset(region.chunk1, 0, region.size1);
|
||||
memset(region.chunk2, 0, region.size2);
|
||||
|
||||
release_region(ds, ®ion);
|
||||
write_ptr = (write_ptr + region.size1 + region.size2) % ds->buffer_size;
|
||||
}
|
||||
else // All is good. Pull from it and notify FIFO :D
|
||||
{
|
||||
struct audio_lock region;
|
||||
if (!grab_region(ds, write_ptr, ®ion))
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
SetEvent(ds->event);
|
||||
break;
|
||||
}
|
||||
|
||||
EnterCriticalSection(&ds->crit);
|
||||
if (region.chunk1)
|
||||
fifo_read(ds->buffer, region.chunk1, region.size1);
|
||||
if (region.chunk2)
|
||||
fifo_read(ds->buffer, region.chunk2, region.size2);
|
||||
LeaveCriticalSection(&ds->crit);
|
||||
|
||||
release_region(ds, ®ion);
|
||||
write_ptr = (write_ptr + region.size1 + region.size2) % ds->buffer_size;
|
||||
|
||||
SetEvent(ds->event);
|
||||
}
|
||||
}
|
||||
|
||||
ExitThread(0);
|
||||
}
|
||||
|
||||
static void dsound_stop_thread(dsound_t *ds)
|
||||
{
|
||||
if (ds->thread)
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
WaitForSingleObject(ds->thread, INFINITE);
|
||||
CloseHandle(ds->thread);
|
||||
ds->thread = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool dsound_start_thread(dsound_t *ds)
|
||||
{
|
||||
if (!ds->thread)
|
||||
{
|
||||
ds->thread_alive = true;
|
||||
ds->thread = CreateThread(NULL, 0, dsound_thread, ds, 0, NULL);
|
||||
if (ds->thread == NULL)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dsound_clear_buffer(dsound_t *ds)
|
||||
{
|
||||
IDirectSoundBuffer_SetCurrentPosition(ds->dsb, 0);
|
||||
void *ptr;
|
||||
DWORD size;
|
||||
|
||||
if (IDirectSoundBuffer_Lock(ds->dsb, 0, 0, &ptr, &size, NULL, NULL, DSBLOCK_ENTIREBUFFER) == DS_OK)
|
||||
{
|
||||
memset(ptr, 0, size);
|
||||
IDirectSoundBuffer_Unlock(ds->dsb, ptr, size, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void dsound_free(void *data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
if (ds)
|
||||
{
|
||||
if (ds->thread)
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
WaitForSingleObject(ds->thread, INFINITE);
|
||||
CloseHandle(ds->thread);
|
||||
}
|
||||
|
||||
DeleteCriticalSection(&ds->crit);
|
||||
|
||||
if (ds->dsb)
|
||||
{
|
||||
IDirectSoundBuffer_Stop(ds->dsb);
|
||||
IDirectSoundBuffer_Release(ds->dsb);
|
||||
}
|
||||
|
||||
if (ds)
|
||||
IDirectSound_Release(ds->ds);
|
||||
|
||||
if (ds->event)
|
||||
CloseHandle(ds->event);
|
||||
|
||||
if (ds->buffer)
|
||||
fifo_free(ds->buffer);
|
||||
|
||||
free(ds);
|
||||
}
|
||||
}
|
||||
|
||||
static void* dsound_init(const char *device, unsigned rate, unsigned latency)
|
||||
{
|
||||
dsound_t *ds = calloc(1, sizeof(*ds));
|
||||
if (!ds)
|
||||
goto error;
|
||||
|
||||
InitializeCriticalSection(&ds->crit);
|
||||
|
||||
if (DirectSoundCreate(NULL, &ds->ds, NULL) != DS_OK)
|
||||
goto error;
|
||||
|
||||
if (IDirectSound_SetCooperativeLevel(ds->ds, GetDesktopWindow(), DSSCL_PRIORITY) != DS_OK)
|
||||
goto error;
|
||||
|
||||
WAVEFORMATEX wfx = {
|
||||
.wFormatTag = WAVE_FORMAT_PCM,
|
||||
.nChannels = 2,
|
||||
.nSamplesPerSec = rate,
|
||||
.wBitsPerSample = 16,
|
||||
.nBlockAlign = 2 * sizeof(int16_t),
|
||||
.nAvgBytesPerSec = rate * 2 * sizeof(int16_t),
|
||||
};
|
||||
|
||||
ds->buffer_size = (latency * wfx.nAvgBytesPerSec) / 1000;
|
||||
ds->buffer_size /= CHUNK_SIZE;
|
||||
ds->buffer_size *= CHUNK_SIZE;
|
||||
if (ds->buffer_size < 4 * CHUNK_SIZE)
|
||||
ds->buffer_size = 4 * CHUNK_SIZE;
|
||||
|
||||
SSNES_LOG("[DirectSound]: Setting buffer size of %u bytes\n", ds->buffer_size);
|
||||
SSNES_LOG("[DirectSound]: Latency = %u ms\n", (unsigned)((1000 * ds->buffer_size) / wfx.nAvgBytesPerSec));
|
||||
|
||||
DSBUFFERDESC bufdesc = {
|
||||
.dwSize = sizeof(DSBUFFERDESC),
|
||||
.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS,
|
||||
.dwBufferBytes = ds->buffer_size,
|
||||
.lpwfxFormat = &wfx,
|
||||
};
|
||||
|
||||
ds->event = CreateEvent(NULL, false, false, NULL);
|
||||
if (!ds->event)
|
||||
goto error;
|
||||
|
||||
ds->buffer = fifo_new(4 * 1024);
|
||||
if (!ds->buffer)
|
||||
goto error;
|
||||
|
||||
if (IDirectSound_CreateSoundBuffer(ds->ds, &bufdesc, &ds->dsb, 0) != DS_OK)
|
||||
goto error;
|
||||
|
||||
IDirectSoundBuffer_SetCurrentPosition(ds->dsb, 0);
|
||||
|
||||
dsound_clear_buffer(ds);
|
||||
|
||||
if (IDirectSoundBuffer_Play(ds->dsb, 0, 0, DSBPLAY_LOOPING) != DS_OK)
|
||||
goto error;
|
||||
|
||||
if (!dsound_start_thread(ds))
|
||||
goto error;
|
||||
|
||||
return ds;
|
||||
|
||||
error:
|
||||
SSNES_ERR("[DirectSound] Error occured in init!\n");
|
||||
dsound_free(ds);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool dsound_stop(void *data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
dsound_stop_thread(ds);
|
||||
return IDirectSoundBuffer_Stop(ds->dsb) == DS_OK;
|
||||
}
|
||||
|
||||
static bool dsound_start(void *data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
dsound_clear_buffer(ds);
|
||||
|
||||
if (!dsound_start_thread(ds))
|
||||
return false;
|
||||
|
||||
return IDirectSoundBuffer_Play(ds->dsb, 0, 0, DSBPLAY_LOOPING) == DS_OK;
|
||||
}
|
||||
|
||||
static void dsound_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
ds->nonblock = state;
|
||||
}
|
||||
|
||||
static ssize_t dsound_write(void *data, const void *buf_, size_t size)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
const uint8_t *buf = buf_;
|
||||
|
||||
if (!ds->thread_alive)
|
||||
return -1;
|
||||
|
||||
size_t written = 0;
|
||||
while (size > 0)
|
||||
{
|
||||
EnterCriticalSection(&ds->crit);
|
||||
size_t avail = fifo_write_avail(ds->buffer);
|
||||
if (avail > size)
|
||||
avail = size;
|
||||
|
||||
fifo_write(ds->buffer, buf, avail);
|
||||
LeaveCriticalSection(&ds->crit);
|
||||
|
||||
buf += avail;
|
||||
size -= avail;
|
||||
written += avail;
|
||||
|
||||
if (ds->nonblock || !ds->thread_alive)
|
||||
break;
|
||||
|
||||
if (avail == 0)
|
||||
WaitForSingleObject(ds->event, INFINITE);
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
const audio_driver_t audio_dsound = {
|
||||
.init = dsound_init,
|
||||
.write = dsound_write,
|
||||
.stop = dsound_stop,
|
||||
.start = dsound_start,
|
||||
.set_nonblock_state = dsound_set_nonblock_state,
|
||||
.free = dsound_free,
|
||||
.ident = "dsound"
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ extern "C" {
|
||||
#define SSNES_TRUE 1
|
||||
#endif
|
||||
|
||||
#define SSNES_DSP_API_VERSION 2
|
||||
#define SSNES_DSP_API_VERSION 3
|
||||
|
||||
typedef struct ssnes_dsp_info
|
||||
{
|
||||
@@ -94,7 +94,7 @@ typedef struct ssnes_dsp_plugin
|
||||
void (*process)(void *data, ssnes_dsp_output_t *output,
|
||||
const ssnes_dsp_input_t *input);
|
||||
|
||||
// Frees the handle.
|
||||
// Frees the handle.
|
||||
void (*free)(void *data);
|
||||
|
||||
// API version used to compile the plugin.
|
||||
@@ -110,6 +110,11 @@ typedef struct ssnes_dsp_plugin
|
||||
|
||||
// Human readable identification string.
|
||||
const char *ident;
|
||||
|
||||
// Called every frame, allows creating a GUI main loop in the main thread.
|
||||
// GUI events can be processed here in a non-blocking fashion.
|
||||
// Can be set to NULL to ignore it.
|
||||
void (*events)(void *data);
|
||||
} ssnes_dsp_plugin_t;
|
||||
|
||||
// Called by SSNES at startup to get the callback struct.
|
||||
|
||||
+17
-18
@@ -127,9 +127,12 @@ static size_t find_buffersize(jack_t *jd, int latency)
|
||||
static void* __jack_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
jack_t *jd = calloc(1, sizeof(jack_t));
|
||||
if ( jd == NULL )
|
||||
if (!jd)
|
||||
return NULL;
|
||||
|
||||
pthread_cond_init(&jd->cond, NULL);
|
||||
pthread_mutex_init(&jd->cond_lock, NULL);
|
||||
|
||||
const char **jports = NULL;
|
||||
|
||||
jd->client = jack_client_open("SSNES", JackNullOption, NULL);
|
||||
@@ -148,7 +151,6 @@ static void* __jack_init(const char* device, unsigned rate, unsigned latency)
|
||||
SSNES_ERR("Failed to register ports.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
char *dest_ports[2];
|
||||
jports = jack_get_ports(jd->client, NULL, NULL, JackPortIsPhysical | JackPortIsInput);
|
||||
@@ -158,6 +160,18 @@ static void* __jack_init(const char* device, unsigned rate, unsigned latency)
|
||||
goto error;
|
||||
}
|
||||
|
||||
size_t bufsize = find_buffersize(jd, latency);
|
||||
SSNES_LOG("JACK: Internal buffer size: %d frames.\n", (int)(bufsize / sizeof(jack_default_audio_sample_t)));
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
jd->buffer[i] = jack_ringbuffer_create(bufsize);
|
||||
if (jd->buffer[i] == NULL)
|
||||
{
|
||||
SSNES_ERR("Failed to create buffers.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
int parsed = parse_ports(dest_ports, jports);
|
||||
|
||||
if (jack_activate(jd->client) < 0)
|
||||
@@ -178,22 +192,7 @@ static void* __jack_init(const char* device, unsigned rate, unsigned latency)
|
||||
for (int i = 0; i < parsed; i++)
|
||||
free(dest_ports[i]);
|
||||
|
||||
size_t bufsize = find_buffersize(jd, latency);
|
||||
|
||||
SSNES_LOG("JACK: Internal buffer size: %d frames.\n", (int)(bufsize / sizeof(jack_default_audio_sample_t)));
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
jd->buffer[i] = jack_ringbuffer_create(bufsize);
|
||||
if (jd->buffer[i] == NULL)
|
||||
{
|
||||
SSNES_ERR("Failed to create buffers.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_cond_init(&jd->cond, NULL);
|
||||
pthread_mutex_init(&jd->cond_lock, NULL);
|
||||
|
||||
|
||||
jack_free(jports);
|
||||
return jd;
|
||||
|
||||
|
||||
+3
-1
@@ -20,6 +20,7 @@
|
||||
#endif
|
||||
|
||||
#include "driver.h"
|
||||
#include "general.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_OSS_BSD
|
||||
@@ -68,7 +69,8 @@ static void* __oss_init(const char* device, unsigned rate, unsigned latency)
|
||||
}
|
||||
|
||||
int channels = 2;
|
||||
int format = AFMT_S16_LE;
|
||||
int format = is_little_endian() ?
|
||||
AFMT_S16_LE : AFMT_S16_BE;
|
||||
|
||||
if (ioctl(*fd, SNDCTL_DSP_CHANNELS, &channels) < 0)
|
||||
{
|
||||
|
||||
@@ -59,18 +59,6 @@ static void __pulse_free(void *data)
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint8_t is_little_endian(void)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint16_t x;
|
||||
uint8_t y[2];
|
||||
} u;
|
||||
|
||||
u.x = 1;
|
||||
return u.y[0];
|
||||
}
|
||||
|
||||
static void context_state_cb(pa_context *c, void *data)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
|
||||
@@ -196,9 +196,4 @@ const audio_driver_t audio_sdl = {
|
||||
.free = sdl_audio_free,
|
||||
.ident = "sdl"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@
|
||||
#define AUDIO_XAUDIO 9
|
||||
#define AUDIO_PULSE 10
|
||||
#define AUDIO_EXT 15
|
||||
#define AUDIO_DSOUND 16
|
||||
#define AUDIO_COREAUDIO 17
|
||||
////////////////////////
|
||||
#define INPUT_SDL 7
|
||||
#define INPUT_X 12
|
||||
@@ -82,8 +84,12 @@
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_OSS
|
||||
#elif defined(HAVE_JACK)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_JACK
|
||||
#elif defined(HAVE_COREAUDIO)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_COREAUDIO
|
||||
#elif defined(HAVE_AL)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_AL
|
||||
#elif defined(HAVE_DSOUND)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_DSOUND
|
||||
#elif defined(HAVE_SDL)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_SDL
|
||||
#elif defined(HAVE_XAUDIO)
|
||||
@@ -123,6 +129,9 @@ static const unsigned fullscreen_y = 0;
|
||||
// Force 16-bit colors.
|
||||
static const bool force_16bit = false;
|
||||
|
||||
// Forcibly disable composition. Only valid on Windows Vista/7 for now.
|
||||
static const bool disable_composition = false;
|
||||
|
||||
// Video VSYNC (recommended)
|
||||
static const bool vsync = true;
|
||||
|
||||
@@ -148,6 +157,12 @@ static const float fbo_scale_x = 2.0;
|
||||
static const float fbo_scale_y = 2.0;
|
||||
static const bool second_pass_smooth = true;
|
||||
|
||||
// Record video assuming game runs hi-res.
|
||||
static const bool hires_record = false;
|
||||
|
||||
// Record post-filtered (CPU filter) video rather than raw SNES output.
|
||||
static const bool post_filter_record = false;
|
||||
|
||||
#define SNES_ASPECT_RATIO (4.0/3)
|
||||
|
||||
////////////////
|
||||
|
||||
@@ -40,6 +40,10 @@ Without this flag, the save state path will be inferred from the rom path name,
|
||||
When rom is loaded from \fBstdin\fR, this flag is mandatory to define as no path can be inferred.
|
||||
Do note that save states are bound to the libsnes implementation being used. Using a different libsnes could invalidate the save state file.
|
||||
|
||||
.TP
|
||||
\fB--fullscreen, -f\fR
|
||||
Always starts SSNES in fullscreen. Disregards settings in configuration file.
|
||||
|
||||
.TP
|
||||
\fB--config PATH, -c PATH\fR
|
||||
Sets the configuration file path. \fBssnes\fR will use this path to load the configuration file.
|
||||
|
||||
@@ -38,6 +38,9 @@ static const audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_RSOUND
|
||||
&audio_rsound,
|
||||
#endif
|
||||
#ifdef HAVE_COREAUDIO
|
||||
&audio_coreaudio,
|
||||
#endif
|
||||
#ifdef HAVE_AL
|
||||
&audio_openal,
|
||||
#endif
|
||||
@@ -53,6 +56,9 @@ static const audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_XAUDIO
|
||||
&audio_xa,
|
||||
#endif
|
||||
#ifdef HAVE_DSOUND
|
||||
&audio_dsound,
|
||||
#endif
|
||||
#ifdef HAVE_PULSE
|
||||
&audio_pulse,
|
||||
#endif
|
||||
@@ -162,7 +168,7 @@ static void init_dsp_plugin(void)
|
||||
}
|
||||
|
||||
const ssnes_dsp_plugin_t* (*SSNES_API_CALLTYPE plugin_init)(void) =
|
||||
(const ssnes_dsp_plugin_t *(*)(void))dylib_proc(g_extern.audio_data.dsp_lib, "ssnes_dsp_plugin_init");
|
||||
(const ssnes_dsp_plugin_t *(SSNES_API_CALLTYPE*)(void))dylib_proc(g_extern.audio_data.dsp_lib, "ssnes_dsp_plugin_init");
|
||||
if (!plugin_init)
|
||||
{
|
||||
SSNES_ERR("Failed to find symbol \"ssnes_dsp_plugin_init\" in DSP plugin.\n");
|
||||
@@ -184,7 +190,7 @@ static void init_dsp_plugin(void)
|
||||
|
||||
SSNES_LOG("Loaded DSP plugin: \"%s\"\n", g_extern.audio_data.dsp_plugin->ident ? g_extern.audio_data.dsp_plugin->ident : "Unknown");
|
||||
|
||||
ssnes_dsp_info_t info = {
|
||||
const ssnes_dsp_info_t info = {
|
||||
.input_rate = g_settings.audio.in_rate,
|
||||
.output_rate = g_settings.audio.out_rate
|
||||
};
|
||||
@@ -479,12 +485,12 @@ void init_video_input(void)
|
||||
|
||||
void uninit_video_input(void)
|
||||
{
|
||||
if ( driver.video_data && driver.video )
|
||||
driver.video->free(driver.video_data);
|
||||
|
||||
if ( driver.input_data != driver.video_data && driver.input )
|
||||
driver.input->free(driver.input_data);
|
||||
|
||||
if ( driver.video_data && driver.video )
|
||||
driver.video->free(driver.video_data);
|
||||
|
||||
deinit_filter();
|
||||
|
||||
#ifdef HAVE_XML
|
||||
|
||||
@@ -158,6 +158,8 @@ extern const audio_driver_t audio_sdl;
|
||||
extern const audio_driver_t audio_xa;
|
||||
extern const audio_driver_t audio_pulse;
|
||||
extern const audio_driver_t audio_ext;
|
||||
extern const audio_driver_t audio_dsound;
|
||||
extern const audio_driver_t audio_coreaudio;
|
||||
extern const video_driver_t video_gl;
|
||||
extern const video_driver_t video_xvideo;
|
||||
extern const video_driver_t video_sdl;
|
||||
|
||||
@@ -89,6 +89,10 @@ struct settings
|
||||
float msg_pos_y;
|
||||
|
||||
bool force_16bit;
|
||||
bool disable_composition;
|
||||
|
||||
bool hires_record;
|
||||
bool post_filter_record;
|
||||
|
||||
char external_driver[256];
|
||||
} video;
|
||||
@@ -147,6 +151,7 @@ struct global
|
||||
bool verbose;
|
||||
bool audio_active;
|
||||
bool video_active;
|
||||
bool force_fullscreen;
|
||||
|
||||
bool has_mouse[2];
|
||||
bool has_scope[2];
|
||||
@@ -304,6 +309,18 @@ static inline uint32_t next_pow2(uint32_t v)
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline uint8_t is_little_endian(void)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint16_t x;
|
||||
uint8_t y[2];
|
||||
} u;
|
||||
|
||||
u.x = 1;
|
||||
return u.y[0];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -224,6 +224,15 @@ static bool setup_video(ext_t *ext, const video_info_t *video, const input_drive
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *cg_shader = NULL;
|
||||
const char *xml_shader = NULL;
|
||||
enum ssnes_shader_type type = g_settings.video.shader_type;
|
||||
if ((type == SSNES_SHADER_CG || type == SSNES_SHADER_AUTO) && *g_settings.video.cg_shader_path)
|
||||
cg_shader = g_settings.video.cg_shader_path;
|
||||
else if ((type == SSNES_SHADER_BSNES || type == SSNES_SHADER_AUTO) && *g_settings.video.bsnes_shader_path)
|
||||
xml_shader = g_settings.video.bsnes_shader_path;
|
||||
|
||||
|
||||
ssnes_video_info_t info = {
|
||||
.width = video->width,
|
||||
.height = video->height,
|
||||
@@ -234,8 +243,8 @@ static bool setup_video(ext_t *ext, const video_info_t *video, const input_drive
|
||||
.smooth = video->smooth,
|
||||
.input_scale = video->input_scale,
|
||||
.color_format = video->rgb32 ? SSNES_COLOR_FORMAT_ARGB8888 : SSNES_COLOR_FORMAT_XRGB1555,
|
||||
.xml_shader = g_settings.video.bsnes_shader_path,
|
||||
.cg_shader = g_settings.video.cg_shader_path,
|
||||
.xml_shader = xml_shader,
|
||||
.cg_shader = cg_shader,
|
||||
.ttf_font = *g_settings.video.font_path ? g_settings.video.font_path : NULL,
|
||||
.ttf_font_size = g_settings.video.font_size
|
||||
};
|
||||
|
||||
+91
-6
@@ -25,21 +25,26 @@ static float tv_to_fps(const struct timeval *tv, const struct timeval *new_tv, i
|
||||
return frames/time;
|
||||
}
|
||||
|
||||
static unsigned gl_frames = 0;
|
||||
|
||||
void gfx_window_title_reset(void)
|
||||
{
|
||||
gl_frames = 0;
|
||||
}
|
||||
|
||||
bool gfx_window_title(char *buf, size_t size)
|
||||
{
|
||||
static int frames = 0;
|
||||
static struct timeval tv;
|
||||
struct timeval new_tv;
|
||||
bool ret = false;
|
||||
|
||||
if (frames == 0)
|
||||
if (gl_frames == 0)
|
||||
{
|
||||
gettimeofday(&tv, NULL);
|
||||
snprintf(buf, size, "%s", g_extern.title_buf);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if ((frames % 180) == 0 && frames > 0)
|
||||
else if ((gl_frames % 180) == 0)
|
||||
{
|
||||
gettimeofday(&new_tv, NULL);
|
||||
struct timeval tmp_tv = tv;
|
||||
@@ -47,12 +52,92 @@ bool gfx_window_title(char *buf, size_t size)
|
||||
|
||||
float fps = tv_to_fps(&tmp_tv, &new_tv, 180);
|
||||
|
||||
snprintf(buf, size, "%s || FPS: %6.1f || Frames: %d", g_extern.title_buf, fps, frames);
|
||||
snprintf(buf, size, "%s || FPS: %6.1f || Frames: %d", g_extern.title_buf, fps, gl_frames);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
frames++;
|
||||
gl_frames++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include "dynamic.h"
|
||||
// We only load this library once, so we let it be unloaded at application shutdown,
|
||||
// since unloading it early seems to cause issues on some systems.
|
||||
|
||||
static dylib_t dwmlib = NULL;
|
||||
|
||||
static void gfx_dwm_shutdown(void)
|
||||
{
|
||||
if (dwmlib)
|
||||
dylib_close(dwmlib);
|
||||
}
|
||||
|
||||
void gfx_set_dwm(void)
|
||||
{
|
||||
static bool inited = false;
|
||||
if (inited)
|
||||
return;
|
||||
inited = true;
|
||||
|
||||
dwmlib = dylib_load("dwmapi.dll");
|
||||
if (!dwmlib)
|
||||
{
|
||||
SSNES_LOG("Did not find dwmapi.dll");
|
||||
return;
|
||||
}
|
||||
atexit(gfx_dwm_shutdown);
|
||||
|
||||
HRESULT (WINAPI *mmcss)(BOOL) = (HRESULT (WINAPI*)(BOOL))dylib_proc(dwmlib, "DwmEnableMMCSS");
|
||||
if (mmcss)
|
||||
{
|
||||
SSNES_LOG("Setting multimedia scheduling for DWM.\n");
|
||||
mmcss(TRUE);
|
||||
}
|
||||
|
||||
if (!g_settings.video.disable_composition)
|
||||
return;
|
||||
|
||||
HRESULT (WINAPI *composition_enable)(UINT) = (HRESULT (WINAPI*)(UINT))dylib_proc(dwmlib, "DwmEnableComposition");
|
||||
if (!composition_enable)
|
||||
{
|
||||
SSNES_ERR("Did not find DwmEnableComposition ...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
HRESULT ret = composition_enable(0);
|
||||
if (FAILED(ret))
|
||||
SSNES_ERR("Failed to set composition state ...\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#include "SDL_syswm.h"
|
||||
#include "SDL.h"
|
||||
|
||||
void gfx_get_window_size(unsigned *width, unsigned *height)
|
||||
{
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
SDL_Event evnt;
|
||||
while (SDL_PollEvent(&evnt));
|
||||
const SDL_VideoInfo *info = SDL_GetVideoInfo();
|
||||
*width = info->current_w;
|
||||
*height = info->current_h;
|
||||
#else
|
||||
// It seems that we need to go hardcore to get the actual
|
||||
// window sizes properly right after startup ... :D
|
||||
SDL_SysWMinfo info;
|
||||
SDL_VERSION(&info.version);
|
||||
SDL_GetWMInfo(&info);
|
||||
XWindowAttributes target;
|
||||
info.info.x11.lock_func();
|
||||
XGetWindowAttributes(info.info.x11.display, info.info.x11.window,
|
||||
&target);
|
||||
info.info.x11.unlock_func();
|
||||
|
||||
*width = target.width;
|
||||
*height = target.height;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -22,5 +22,12 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
bool gfx_window_title(char *buf, size_t size);
|
||||
void gfx_window_title_reset(void);
|
||||
|
||||
#ifdef _WIN32
|
||||
void gfx_set_dwm(void);
|
||||
#endif
|
||||
|
||||
void gfx_get_window_size(unsigned *width, unsigned *height);
|
||||
|
||||
#endif
|
||||
@@ -125,7 +125,9 @@ static inline bool load_gl_proc(void) { return true; }
|
||||
typedef struct gl
|
||||
{
|
||||
bool vsync;
|
||||
GLuint texture;
|
||||
GLuint texture[2];
|
||||
unsigned tex_index; // For use with PREV.
|
||||
struct gl_tex_info prev_info;
|
||||
GLuint tex_filter;
|
||||
|
||||
void *empty_buf;
|
||||
@@ -153,8 +155,8 @@ typedef struct gl
|
||||
unsigned win_height;
|
||||
unsigned vp_width, vp_out_width;
|
||||
unsigned vp_height, vp_out_height;
|
||||
unsigned last_width;
|
||||
unsigned last_height;
|
||||
unsigned last_width[2];
|
||||
unsigned last_height[2];
|
||||
unsigned tex_w, tex_h;
|
||||
GLfloat tex_coords[8];
|
||||
#ifdef HAVE_FBO
|
||||
@@ -255,20 +257,21 @@ static void gl_shader_set_params(unsigned width, unsigned height,
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_count,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *prev_info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt)
|
||||
{
|
||||
#ifdef HAVE_CG
|
||||
gl_cg_set_params(width, height,
|
||||
tex_width, tex_height,
|
||||
out_width, out_height,
|
||||
frame_count, info, fbo_info, fbo_info_cnt);
|
||||
frame_count, info, prev_info, fbo_info, fbo_info_cnt);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XML
|
||||
gl_glsl_set_params(width, height,
|
||||
tex_width, tex_height,
|
||||
out_width, out_height,
|
||||
frame_count, info, fbo_info, fbo_info_cnt);
|
||||
frame_count, info, prev_info, fbo_info, fbo_info_cnt);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -338,7 +341,7 @@ static inline void gl_init_font(gl_t *gl, const char *font_path, unsigned font_s
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
}
|
||||
else
|
||||
SSNES_WARN("Couldn't init font renderer with font \"%s\"...\n", font_path);
|
||||
@@ -653,7 +656,7 @@ static void gl_render_msg(gl_t *gl, const char *msg)
|
||||
// Go back to old rendering path.
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords);
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes_flipped);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
glDisable(GL_BLEND);
|
||||
#endif
|
||||
}
|
||||
@@ -702,6 +705,19 @@ static void check_window(gl_t *gl)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gl->should_resize)
|
||||
{
|
||||
unsigned new_width, new_height;
|
||||
gfx_get_window_size(&new_width, &new_height);
|
||||
if ((new_width != gl->win_width || new_height != gl->win_height) || (gl->frame_count == 10)) // Ugly hack :D
|
||||
{
|
||||
gl->should_resize = true;
|
||||
gl->win_width = new_width;
|
||||
gl->win_height = new_height;
|
||||
SSNES_LOG("GL: Verified window size: %u x %u\n", gl->win_width, gl->win_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool gl_frame(void *data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
|
||||
@@ -712,6 +728,10 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
gl_shader_use(1);
|
||||
gl->frame_count++;
|
||||
|
||||
#if defined(HAVE_XML) || defined(HAVE_CG)
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
// Render to texture in first pass.
|
||||
if (gl->fbo_inited)
|
||||
@@ -767,7 +787,7 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
last_max_height = gl->fbo_rect[i].max_img_height;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
pglBindFramebuffer(GL_FRAMEBUFFER, gl->fbo[0]);
|
||||
gl->render_to_tex = true;
|
||||
set_viewport(gl, gl->fbo_rect[0].img_width, gl->fbo_rect[0].img_height, true);
|
||||
@@ -814,7 +834,7 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
}
|
||||
|
||||
// Go back to what we're supposed to do, render to FBO #0 :D
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
pglBindFramebuffer(GL_FRAMEBUFFER, gl->fbo[0]);
|
||||
set_viewport(gl, gl->fbo_rect[0].img_width, gl->fbo_rect[0].img_height, true);
|
||||
}
|
||||
@@ -823,10 +843,10 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
#endif
|
||||
}
|
||||
|
||||
if ((width != gl->last_width || height != gl->last_height) && gl->empty_buf) // Res change. need to clear out texture.
|
||||
if ((width != gl->last_width[gl->tex_index] || height != gl->last_height[gl->tex_index]) && gl->empty_buf) // Res change. need to clear out texture.
|
||||
{
|
||||
gl->last_width = width;
|
||||
gl->last_height = height;
|
||||
gl->last_width[gl->tex_index] = width;
|
||||
gl->last_height[gl->tex_index] = height;
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(pitch));
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->tex_w);
|
||||
|
||||
@@ -839,13 +859,13 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
|
||||
set_texture_coords(gl->tex_coords, xamt, yamt);
|
||||
}
|
||||
|
||||
// Work around a certain issue a Cg where not using TEXUNIT0
|
||||
// in shader causes cgGLEnableTextureParameter() causes it
|
||||
// to bind to TEXUNIT0, to avoid really funny bugs, rebind
|
||||
// our texture.
|
||||
#ifdef HAVE_CG
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
#if defined(HAVE_XML) || defined(HAVE_CG)
|
||||
else if (width != gl->last_width[1 - gl->tex_index] || height != gl->last_height[1 - gl->tex_index])
|
||||
{
|
||||
GLfloat xamt = (GLfloat)width / gl->tex_w;
|
||||
GLfloat yamt = (GLfloat)height / gl->tex_h;
|
||||
set_texture_coords(gl->tex_coords, xamt, yamt);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
@@ -861,7 +881,7 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
gl->texture_fmt, frame);
|
||||
|
||||
struct gl_tex_info tex_info = {
|
||||
.tex = gl->texture,
|
||||
.tex = gl->texture[gl->tex_index],
|
||||
.input_size = {width, height},
|
||||
.tex_size = {gl->tex_w, gl->tex_h}
|
||||
};
|
||||
@@ -871,7 +891,7 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl->vp_width, gl->vp_height, gl->frame_count,
|
||||
&tex_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
&tex_info, &gl->prev_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
|
||||
@@ -916,7 +936,7 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
gl_shader_set_params(prev_rect->img_width, prev_rect->img_height,
|
||||
prev_rect->width, prev_rect->height,
|
||||
gl->vp_width, gl->vp_height, gl->frame_count,
|
||||
&tex_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
&tex_info, &gl->prev_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
|
||||
@@ -942,7 +962,7 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
gl_shader_set_params(prev_rect->img_width, prev_rect->img_height,
|
||||
prev_rect->width, prev_rect->height,
|
||||
gl->vp_width, gl->vp_height, gl->frame_count,
|
||||
&tex_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
&tex_info, &gl->prev_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes_flipped);
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
@@ -951,6 +971,11 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_XML) || defined(HAVE_CG)
|
||||
memcpy(&gl->prev_info, &tex_info, sizeof(tex_info));
|
||||
gl->tex_index = 1 - gl->tex_index;
|
||||
#endif
|
||||
|
||||
if (msg)
|
||||
gl_render_msg(gl, msg);
|
||||
|
||||
@@ -970,7 +995,7 @@ static void gl_free(void *data)
|
||||
gl_shader_deinit();
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glDeleteTextures(1, &gl->texture);
|
||||
glDeleteTextures(2, gl->texture);
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
if (gl->fbo_inited)
|
||||
@@ -987,19 +1012,26 @@ static void gl_free(void *data)
|
||||
free(gl);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#endif
|
||||
|
||||
static void gl_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
gl_t *gl = data;
|
||||
if (gl->vsync)
|
||||
{
|
||||
SSNES_LOG("GL VSync => %s\n", state ? "off" : "on");
|
||||
#ifdef _WIN32
|
||||
#if defined(_WIN32)
|
||||
static BOOL (APIENTRY *wgl_swap_interval)(int) = NULL;
|
||||
if (!wgl_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(wgl_swap_interval, "wglSwapIntervalEXT");
|
||||
}
|
||||
if (wgl_swap_interval) wgl_swap_interval(state ? 0 : 1);
|
||||
#elif defined(__APPLE__)
|
||||
GLint val = state ? 0 : 1;
|
||||
CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &val);
|
||||
#else
|
||||
static int (*glx_swap_interval)(int) = NULL;
|
||||
if (!glx_swap_interval)
|
||||
@@ -1017,6 +1049,10 @@ static void gl_set_nonblock_state(void *data, bool state)
|
||||
|
||||
static void* gl_init(const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
gfx_set_dwm();
|
||||
#endif
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
return NULL;
|
||||
|
||||
@@ -1032,6 +1068,11 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
if (!SDL_SetVideoMode(video->width, video->height, g_settings.video.force_16bit ? 16 : 0, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
|
||||
return NULL;
|
||||
|
||||
gfx_window_title_reset();
|
||||
char buf[128];
|
||||
if (gfx_window_title(buf, sizeof(buf)))
|
||||
SDL_WM_SetCaption(buf, NULL);
|
||||
|
||||
// Remove that ugly mouse :D
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
@@ -1119,22 +1160,21 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
glColor4f(1, 1, 1, 1);
|
||||
glClearColor(0, 0, 0, 1);
|
||||
|
||||
char buf[128];
|
||||
if (gfx_window_title(buf, sizeof(buf)))
|
||||
SDL_WM_SetCaption(buf, NULL);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
glGenTextures(1, &gl->texture);
|
||||
glGenTextures(2, gl->texture);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[i]);
|
||||
|
||||
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_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);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl->tex_filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl->tex_filter);
|
||||
}
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
@@ -1148,15 +1188,30 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
gl->tex_w = 256 * video->input_scale;
|
||||
gl->tex_h = 256 * video->input_scale;
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RGBA, gl->tex_w, gl->tex_h, 0, gl->texture_type,
|
||||
gl->texture_fmt, NULL);
|
||||
|
||||
// Empty buffer that we use to clear out the texture with on res change.
|
||||
gl->empty_buf = calloc(gl->base_size, gl->tex_w * gl->tex_h);
|
||||
|
||||
gl->last_width = gl->tex_w;
|
||||
gl->last_height = gl->tex_h;
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[i]);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RGBA, gl->tex_w, gl->tex_h, 0, gl->texture_type,
|
||||
gl->texture_fmt, gl->empty_buf ? gl->empty_buf : NULL);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
gl->last_width[i] = gl->tex_w;
|
||||
gl->last_height[i] = gl->tex_h;
|
||||
}
|
||||
|
||||
gl->prev_info.tex = gl->texture[1 - gl->tex_index];
|
||||
gl->prev_info.input_size[0] = gl->tex_w;
|
||||
gl->prev_info.tex_size[0] = gl->tex_w;
|
||||
gl->prev_info.input_size[1] = gl->tex_h;
|
||||
gl->prev_info.tex_size[1] = gl->tex_h;
|
||||
memcpy(gl->prev_info.coord, tex_coords, sizeof(tex_coords));
|
||||
|
||||
// Hook up SDL input driver to get SDL_QUIT events and RESIZE.
|
||||
sdl_input_t *sdl_input = input_sdl.init();
|
||||
@@ -1215,7 +1270,7 @@ static bool gl_xml_shader(void *data, const char *path)
|
||||
gl->render_to_tex = false;
|
||||
gl->fbo_pass = 0;
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
+3
-3
@@ -101,9 +101,9 @@ struct gl_fbo_scale
|
||||
struct gl_tex_info
|
||||
{
|
||||
GLuint tex;
|
||||
float input_size[2];
|
||||
float tex_size[2];
|
||||
float coord[8];
|
||||
GLfloat input_size[2];
|
||||
GLfloat tex_size[2];
|
||||
GLfloat coord[8];
|
||||
};
|
||||
|
||||
// Not legal to cast void* to fn-pointer. Need dirty hack to be compilant.
|
||||
|
||||
@@ -213,6 +213,10 @@ static void sdl_render_msg_32(sdl_video_t *vid, SDL_Surface *buffer, const char
|
||||
|
||||
static void* sdl_gfx_init(const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
gfx_set_dwm();
|
||||
#endif
|
||||
|
||||
SDL_InitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
sdl_video_t *vid = calloc(1, sizeof(*vid));
|
||||
|
||||
+58
-8
@@ -36,18 +36,15 @@ static const char* stock_cg_program =
|
||||
"void main_vertex"
|
||||
"("
|
||||
" float4 position : POSITION,"
|
||||
" float4 color : COLOR,"
|
||||
" float2 texCoord : TEXCOORD0,"
|
||||
""
|
||||
" uniform float4x4 modelViewProj,"
|
||||
""
|
||||
" out float4 oPosition : POSITION,"
|
||||
" out float4 oColor : COLOR,"
|
||||
" out float2 otexCoord : TEXCOORD0"
|
||||
")"
|
||||
"{"
|
||||
" oPosition = mul(modelViewProj, position);"
|
||||
" oColor = color;"
|
||||
" otexCoord = texCoord;"
|
||||
"}"
|
||||
""
|
||||
@@ -112,6 +109,7 @@ struct cg_program
|
||||
|
||||
struct cg_fbo_params fbo[MAX_SHADERS];
|
||||
struct cg_fbo_params orig;
|
||||
struct cg_fbo_params prev;
|
||||
};
|
||||
|
||||
#define FILTER_UNSPEC 0
|
||||
@@ -148,6 +146,7 @@ void gl_cg_set_params(unsigned width, unsigned height,
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_count,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *prev_info,
|
||||
const struct gl_tex_info *fbo_info,
|
||||
unsigned fbo_info_cnt)
|
||||
{
|
||||
@@ -169,7 +168,6 @@ void gl_cg_set_params(unsigned width, unsigned height,
|
||||
if (param)
|
||||
{
|
||||
cgGLSetTextureParameter(param, info->tex);
|
||||
//fprintf(stderr, "ORIGtex = (%d) %d\n", cgGLGetTextureParameter(param), cgGLGetTextureEnum(param) - GL_TEXTURE0);
|
||||
cgGLEnableTextureParameter(param);
|
||||
}
|
||||
|
||||
@@ -183,6 +181,24 @@ void gl_cg_set_params(unsigned width, unsigned height,
|
||||
cgGLEnableClientState(prg[active_index].orig.coord);
|
||||
}
|
||||
|
||||
// Set prev texture
|
||||
param = prg[active_index].prev.tex;
|
||||
if (param)
|
||||
{
|
||||
cgGLSetTextureParameter(param, prev_info->tex);
|
||||
cgGLEnableTextureParameter(param);
|
||||
}
|
||||
|
||||
set_param_2f(prg[active_index].prev.vid_size_v, prev_info->input_size[0], prev_info->input_size[1]);
|
||||
set_param_2f(prg[active_index].prev.vid_size_f, prev_info->input_size[0], prev_info->input_size[1]);
|
||||
set_param_2f(prg[active_index].prev.tex_size_v, prev_info->tex_size[0], prev_info->tex_size[1]);
|
||||
set_param_2f(prg[active_index].prev.tex_size_f, prev_info->tex_size[0], prev_info->tex_size[1]);
|
||||
if (prg[active_index].prev.coord)
|
||||
{
|
||||
cgGLSetParameterPointer(prg[active_index].prev.coord, 2, GL_FLOAT, 0, prev_info->coord);
|
||||
cgGLEnableClientState(prg[active_index].prev.coord);
|
||||
}
|
||||
|
||||
// Set lookup textures.
|
||||
for (unsigned i = 0; i < lut_textures_num; i++)
|
||||
{
|
||||
@@ -191,7 +207,6 @@ void gl_cg_set_params(unsigned width, unsigned height,
|
||||
{
|
||||
cgGLSetTextureParameter(param, lut_textures[i]);
|
||||
cgGLEnableTextureParameter(param);
|
||||
//fprintf(stderr, "LUTtex = (%d) %d\n", cgGLGetTextureParameter(param), cgGLGetTextureEnum(param) - GL_TEXTURE0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,16 +288,31 @@ static bool load_plain(const char *path)
|
||||
if (strlen(g_settings.video.second_pass_shader) > 0)
|
||||
SSNES_LOG("Loading 2nd pass: %s\n", g_settings.video.second_pass_shader);
|
||||
|
||||
char *listing[3] = {NULL};
|
||||
const char *list = NULL;
|
||||
|
||||
prg[0].fprg = cgCreateProgram(cgCtx, CG_SOURCE, stock_cg_program, cgFProf, "main_fragment", 0);
|
||||
prg[0].vprg = cgCreateProgram(cgCtx, CG_SOURCE, stock_cg_program, cgVProf, "main_vertex", 0);
|
||||
|
||||
list = cgGetLastListing(cgCtx);
|
||||
if (list)
|
||||
listing[0] = strdup(list);
|
||||
|
||||
prg[1].fprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, path, cgFProf, "main_fragment", 0);
|
||||
prg[1].vprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, path, cgVProf, "main_vertex", 0);
|
||||
|
||||
list = cgGetLastListing(cgCtx);
|
||||
if (list)
|
||||
listing[1] = strdup(list);
|
||||
|
||||
if (strlen(g_settings.video.second_pass_shader) > 0)
|
||||
{
|
||||
prg[2].fprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, g_settings.video.second_pass_shader, cgFProf, "main_fragment", 0);
|
||||
prg[2].vprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, g_settings.video.second_pass_shader, cgVProf, "main_vertex", 0);
|
||||
|
||||
list = cgGetLastListing(cgCtx);
|
||||
if (list)
|
||||
listing[2] = strdup(list);
|
||||
cg_shader_num = 2;
|
||||
}
|
||||
else
|
||||
@@ -291,13 +321,15 @@ static bool load_plain(const char *path)
|
||||
cg_shader_num = 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < cg_shader_num + 1; i++)
|
||||
for (unsigned i = 0; i <= cg_shader_num; i++)
|
||||
{
|
||||
if (!prg[i].fprg || !prg[i].vprg)
|
||||
{
|
||||
CGerror err = cgGetError();
|
||||
SSNES_ERR("CG error: %s\n", cgGetErrorString(err));
|
||||
return false;
|
||||
if (listing[i])
|
||||
SSNES_ERR("%s\n", listing[i]);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cgGLLoadProgram(prg[i].fprg);
|
||||
@@ -305,6 +337,14 @@ static bool load_plain(const char *path)
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
for (unsigned i = 0; i < 3; i++)
|
||||
{
|
||||
if (listing[i])
|
||||
free(listing[i]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define print_buf(buf, ...) snprintf(buf, sizeof(buf), __VA_ARGS__)
|
||||
@@ -802,8 +842,11 @@ static bool load_preset(const char *path)
|
||||
|
||||
if (!prog->fprg || !prog->vprg)
|
||||
{
|
||||
const char *listing = cgGetLastListing(cgCtx);
|
||||
CGerror err = cgGetError();
|
||||
SSNES_ERR("CG error: %s\n", cgGetErrorString(err));
|
||||
if (listing)
|
||||
SSNES_ERR("%s\n", listing);
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -878,7 +921,7 @@ bool gl_cg_init(const char *path)
|
||||
if (prg[0].mvp)
|
||||
cgGLSetStateMatrixParameter(prg[0].mvp, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
|
||||
|
||||
for (unsigned i = 1; i < cg_shader_num + 1; i++)
|
||||
for (unsigned i = 1; i <= cg_shader_num; i++)
|
||||
{
|
||||
cgGLBindProgram(prg[i].fprg);
|
||||
cgGLBindProgram(prg[i].vprg);
|
||||
@@ -902,6 +945,13 @@ bool gl_cg_init(const char *path)
|
||||
prg[i].orig.tex_size_f = cgGetNamedParameter(prg[i].fprg, "ORIG.texture_size");
|
||||
prg[i].orig.coord = cgGetNamedParameter(prg[i].vprg, "ORIG.tex_coord");
|
||||
|
||||
prg[i].prev.tex = cgGetNamedParameter(prg[i].fprg, "PREV.texture");
|
||||
prg[i].prev.vid_size_v = cgGetNamedParameter(prg[i].vprg, "PREV.video_size");
|
||||
prg[i].prev.vid_size_f = cgGetNamedParameter(prg[i].fprg, "PREV.video_size");
|
||||
prg[i].prev.tex_size_v = cgGetNamedParameter(prg[i].vprg, "PREV.texture_size");
|
||||
prg[i].prev.tex_size_f = cgGetNamedParameter(prg[i].fprg, "PREV.texture_size");
|
||||
prg[i].prev.coord = cgGetNamedParameter(prg[i].vprg, "PREV.tex_coord");
|
||||
|
||||
for (unsigned j = 0; j < i - 1; j++)
|
||||
{
|
||||
char attr_buf[64];
|
||||
|
||||
@@ -33,6 +33,7 @@ void gl_cg_set_params(unsigned width, unsigned height,
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_count,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *prev_info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt);
|
||||
|
||||
void gl_cg_use(unsigned index);
|
||||
|
||||
+38
-10
@@ -712,8 +712,8 @@ error:
|
||||
|
||||
static void print_shader_log(GLuint obj)
|
||||
{
|
||||
int info_len = 0;
|
||||
int max_len;
|
||||
GLint info_len = 0;
|
||||
GLint max_len;
|
||||
|
||||
pglGetShaderiv(obj, GL_INFO_LOG_LENGTH, &max_len);
|
||||
|
||||
@@ -726,8 +726,8 @@ static void print_shader_log(GLuint obj)
|
||||
|
||||
static void print_linker_log(GLuint obj)
|
||||
{
|
||||
int info_len = 0;
|
||||
int max_len;
|
||||
GLint info_len = 0;
|
||||
GLint max_len;
|
||||
|
||||
pglGetProgramiv(obj, GL_INFO_LOG_LENGTH, &max_len);
|
||||
|
||||
@@ -1005,8 +1005,16 @@ void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_count,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *prev_info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt)
|
||||
{
|
||||
// We enforce a certain layout for our various texture types in the texunits.
|
||||
// Unit 0: Regular SNES frame (rubyTexture).
|
||||
// Unit 1-A: LUT textures.
|
||||
// Unit A+1: Previous texture.
|
||||
// Unit A+2: Original texture.
|
||||
// Unit A+3-B: FBO textures.
|
||||
|
||||
if (glsl_enable && gl_program[active_index] > 0)
|
||||
{
|
||||
GLint location;
|
||||
@@ -1032,15 +1040,34 @@ void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
pglUniform1i(location, i + 1);
|
||||
}
|
||||
|
||||
// Set previous texture.
|
||||
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 1);
|
||||
glBindTexture(GL_TEXTURE_2D, prev_info->tex);
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyPrevTexture");
|
||||
pglUniform1i(location, gl_teximage_cnt + 1);
|
||||
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyPrevTextureSize");
|
||||
pglUniform2fv(location, 1, prev_info->tex_size);
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyPrevInputSize");
|
||||
pglUniform2fv(location, 1, prev_info->input_size);
|
||||
|
||||
// Pass texture coordinates.
|
||||
location = pglGetAttribLocation(gl_program[active_index], "rubyPrevTexCoord");
|
||||
if (location >= 0)
|
||||
{
|
||||
pglEnableVertexAttribArray(location);
|
||||
pglVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, prev_info->coord);
|
||||
}
|
||||
|
||||
// Set original texture unless we're in first pass (pointless).
|
||||
if (active_index > 1)
|
||||
{
|
||||
// Bind original texture.
|
||||
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 1);
|
||||
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 2);
|
||||
glBindTexture(GL_TEXTURE_2D, info->tex);
|
||||
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyOrigTexture");
|
||||
pglUniform1i(location, gl_teximage_cnt + 1);
|
||||
pglUniform1i(location, gl_teximage_cnt + 2);
|
||||
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyOrigTextureSize");
|
||||
pglUniform2fv(location, 1, info->tex_size);
|
||||
@@ -1055,7 +1082,7 @@ void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
pglVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, info->coord);
|
||||
}
|
||||
|
||||
GLuint base_tex = GL_TEXTURE0 + gl_teximage_cnt + 2;
|
||||
GLuint base_tex = GL_TEXTURE0 + gl_teximage_cnt + 3;
|
||||
|
||||
// Bind new texture in the chain.
|
||||
if (fbo_info_cnt > 0)
|
||||
@@ -1093,14 +1120,15 @@ void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
else
|
||||
{
|
||||
// First pass, so unbind everything to avoid collitions.
|
||||
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 1);
|
||||
// Unbind ORIG.
|
||||
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 2);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
GLuint base_tex = GL_TEXTURE0 + gl_teximage_cnt + 2;
|
||||
GLuint base_tex = GL_TEXTURE0 + gl_teximage_cnt + 3;
|
||||
// Unbind any lurking FBO passes.
|
||||
// Rendering to a texture that is bound to a texture unit
|
||||
// sounds very shaky ... ;)
|
||||
for (int i = 0; i < gl_num_programs; i++)
|
||||
for (unsigned i = 0; i < gl_num_programs; i++)
|
||||
{
|
||||
pglActiveTexture(GL_TEXTURE0 + base_tex + i);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
@@ -33,6 +33,7 @@ void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_counter,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *prev_info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt);
|
||||
|
||||
void gl_glsl_use(unsigned index);
|
||||
|
||||
@@ -107,18 +107,6 @@ struct bsv_movie
|
||||
#define CRC_INDEX 2
|
||||
#define STATE_SIZE_INDEX 3
|
||||
|
||||
static inline uint8_t is_little_endian(void)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint16_t u16;
|
||||
uint8_t u8[2];
|
||||
} u;
|
||||
|
||||
u.u16 = 1;
|
||||
return u.u8[0];
|
||||
}
|
||||
|
||||
// Convert to big-endian if needed
|
||||
static inline uint32_t swap_if_big32(uint32_t val)
|
||||
{
|
||||
|
||||
+9
-2
@@ -46,8 +46,15 @@ check_pkgconf ROAR libroar
|
||||
check_pkgconf JACK jack 0.120.1
|
||||
check_pkgconf PULSE libpulse
|
||||
|
||||
check_lib COREAUDIO "-framework AudioUnit" AudioUnitInitialize
|
||||
|
||||
check_pkgconf SDL sdl 1.2.10
|
||||
check_critical SDL "Cannot find SDL library."
|
||||
check_critical SDL "Cannot find SDL 1.2 library."
|
||||
check_pkgconf SDL_NEW sdl 1.3
|
||||
if [ $HAVE_SDL_NEW = yes ]; then
|
||||
echo "SSNES is not compatible with SDL 1.3 API, please use a recent 1.2 release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_lib CG -lCg cgCreateContext
|
||||
check_pkgconf XML libxml-2.0
|
||||
@@ -76,7 +83,7 @@ check_pkgconf PYTHON python3
|
||||
add_define_make OS $OS
|
||||
|
||||
# Creates config.mk and config.h.
|
||||
VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK PULSE SDL DYLIB CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL PYTHON"
|
||||
VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL DYLIB CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL PYTHON"
|
||||
create_config_make config.mk $VARS
|
||||
create_config_header config.h $VARS
|
||||
|
||||
+2
-1
@@ -1,7 +1,7 @@
|
||||
. qb/qb.params.sh
|
||||
|
||||
PACKAGE_NAME=ssnes
|
||||
PACKAGE_VERSION=0.6.1
|
||||
PACKAGE_VERSION=0.7
|
||||
|
||||
# Adds a command line opt to ./configure --help
|
||||
# $1: Variable (HAVE_ALSA, HAVE_OSS, etc)
|
||||
@@ -23,6 +23,7 @@ add_command_line_enable RSOUND "Enable RSound support" auto
|
||||
add_command_line_enable ROAR "Enable RoarAudio support" auto
|
||||
add_command_line_enable AL "Enable OpenAL support" auto
|
||||
add_command_line_enable JACK "Enable JACK support" auto
|
||||
add_command_line_enable COREAUDIO "Enable CoreAudio support" auto
|
||||
add_command_line_enable PULSE "Enable PulseAudio support" auto
|
||||
add_command_line_enable FREETYPE "Enable FreeType support" auto
|
||||
add_command_line_enable XVIDEO "Enable XVideo support" auto
|
||||
|
||||
+30
-11
@@ -5,6 +5,7 @@
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavutil/avconfig.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
@@ -25,6 +26,9 @@ struct video_info
|
||||
uint8_t *outbuf;
|
||||
size_t outbuf_size;
|
||||
|
||||
int fmt;
|
||||
int pix_size;
|
||||
|
||||
AVFormatContext *format;
|
||||
|
||||
struct SwsContext *sws_ctx;
|
||||
@@ -41,8 +45,6 @@ struct audio_info
|
||||
|
||||
void *outbuf;
|
||||
size_t outbuf_size;
|
||||
|
||||
int pix_fmt;
|
||||
} audio;
|
||||
|
||||
struct muxer_info
|
||||
@@ -91,7 +93,7 @@ static bool init_audio(struct audio_info *audio, struct ffemu_params *param)
|
||||
if (!audio->buffer)
|
||||
return false;
|
||||
|
||||
audio->outbuf_size = 200000;
|
||||
audio->outbuf_size = 2000000;
|
||||
audio->outbuf = av_malloc(audio->outbuf_size);
|
||||
if (!audio->outbuf)
|
||||
return false;
|
||||
@@ -105,6 +107,18 @@ static bool init_video(struct video_info *video, struct ffemu_params *param)
|
||||
if (!codec)
|
||||
return false;
|
||||
|
||||
#if AV_HAVE_BIGENDIAN
|
||||
video->fmt = PIX_FMT_RGB555BE;
|
||||
#else
|
||||
video->fmt = PIX_FMT_RGB555LE;
|
||||
#endif
|
||||
video->pix_size = sizeof(uint16_t);
|
||||
if (param->rgb32)
|
||||
{
|
||||
video->fmt = PIX_FMT_RGB32;
|
||||
video->pix_size = sizeof(uint32_t);
|
||||
}
|
||||
|
||||
video->codec = avcodec_alloc_context();
|
||||
video->codec->width = param->out_width;
|
||||
video->codec->height = param->out_height;
|
||||
@@ -116,7 +130,7 @@ static bool init_video(struct video_info *video, struct ffemu_params *param)
|
||||
return false;
|
||||
|
||||
// Allocate a big buffer :p ffmpeg API doesn't seem to give us some clues how big this buffer should be.
|
||||
video->outbuf_size = 2000000;
|
||||
video->outbuf_size = 1 << 23;
|
||||
video->outbuf = av_malloc(video->outbuf_size);
|
||||
|
||||
// Just to make sure we can handle the biggest frames. Seemed to crash with just 256 * 224.
|
||||
@@ -177,7 +191,8 @@ static bool init_thread(ffemu_t *handle)
|
||||
assert(handle->cond = SDL_CreateCond());
|
||||
assert(handle->audio_fifo = fifo_new(32000 * sizeof(int16_t) * handle->params.channels * MAX_FRAMES / 60));
|
||||
assert(handle->attr_fifo = fifo_new(sizeof(struct ffemu_video_data) * MAX_FRAMES));
|
||||
assert(handle->video_fifo = fifo_new(handle->params.fb_width * handle->params.fb_height * sizeof(int16_t) * MAX_FRAMES));
|
||||
assert(handle->video_fifo = fifo_new(handle->params.fb_width * handle->params.fb_height *
|
||||
handle->video.pix_size * MAX_FRAMES));
|
||||
|
||||
handle->alive = true;
|
||||
handle->can_sleep = true;
|
||||
@@ -365,18 +380,20 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
|
||||
static int ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_data *data)
|
||||
{
|
||||
handle->video.sws_ctx = sws_getCachedContext(handle->video.sws_ctx, data->width, data->height, PIX_FMT_RGB555LE,
|
||||
handle->video.sws_ctx = sws_getCachedContext(handle->video.sws_ctx, data->width, data->height, handle->video.fmt,
|
||||
handle->params.out_width, handle->params.out_height, PIX_FMT_RGB32, SWS_POINT,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
int linesize = data->pitch;
|
||||
|
||||
sws_scale(handle->video.sws_ctx, (const uint8_t* const*)&data->data, &linesize, 0, handle->params.out_width, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
|
||||
sws_scale(handle->video.sws_ctx, (const uint8_t* const*)&data->data, &linesize, 0,
|
||||
handle->video.codec->height, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
|
||||
|
||||
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);
|
||||
int outsize = avcodec_encode_video(handle->video.codec, handle->video.outbuf,
|
||||
handle->video.outbuf_size, handle->video.conv_frame);
|
||||
|
||||
if (outsize < 0)
|
||||
return -1;
|
||||
@@ -387,7 +404,8 @@ static int ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_dat
|
||||
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);
|
||||
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;
|
||||
@@ -454,7 +472,7 @@ int ffemu_finalize(ffemu_t *handle)
|
||||
deinit_thread(handle);
|
||||
|
||||
// Push out frames still stuck in queue.
|
||||
uint16_t *video_buf = av_malloc(handle->params.fb_width * handle->params.fb_height * sizeof(uint16_t));
|
||||
void *video_buf = av_malloc(2 * handle->params.fb_width * handle->params.fb_height * handle->video.pix_size);
|
||||
if (video_buf)
|
||||
{
|
||||
struct ffemu_video_data attr_buf;
|
||||
@@ -524,7 +542,8 @@ static int SDLCALL ffemu_thread(void *data)
|
||||
{
|
||||
ffemu_t *ff = data;
|
||||
|
||||
uint16_t *video_buf = av_malloc(ff->params.fb_width * ff->params.fb_height * sizeof(uint16_t));
|
||||
// For some reason, FFmpeg has a tendency to crash if we don't overallocate a bit. :s
|
||||
void *video_buf = av_malloc(2 * ff->params.fb_width * ff->params.fb_height * ff->video.pix_size);
|
||||
assert(video_buf);
|
||||
|
||||
size_t audio_buf_size = 128 * ff->params.channels * sizeof(int16_t);
|
||||
|
||||
+3
-27
@@ -8,29 +8,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Available video codecs
|
||||
typedef enum ffemu_video_codec
|
||||
{
|
||||
FFEMU_VIDEO_FFV1,
|
||||
} ffemu_video_codec;
|
||||
|
||||
// Available audio codecs
|
||||
typedef enum ffemu_audio_codec
|
||||
{
|
||||
FFEMU_AUDIO_VORBIS,
|
||||
} ffemu_audio_codec;
|
||||
|
||||
// Available pixel formats
|
||||
typedef enum ffemu_pixel_format
|
||||
{
|
||||
FFEMU_FMT_XBGR1555,
|
||||
} ffemu_pixel_format;
|
||||
|
||||
typedef enum ffemu_rescaler
|
||||
{
|
||||
FFEMU_RESCALER_POINT
|
||||
} ffemu_rescaler;
|
||||
|
||||
struct ffemu_rational
|
||||
{
|
||||
unsigned num;
|
||||
@@ -40,9 +17,6 @@ struct ffemu_rational
|
||||
// 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;
|
||||
@@ -68,6 +42,9 @@ struct ffemu_params
|
||||
// Audio channels.
|
||||
unsigned channels;
|
||||
|
||||
// If input is ARGB or XRGB1555.
|
||||
bool rgb32;
|
||||
|
||||
// Audio bits. Sample format is always signed PCM in native byte order.
|
||||
//unsigned bits;
|
||||
|
||||
@@ -107,7 +84,6 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data);
|
||||
int ffemu_finalize(ffemu_t *handle);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+21
-2
@@ -71,12 +71,18 @@ static void set_defaults(void)
|
||||
case AUDIO_ROAR:
|
||||
def_audio = "roar";
|
||||
break;
|
||||
case AUDIO_COREAUDIO:
|
||||
def_audio = "coreaudio";
|
||||
break;
|
||||
case AUDIO_AL:
|
||||
def_audio = "openal";
|
||||
break;
|
||||
case AUDIO_SDL:
|
||||
def_audio = "sdl";
|
||||
break;
|
||||
case AUDIO_DSOUND:
|
||||
def_audio = "dsound";
|
||||
break;
|
||||
case AUDIO_XAUDIO:
|
||||
def_audio = "xaudio";
|
||||
break;
|
||||
@@ -111,10 +117,11 @@ static void set_defaults(void)
|
||||
|
||||
g_settings.video.xscale = xscale;
|
||||
g_settings.video.yscale = yscale;
|
||||
g_settings.video.fullscreen = fullscreen;
|
||||
g_settings.video.fullscreen = g_extern.force_fullscreen ? true : fullscreen;
|
||||
g_settings.video.fullscreen_x = fullscreen_x;
|
||||
g_settings.video.fullscreen_y = fullscreen_y;
|
||||
g_settings.video.force_16bit = force_16bit;
|
||||
g_settings.video.disable_composition = disable_composition;
|
||||
g_settings.video.vsync = vsync;
|
||||
g_settings.video.smooth = video_smooth;
|
||||
g_settings.video.force_aspect = force_aspect;
|
||||
@@ -135,6 +142,9 @@ static void set_defaults(void)
|
||||
g_settings.video.second_pass_smooth = second_pass_smooth;
|
||||
#endif
|
||||
|
||||
g_settings.video.hires_record = hires_record;
|
||||
g_settings.video.post_filter_record = post_filter_record;
|
||||
|
||||
g_settings.audio.enable = audio_enable;
|
||||
g_settings.audio.out_rate = out_rate;
|
||||
g_settings.audio.in_rate = in_rate;
|
||||
@@ -289,8 +299,14 @@ static void parse_config_file(void)
|
||||
CONFIG_GET_DOUBLE(video.yscale, "video_yscale");
|
||||
CONFIG_GET_INT(video.fullscreen_x, "video_fullscreen_x");
|
||||
CONFIG_GET_INT(video.fullscreen_y, "video_fullscreen_y");
|
||||
CONFIG_GET_BOOL(video.fullscreen, "video_fullscreen");
|
||||
|
||||
if (!g_extern.force_fullscreen)
|
||||
{
|
||||
CONFIG_GET_BOOL(video.fullscreen, "video_fullscreen");
|
||||
}
|
||||
|
||||
CONFIG_GET_BOOL(video.force_16bit, "video_force_16bit");
|
||||
CONFIG_GET_BOOL(video.disable_composition, "video_disable_composition");
|
||||
CONFIG_GET_BOOL(video.vsync, "video_vsync");
|
||||
CONFIG_GET_BOOL(video.smooth, "video_smooth");
|
||||
CONFIG_GET_BOOL(video.force_aspect, "video_force_aspect");
|
||||
@@ -312,6 +328,9 @@ static void parse_config_file(void)
|
||||
CONFIG_GET_DOUBLE(video.msg_pos_y, "video_message_pos_y");
|
||||
#endif
|
||||
|
||||
CONFIG_GET_BOOL(video.hires_record, "video_hires_record");
|
||||
CONFIG_GET_BOOL(video.post_filter_record, "video_post_filter_record");
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
CONFIG_GET_STRING(video.filter_path, "video_filter");
|
||||
CONFIG_GET_STRING(video.external_driver, "video_external_driver");
|
||||
|
||||
@@ -142,14 +142,16 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
||||
if (g_extern.do_screenshot)
|
||||
take_screenshot(data, width, height);
|
||||
|
||||
// Slightly messy code,
|
||||
// but we really need to do processing before blocking on VSync for best possible scheduling.
|
||||
#ifdef HAVE_FFMPEG
|
||||
if (g_extern.recording)
|
||||
if (g_extern.recording && (!g_extern.filter.active || !g_settings.video.post_filter_record))
|
||||
{
|
||||
struct ffemu_video_data ffemu_data = {
|
||||
.data = data,
|
||||
.pitch = height == 448 || height == 478 ? 1024 : 2048,
|
||||
.width = width,
|
||||
.height = height
|
||||
.height = height,
|
||||
};
|
||||
ffemu_push_video(g_extern.rec, &ffemu_data);
|
||||
}
|
||||
@@ -165,6 +167,20 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
||||
g_extern.filter.psize(&owidth, &oheight);
|
||||
g_extern.filter.prender(g_extern.filter.colormap, g_extern.filter.buffer,
|
||||
g_extern.filter.pitch, data, (height == 448 || height == 478) ? 1024 : 2048, width, height);
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
if (g_extern.recording && g_settings.video.post_filter_record)
|
||||
{
|
||||
struct ffemu_video_data ffemu_data = {
|
||||
.data = g_extern.filter.buffer,
|
||||
.pitch = g_extern.filter.pitch,
|
||||
.width = owidth,
|
||||
.height = oheight,
|
||||
};
|
||||
ffemu_push_video(g_extern.rec, &ffemu_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!driver.video->frame(driver.video_data, g_extern.filter.buffer, owidth, oheight, g_extern.filter.pitch, msg))
|
||||
g_extern.video_active = false;
|
||||
}
|
||||
@@ -340,7 +356,7 @@ static void fill_pathname_noext(char *out_path, const char *in_path, const char
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define PACKAGE_VERSION "0.6.1"
|
||||
#define PACKAGE_VERSION "0.7"
|
||||
#endif
|
||||
|
||||
#include "config.features.h"
|
||||
@@ -383,6 +399,7 @@ static void print_help(void)
|
||||
puts("Usage: ssnes [rom file] [options...]");
|
||||
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-f/--fullscreen: Start SSNES in fullscreen regardless of config settings.");
|
||||
puts("\t-S/--savestate: Path to use for save states. If not selected, *.state will be assumed.");
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
puts("\t-c/--config: Path for config file." SSNES_DEFAULT_CONF_PATH_STR);
|
||||
@@ -441,6 +458,7 @@ static void parse_input(int argc, char *argv[])
|
||||
struct option opts[] = {
|
||||
{ "help", 0, NULL, 'h' },
|
||||
{ "save", 1, NULL, 's' },
|
||||
{ "fullscreen", 0, NULL, 'f' },
|
||||
#ifdef HAVE_FFMPEG
|
||||
{ "record", 1, NULL, 'r' },
|
||||
#endif
|
||||
@@ -485,7 +503,7 @@ static void parse_input(int argc, char *argv[])
|
||||
#define CONFIG_FILE_ARG
|
||||
#endif
|
||||
|
||||
char optstring[] = "hs:vS:m:p4jJg:b:B:Y:Z:P:HC:F:U:DN:X:" FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
|
||||
char optstring[] = "hs:fvS:m:p4jJg:b:B:Y:Z:P:HC:F:U:DN:X:" FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
|
||||
for(;;)
|
||||
{
|
||||
val = 0;
|
||||
@@ -518,6 +536,10 @@ static void parse_input(int argc, char *argv[])
|
||||
g_extern.has_set_save_path = true;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
g_extern.force_fullscreen = true;
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
strlcpy(g_extern.gb_rom_path, optarg, sizeof(g_extern.gb_rom_path));
|
||||
g_extern.game_type = SSNES_CART_SGB;
|
||||
@@ -786,9 +808,11 @@ static inline void save_files(void)
|
||||
#ifdef HAVE_FFMPEG
|
||||
static void init_recording(void)
|
||||
{
|
||||
// Hardcode these options at the moment. Should be specificed in the config file later on.
|
||||
if (g_extern.recording)
|
||||
{
|
||||
// Not perfectly SNES accurate, but this can be adjusted later during processing
|
||||
// and playback losslessly, and we please the containers more by
|
||||
// using "sane" values.
|
||||
struct ffemu_rational ntsc_fps = {60000, 1000};
|
||||
struct ffemu_rational pal_fps = {50000, 1000};
|
||||
struct ffemu_params params = {
|
||||
@@ -800,9 +824,29 @@ static void init_recording(void)
|
||||
.samplerate = 32000,
|
||||
.filename = g_extern.record_path,
|
||||
.fps = psnes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps,
|
||||
.aspect_ratio = 4.0/3
|
||||
.aspect_ratio = 4.0 / 3,
|
||||
.rgb32 = false,
|
||||
};
|
||||
SSNES_LOG("Recording with FFmpeg to %s.\n", g_extern.record_path);
|
||||
|
||||
if (g_settings.video.hires_record)
|
||||
{
|
||||
params.out_width = 512;
|
||||
params.out_height = 448;
|
||||
}
|
||||
|
||||
if (g_settings.video.post_filter_record && g_extern.filter.active)
|
||||
{
|
||||
g_extern.filter.psize(¶ms.out_width, ¶ms.out_height);
|
||||
params.rgb32 = true;
|
||||
|
||||
unsigned max_width = 512;
|
||||
unsigned max_height = 512;
|
||||
g_extern.filter.psize(&max_width, &max_height);
|
||||
params.fb_width = g_extern.filter.pitch >> 2;
|
||||
params.fb_height = next_pow2(max_height);
|
||||
}
|
||||
|
||||
SSNES_LOG("Recording with FFmpeg to %s @ %ux%u. (FB size: %ux%u 32-bit: %s)\n", g_extern.record_path, params.out_width, params.out_height, params.fb_width, params.fb_height, params.rgb32 ? "yes" : "no");
|
||||
g_extern.rec = ffemu_new(¶ms);
|
||||
if (!g_extern.rec)
|
||||
{
|
||||
@@ -1529,6 +1573,10 @@ int main(int argc, char *argv[])
|
||||
// Main loop
|
||||
for(;;)
|
||||
{
|
||||
// DSP plugin GUI events.
|
||||
if (g_extern.audio_data.dsp_handle && g_extern.audio_data.dsp_plugin->events)
|
||||
g_extern.audio_data.dsp_plugin->events(g_extern.audio_data.dsp_handle);
|
||||
|
||||
// Time to drop?
|
||||
if (driver.input->key_pressed(driver.input_data, SSNES_QUIT_KEY) ||
|
||||
!driver.video->alive(driver.video_data) || g_extern.bsv_movie_end)
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
# Force 16-bit colors. Apparently some video cards in use today have troubles with 32-bit ...
|
||||
# video_force_16bit = false
|
||||
|
||||
# Forcibly disable composition. Only works in Windows Vista/7 for now.
|
||||
# video_disable_composition = false
|
||||
|
||||
# Video vsync.
|
||||
# video_vsync = true
|
||||
|
||||
@@ -290,3 +293,9 @@
|
||||
|
||||
# Directory to dump screenshots to.
|
||||
# screenshot_directory =
|
||||
|
||||
# Records video assuming video is hi-res.
|
||||
# video_hires_record = false
|
||||
|
||||
# Records video after CPU video filter.
|
||||
# video_post_filter_record = false
|
||||
Reference in new issue
Block a user