mirror of https://github.com/bsnes-emu/bsnes.git
Update to v103r17 release.
byuu says: Changelog: - tomoko: re-hid the video sync option¹ - tomoko: removed " Settings" duplication on all the individual settings tab options - ruby/audio/wasapi: finished port to new syntax; adapted to an event-driven model; support 32-bit integral audio² - ruby/video/sdl: ported to new syntax; disabled driver on FreeBSD³ ¹: still contemplating a synchronize submenu of {none, video, audio}, but ... the fact that video can't work on PAL, WonderSwan games is a real limitation for it ²: this driver actually received a ton of work. There's also a new ring-buffer queue, and I added special handling for when exclusive mode fails because the latency requested is lower than the hardware can support. It'll pick the closest latency to the minimum that is possible in this case. On my Audigy Rx, the results for non-exclusive mode are the same. For exclusive mode, the framerate drops from 60fps to ~50fps for smaller buffers, and ~55fps for larger buffers (no matter how big, it never hits 60fps.) This is a lot better than before where it was hitting ~15fps, but unfortunately it's the best I can do. The event system used by WASAPI is really stupid. It just uses SetEvent at some arbitrary time, and you have to query to see how many samples it's waiting on. This makes it unknowable how many samples we should buffer before calling `WaitForSingleObject(INFINITE)`, and it's also unclear how we should handle cases where there's more samples available than our queue has: either we can fill it with zeroes, or we can write less samples. The former should prevent audio looping effects when running too slowly, whereas the latter could potentially be too ambitious when the audio could've recovered from a minor stall. It's shocking to me how there's as many ways to send audio to a sound card as there are sound card APIs, when all that's needed is a simple double buffer and a callback event from another thread to do it right. It's also terrifying how unbelievably shitty nearly all sound card drivers apparently are. Also, I don't know if cards can output an actual 24-bit mode with three byte audio samples, or if they always just take 32-bit samples and ignore the lower 8-bits. Whatever, it's all nonsense for the final output to be >16-bits anyway (hi, `double[]` input from ruby.) ³: unfortunately, this driver always crashes on FreeBSD (even before the rewrite), so I'll need someone on Linux to test it and make sure it actually works. I'll also need testing for a lot of the other drivers as well, once they're ported over (I don't have X-video, PulseAudio, ALSA, or udev.) Note that I forgot to set `_ready=true` at the end of `initialize()`, and `_ready=false` in `terminate()`, but it shouldn't actually matter beyond showing you a false warning message on startup about it failing to initialize.
This commit is contained in:
parent
f87c6b7ecb
commit
0b4e7fb5a5
|
@ -12,7 +12,7 @@ using namespace nall;
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const string Name = "higan";
|
static const string Name = "higan";
|
||||||
static const string Version = "103.16";
|
static const string Version = "103.17";
|
||||||
static const string Author = "byuu";
|
static const string Author = "byuu";
|
||||||
static const string License = "GPLv3";
|
static const string License = "GPLv3";
|
||||||
static const string Website = "http://byuu.org/";
|
static const string Website = "http://byuu.org/";
|
||||||
|
|
|
@ -19,18 +19,18 @@ ui_objects += $(if $(call streq,$(platform),windows),ui-resource)
|
||||||
# platform
|
# platform
|
||||||
ifeq ($(platform),windows)
|
ifeq ($(platform),windows)
|
||||||
ruby += video.direct3d #video.wgl video.directdraw video.gdi
|
ruby += video.direct3d #video.wgl video.directdraw video.gdi
|
||||||
ruby += audio.asio #audio.wasapi audio.xaudio2 audio.directsound
|
ruby += audio.asio audio.wasapi #audio.xaudio2 audio.directsound
|
||||||
ruby += input.windows
|
ruby += input.windows
|
||||||
else ifeq ($(platform),macosx)
|
else ifeq ($(platform),macosx)
|
||||||
ruby += #video.cgl
|
ruby += #video.cgl
|
||||||
ruby += audio.openal
|
ruby += audio.openal
|
||||||
ruby += #input.quartz input.carbon
|
ruby += #input.quartz input.carbon
|
||||||
else ifeq ($(platform),linux)
|
else ifeq ($(platform),linux)
|
||||||
ruby += video.xshm #video.glx video.xv video.xshm video.sdl
|
ruby += video.xshm video.sdl #video.glx video.xv
|
||||||
ruby += audio.oss audio.openal #audio.alsa audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao
|
ruby += audio.oss audio.openal #audio.alsa audio.pulseaudio audio.pulseaudiosimple audio.ao
|
||||||
ruby += input.sdl input.xlib #input.udev input.sdl input.xlib
|
ruby += input.sdl input.xlib #input.udev
|
||||||
else ifeq ($(platform),bsd)
|
else ifeq ($(platform),bsd)
|
||||||
ruby += video.xshm #video.glx video.xv video.xshm video.sdl
|
ruby += video.xshm #video.glx video.xv
|
||||||
ruby += audio.oss audio.openal
|
ruby += audio.oss audio.openal
|
||||||
ruby += input.sdl input.xlib
|
ruby += input.sdl input.xlib
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -82,7 +82,7 @@ Presentation::Presentation() {
|
||||||
program->updateVideoShader();
|
program->updateVideoShader();
|
||||||
});
|
});
|
||||||
loadShaders();
|
loadShaders();
|
||||||
synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Synchronize"].boolean()).onToggle([&] {
|
synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Synchronize"].boolean()).setVisible(false).onToggle([&] {
|
||||||
settings["Video/Synchronize"].setValue(synchronizeVideo.checked());
|
settings["Video/Synchronize"].setValue(synchronizeVideo.checked());
|
||||||
video->setBlocking(synchronizeVideo.checked());
|
video->setBlocking(synchronizeVideo.checked());
|
||||||
});
|
});
|
||||||
|
@ -99,9 +99,9 @@ Presentation::Presentation() {
|
||||||
statusBar.setVisible(showStatusBar.checked());
|
statusBar.setVisible(showStatusBar.checked());
|
||||||
if(visible()) resizeViewport();
|
if(visible()) resizeViewport();
|
||||||
});
|
});
|
||||||
showVideoSettings.setText("Video Settings ...").onActivate([&] { settingsManager->show(0); });
|
showVideoSettings.setText("Video ...").onActivate([&] { settingsManager->show(0); });
|
||||||
showAudioSettings.setText("Audio Settings ...").onActivate([&] { settingsManager->show(1); });
|
showAudioSettings.setText("Audio ...").onActivate([&] { settingsManager->show(1); });
|
||||||
showInputSettings.setText("Input Settings ...").onActivate([&] {
|
showInputSettings.setText("Input ...").onActivate([&] {
|
||||||
if(emulator) {
|
if(emulator) {
|
||||||
//default input panel to current core's input settings
|
//default input panel to current core's input settings
|
||||||
for(auto item : settingsManager->input.emulatorList.items()) {
|
for(auto item : settingsManager->input.emulatorList.items()) {
|
||||||
|
@ -114,8 +114,8 @@ Presentation::Presentation() {
|
||||||
}
|
}
|
||||||
settingsManager->show(2);
|
settingsManager->show(2);
|
||||||
});
|
});
|
||||||
showHotkeySettings.setText("Hotkey Settings ...").onActivate([&] { settingsManager->show(3); });
|
showHotkeySettings.setText("Hotkeys ...").onActivate([&] { settingsManager->show(3); });
|
||||||
showAdvancedSettings.setText("Advanced Settings ...").onActivate([&] { settingsManager->show(4); });
|
showAdvancedSettings.setText("Advanced ...").onActivate([&] { settingsManager->show(4); });
|
||||||
|
|
||||||
toolsMenu.setText("Tools").setVisible(false);
|
toolsMenu.setText("Tools").setVisible(false);
|
||||||
saveQuickStateMenu.setText("Save Quick State");
|
saveQuickStateMenu.setText("Save Quick State");
|
||||||
|
@ -130,9 +130,9 @@ Presentation::Presentation() {
|
||||||
loadSlot3.setText("Slot 3").onActivate([&] { program->loadState(3); });
|
loadSlot3.setText("Slot 3").onActivate([&] { program->loadState(3); });
|
||||||
loadSlot4.setText("Slot 4").onActivate([&] { program->loadState(4); });
|
loadSlot4.setText("Slot 4").onActivate([&] { program->loadState(4); });
|
||||||
loadSlot5.setText("Slot 5").onActivate([&] { program->loadState(5); });
|
loadSlot5.setText("Slot 5").onActivate([&] { program->loadState(5); });
|
||||||
cheatEditor.setText("Cheat Editor").onActivate([&] { toolsManager->show(0); });
|
cheatEditor.setText("Cheat Editor ...").onActivate([&] { toolsManager->show(0); });
|
||||||
stateManager.setText("State Manager").onActivate([&] { toolsManager->show(1); });
|
stateManager.setText("State Manager ...").onActivate([&] { toolsManager->show(1); });
|
||||||
manifestViewer.setText("Manifest Viewer").onActivate([&] { toolsManager->show(2); });
|
manifestViewer.setText("Manifest Viewer ...").onActivate([&] { toolsManager->show(2); });
|
||||||
|
|
||||||
helpMenu.setText("Help");
|
helpMenu.setText("Help");
|
||||||
documentation.setText("Documentation ...").onActivate([&] {
|
documentation.setText("Documentation ...").onActivate([&] {
|
||||||
|
|
|
@ -41,6 +41,7 @@ Program::Program(string_vector args) {
|
||||||
presentation->clearViewport();
|
presentation->clearViewport();
|
||||||
|
|
||||||
audio = Audio::create(settings["Audio/Driver"].text());
|
audio = Audio::create(settings["Audio/Driver"].text());
|
||||||
|
audio->setExclusive(settings["Audio/Exclusive"].boolean());
|
||||||
audio->setContext(presentation->viewport.handle());
|
audio->setContext(presentation->viewport.handle());
|
||||||
audio->setDevice(settings["Audio/Device"].text());
|
audio->setDevice(settings["Audio/Device"].text());
|
||||||
audio->setBlocking(settings["Audio/Synchronize"].boolean());
|
audio->setBlocking(settings["Audio/Synchronize"].boolean());
|
||||||
|
|
|
@ -15,7 +15,7 @@ struct AudioWASAPI : Audio {
|
||||||
Information information;
|
Information information;
|
||||||
information.devices = {"Default"};
|
information.devices = {"Default"};
|
||||||
information.channels = {2};
|
information.channels = {2};
|
||||||
information.frequencies = {};
|
information.frequencies = {(double)_frequency};
|
||||||
information.latencies = {20, 40, 60, 80, 100};
|
information.latencies = {20, 40, 60, 80, 100};
|
||||||
return information;
|
return information;
|
||||||
}
|
}
|
||||||
|
@ -51,32 +51,31 @@ struct AudioWASAPI : Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clear() -> void {
|
auto clear() -> void {
|
||||||
|
_queue.read = 0;
|
||||||
|
_queue.write = 0;
|
||||||
|
_queue.count = 0;
|
||||||
|
if(!_audioClient) return;
|
||||||
_audioClient->Stop();
|
_audioClient->Stop();
|
||||||
_audioClient->Reset();
|
_audioClient->Reset();
|
||||||
for(auto n : range(available())) write(0, 0);
|
|
||||||
_audioClient->Start();
|
_audioClient->Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto output(const double samples[]) -> void {
|
auto output(const double samples[]) -> void {
|
||||||
_queuedFrames.append(int16_t(samples[0] * 32768.0) << 0 | int16_t(samples[1] * 32768.0) << 16);
|
if(_queue.count < _bufferSize) {
|
||||||
|
for(uint n : range(_channels)) {
|
||||||
if(!available() && _queuedFrames.size() >= _bufferSize) {
|
_queue.samples[_queue.write][n] = samples[n];
|
||||||
if(_blocking) {
|
|
||||||
while(!available()); //wait for free sample slot
|
|
||||||
} else {
|
|
||||||
_queuedFrames.takeLeft(); //drop sample (run ahead)
|
|
||||||
}
|
}
|
||||||
}
|
_queue.write++;
|
||||||
|
_queue.count++;
|
||||||
uint32_t cachedFrame = 0;
|
} else if(WaitForSingleObject(_eventHandle, _blocking ? INFINITE : 0) == WAIT_OBJECT_0) {
|
||||||
for(auto n : range(available())) {
|
write();
|
||||||
if(_queuedFrames) cachedFrame = _queuedFrames.takeLeft();
|
|
||||||
write(cachedFrame >> 0, cachedFrame >> 16);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto initialize() -> bool {
|
auto initialize() -> bool {
|
||||||
|
terminate();
|
||||||
|
|
||||||
if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&_enumerator) != S_OK) return false;
|
if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&_enumerator) != S_OK) return false;
|
||||||
if(_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &_audioDevice) != S_OK) return false;
|
if(_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &_audioDevice) != S_OK) return false;
|
||||||
if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false;
|
if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false;
|
||||||
|
@ -87,25 +86,35 @@ private:
|
||||||
_waveFormat = (WAVEFORMATEX*)_propVariant.blob.pBlobData;
|
_waveFormat = (WAVEFORMATEX*)_propVariant.blob.pBlobData;
|
||||||
if(_audioClient->GetDevicePeriod(nullptr, &_devicePeriod) != S_OK) return false;
|
if(_audioClient->GetDevicePeriod(nullptr, &_devicePeriod) != S_OK) return false;
|
||||||
auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units
|
auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units
|
||||||
if(_audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, 0, latency, latency, _waveFormat, nullptr) != S_OK) return false;
|
auto result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, _waveFormat, nullptr);
|
||||||
|
if(result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
|
||||||
|
if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false;
|
||||||
|
_audioClient->Release();
|
||||||
|
latency = (REFERENCE_TIME)(10'000 * 1'000 * _bufferSize / _waveFormat->nSamplesPerSec);
|
||||||
|
if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false;
|
||||||
|
result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, _waveFormat, nullptr);
|
||||||
|
}
|
||||||
|
if(result != S_OK) return false;
|
||||||
DWORD taskIndex = 0;
|
DWORD taskIndex = 0;
|
||||||
_taskHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskIndex);
|
_taskHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskIndex);
|
||||||
} else {
|
} else {
|
||||||
if(_audioClient->GetMixFormat(&waveFormat) != S_OK) return false;
|
if(_audioClient->GetMixFormat(&_waveFormat) != S_OK) return false;
|
||||||
if(_audioClient->GetDevicePeriod(&_devicePeriod, nullptr)) return false;
|
if(_audioClient->GetDevicePeriod(&_devicePeriod, nullptr)) return false;
|
||||||
auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units
|
auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units
|
||||||
if(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, latency, 0, _waveFormat, nullptr) != S_OK) return false;
|
if(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, 0, _waveFormat, nullptr) != S_OK) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_eventHandle = CreateEvent(nullptr, false, false, nullptr);
|
||||||
|
if(_audioClient->SetEventHandle(_eventHandle) != S_OK) return false;
|
||||||
if(_audioClient->GetService(IID_IAudioRenderClient, (void**)&_renderClient) != S_OK) return false;
|
if(_audioClient->GetService(IID_IAudioRenderClient, (void**)&_renderClient) != S_OK) return false;
|
||||||
if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false;
|
if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false;
|
||||||
|
|
||||||
_channels = waveFormat->nChannels;
|
_channels = _waveFormat->nChannels;
|
||||||
_frequency = waveFormat->nSamplesPerSec;
|
_frequency = _waveFormat->nSamplesPerSec;
|
||||||
_mode = ((WAVEFORMATEXTENSIBLE*)_waveFormat)->SubFormat.Data1;
|
_mode = ((WAVEFORMATEXTENSIBLE*)_waveFormat)->SubFormat.Data1;
|
||||||
_precision = _waveFormat->wBitsPerSample;
|
_precision = _waveFormat->wBitsPerSample;
|
||||||
|
|
||||||
_audioClient->Start();
|
clear();
|
||||||
return _ready = true;
|
return _ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,33 +124,52 @@ private:
|
||||||
if(_waveFormat) CoTaskMemFree(_waveFormat), _waveFormat = nullptr;
|
if(_waveFormat) CoTaskMemFree(_waveFormat), _waveFormat = nullptr;
|
||||||
if(_audioClient) _audioClient->Release(), _audioClient = nullptr;
|
if(_audioClient) _audioClient->Release(), _audioClient = nullptr;
|
||||||
if(_audioDevice) _audioDevice->Release(), _audioDevice = nullptr;
|
if(_audioDevice) _audioDevice->Release(), _audioDevice = nullptr;
|
||||||
|
if(_eventHandle) CloseHandle(_eventHandle), _eventHandle = nullptr;
|
||||||
if(_taskHandle) AvRevertMmThreadCharacteristics(_taskHandle), _taskHandle = nullptr;
|
if(_taskHandle) AvRevertMmThreadCharacteristics(_taskHandle), _taskHandle = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto available() -> uint {
|
auto write() -> void {
|
||||||
uint32_t padding = 0;
|
uint32_t padding = 0;
|
||||||
_audioClient->GetCurrentPadding(&padding);
|
_audioClient->GetCurrentPadding(&padding);
|
||||||
return bufferSize - padding;
|
uint32_t available = _bufferSize - padding;
|
||||||
}
|
|
||||||
|
uint8_t* buffer = nullptr;
|
||||||
auto write(int16_t left, int16_t right) -> void {
|
if(_renderClient->GetBuffer(available, &buffer) == S_OK) {
|
||||||
if(_renderClient->GetBuffer(1, &_bufferData) != S_OK) return;
|
uint bufferFlags = 0;
|
||||||
|
for(uint _ : range(available)) {
|
||||||
if(_channels >= 2 && _mode == 1 && _precision == 16) {
|
//if more samples are available than we have queued, fill remainder with silence
|
||||||
auto buffer = (int16_t*)_bufferData;
|
double samples[8] = {};
|
||||||
buffer[0] = left;
|
if(_queue.count) {
|
||||||
buffer[1] = right;
|
for(uint n : range(_channels)) {
|
||||||
}
|
samples[n] = _queue.samples[_queue.read][n];
|
||||||
|
}
|
||||||
if(_channels >= 2 && _mode == 3 && _precision == 32) {
|
_queue.read++;
|
||||||
auto buffer = (float*)_bufferData;
|
_queue.count--;
|
||||||
buffer[0] = left / 32768.0;
|
}
|
||||||
buffer[1] = right / 32768.0;
|
|
||||||
}
|
if(_mode == 1 && _precision == 16) {
|
||||||
|
auto output = (int16_t*)buffer;
|
||||||
_renderClient->ReleaseBuffer(1, 0);
|
for(uint n : range(_channels)) *output++ = int16_t(samples[n] * 32768.0);
|
||||||
|
buffer = (uint8_t*)output;
|
||||||
|
} else if(_mode == 1 && _precision == 32) {
|
||||||
|
auto output = (int32_t*)buffer;
|
||||||
|
for(uint n : range(_channels)) *output++ = int32_t(samples[n] * 65536.0 * 32768.0);
|
||||||
|
buffer = (uint8_t*)output;
|
||||||
|
} else if(_mode == 3 && _precision == 32) {
|
||||||
|
auto output = (float*)buffer;
|
||||||
|
for(uint n : range(_channels)) *output++ = float(samples[n]);
|
||||||
|
buffer = (uint8_t*)output;
|
||||||
|
} else {
|
||||||
|
//output silence for unsupported sample formats
|
||||||
|
bufferFlags = AUDCLNT_BUFFERFLAGS_SILENT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_renderClient->ReleaseBuffer(available, bufferFlags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _ready = false;
|
||||||
bool _exclusive = false;
|
bool _exclusive = false;
|
||||||
bool _blocking = true;
|
bool _blocking = true;
|
||||||
uint _channels = 2;
|
uint _channels = 2;
|
||||||
|
@ -151,6 +179,13 @@ private:
|
||||||
uint _mode = 0;
|
uint _mode = 0;
|
||||||
uint _precision = 0;
|
uint _precision = 0;
|
||||||
|
|
||||||
|
struct Queue {
|
||||||
|
double samples[65536][8];
|
||||||
|
uint16_t read;
|
||||||
|
uint16_t write;
|
||||||
|
uint16_t count;
|
||||||
|
} _queue;
|
||||||
|
|
||||||
IMMDeviceEnumerator* _enumerator = nullptr;
|
IMMDeviceEnumerator* _enumerator = nullptr;
|
||||||
IMMDevice* _audioDevice = nullptr;
|
IMMDevice* _audioDevice = nullptr;
|
||||||
IPropertyStore* _propertyStore = nullptr;
|
IPropertyStore* _propertyStore = nullptr;
|
||||||
|
@ -158,9 +193,8 @@ private:
|
||||||
IAudioRenderClient* _renderClient = nullptr;
|
IAudioRenderClient* _renderClient = nullptr;
|
||||||
WAVEFORMATEX* _waveFormat = nullptr;
|
WAVEFORMATEX* _waveFormat = nullptr;
|
||||||
PROPVARIANT _propVariant;
|
PROPVARIANT _propVariant;
|
||||||
|
HANDLE _eventHandle = nullptr;
|
||||||
HANDLE _taskHandle = nullptr;
|
HANDLE _taskHandle = nullptr;
|
||||||
REFERENCE_TIME _devicePeriod = 0;
|
REFERENCE_TIME _devicePeriod = 0;
|
||||||
uint32_t _bufferSize = 0; //in frames
|
uint32_t _bufferSize = 0; //in frames
|
||||||
uint8_t* _bufferData = nullptr;
|
|
||||||
vector<uint32_t> _queuedFrames;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
//note: this driver works under Linux, but crashes with SIGSEGV under FreeBSD
|
||||||
|
//exact reason is unknown; but I suspect it's a bug in FreeBSD's SDL 1.2 package
|
||||||
|
|
||||||
#include <sys/ipc.h>
|
#include <sys/ipc.h>
|
||||||
#include <sys/shm.h>
|
#include <sys/shm.h>
|
||||||
#include <X11/extensions/Xv.h>
|
#include <X11/extensions/Xv.h>
|
||||||
|
@ -6,130 +9,124 @@
|
||||||
#include <SDL/SDL.h>
|
#include <SDL/SDL.h>
|
||||||
|
|
||||||
struct VideoSDL : Video {
|
struct VideoSDL : Video {
|
||||||
~VideoSDL() { term(); }
|
VideoSDL() { initialize(); }
|
||||||
|
~VideoSDL() { terminate(); }
|
||||||
|
|
||||||
Display* display = nullptr;
|
auto ready() -> bool { return _ready; }
|
||||||
SDL_Surface* screen = nullptr;
|
|
||||||
SDL_Surface* buffer = nullptr;
|
|
||||||
uint iwidth = 0;
|
|
||||||
uint iheight = 0;
|
|
||||||
|
|
||||||
struct {
|
auto context() -> uintptr { return _context; }
|
||||||
uintptr handle = 0;
|
|
||||||
|
|
||||||
uint width = 0;
|
auto setContext(uintptr context) -> bool {
|
||||||
uint height = 0;
|
if(_context == context) return true;
|
||||||
} settings;
|
_context = context;
|
||||||
|
return initialize();
|
||||||
auto cap(const string& name) -> bool {
|
|
||||||
if(name == Video::Handle) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto get(const string& name) -> any {
|
|
||||||
if(name == Video::Handle) return settings.handle;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto set(const string& name, const any& value) -> bool {
|
|
||||||
if(name == Video::Handle && value.is<uintptr>()) {
|
|
||||||
settings.handle = value.get<uintptr>();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto resize(uint width, uint height) -> void {
|
|
||||||
if(iwidth >= width && iheight >= height) return;
|
|
||||||
|
|
||||||
iwidth = max(width, iwidth);
|
|
||||||
iheight = max(height, iheight);
|
|
||||||
|
|
||||||
if(buffer) SDL_FreeSurface(buffer);
|
|
||||||
buffer = SDL_CreateRGBSurface(
|
|
||||||
SDL_SWSURFACE, iwidth, iheight, 32,
|
|
||||||
0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
|
|
||||||
if(width != settings.width || height != settings.height) {
|
|
||||||
resize(settings.width = width, settings.height = height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer);
|
|
||||||
pitch = buffer->pitch;
|
|
||||||
return data = (uint32_t*)buffer->pixels;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto unlock() -> void {
|
|
||||||
if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clear() -> void {
|
auto clear() -> void {
|
||||||
if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer);
|
if(SDL_MUSTLOCK(_buffer)) SDL_LockSurface(_buffer);
|
||||||
for(uint y : range(iheight)) {
|
for(uint y : range(_bufferHeight)) {
|
||||||
uint32_t* data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2);
|
uint32_t* data = (uint32_t*)_buffer->pixels + y * (_buffer->pitch >> 2);
|
||||||
for(uint x : range(iwidth)) *data++ = 0xff000000;
|
for(uint x : range(_bufferWidth)) *data++ = 0xff000000;
|
||||||
}
|
}
|
||||||
if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer);
|
if(SDL_MUSTLOCK(_buffer)) SDL_UnlockSurface(_buffer);
|
||||||
refresh();
|
output();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto refresh() -> void {
|
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
|
||||||
|
if(width != _width || height != _height) {
|
||||||
|
resize(_width = width, _height = height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SDL_MUSTLOCK(_buffer)) SDL_LockSurface(_buffer);
|
||||||
|
pitch = _buffer->pitch;
|
||||||
|
return data = (uint32_t*)_buffer->pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto unlock() -> void {
|
||||||
|
if(SDL_MUSTLOCK(_buffer)) SDL_UnlockSurface(_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto output() -> void {
|
||||||
//ruby input is X8R8G8B8, top 8-bits are ignored.
|
//ruby input is X8R8G8B8, top 8-bits are ignored.
|
||||||
//as SDL forces us to use a 32-bit buffer, we must set alpha to 255 (full opacity)
|
//as SDL forces us to use a 32-bit buffer, we must set alpha to 255 (full opacity)
|
||||||
//to prevent blending against the window beneath when X window visual is 32-bits.
|
//to prevent blending against the window beneath when X window visual is 32-bits.
|
||||||
if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer);
|
if(SDL_MUSTLOCK(_buffer)) SDL_LockSurface(_buffer);
|
||||||
for(uint y : range(settings.height)) {
|
for(uint y : range(_height)) {
|
||||||
uint32_t* data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2);
|
uint32_t* data = (uint32_t*)_buffer->pixels + y * (_buffer->pitch >> 2);
|
||||||
for(uint x : range(settings.width)) *data++ |= 0xff000000;
|
for(uint x : range(_width)) *data++ |= 0xff000000;
|
||||||
}
|
}
|
||||||
if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer);
|
if(SDL_MUSTLOCK(_buffer)) SDL_UnlockSurface(_buffer);
|
||||||
|
|
||||||
XWindowAttributes attributes;
|
XWindowAttributes attributes;
|
||||||
XGetWindowAttributes(display, settings.handle, &attributes);
|
XGetWindowAttributes(_display, _context, &attributes);
|
||||||
|
|
||||||
SDL_Rect src, dest;
|
SDL_Rect source;
|
||||||
|
SDL_Rect target;
|
||||||
|
|
||||||
src.x = 0;
|
source.x = 0;
|
||||||
src.y = 0;
|
source.y = 0;
|
||||||
src.w = settings.width;
|
source.w = _width;
|
||||||
src.h = settings.height;
|
source.h = _height;
|
||||||
|
|
||||||
dest.x = 0;
|
target.x = 0;
|
||||||
dest.y = 0;
|
target.y = 0;
|
||||||
dest.w = attributes.width;
|
target.w = attributes.width;
|
||||||
dest.h = attributes.height;
|
target.h = attributes.height;
|
||||||
|
|
||||||
SDL_SoftStretch(buffer, &src, screen, &dest);
|
SDL_SoftStretch(_buffer, &source, _screen, &target);
|
||||||
SDL_UpdateRect(screen, dest.x, dest.y, dest.w, dest.h);
|
SDL_UpdateRect(_screen, target.x, target.y, target.w, target.h);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
private:
|
||||||
display = XOpenDisplay(0);
|
auto initialize() -> bool {
|
||||||
|
terminate();
|
||||||
|
if(!_context) return false;
|
||||||
|
|
||||||
|
_display = XOpenDisplay(0);
|
||||||
|
|
||||||
//todo: this causes a segfault inside SDL_SetVideoMode on FreeBSD (works under Linux)
|
|
||||||
char env[512];
|
char env[512];
|
||||||
sprintf(env, "SDL_WINDOWID=%ld", (long)settings.handle);
|
sprintf(env, "SDL_WINDOWID=%ld", (long)_context);
|
||||||
putenv(env);
|
putenv(env);
|
||||||
|
|
||||||
SDL_InitSubSystem(SDL_INIT_VIDEO);
|
SDL_InitSubSystem(SDL_INIT_VIDEO);
|
||||||
screen = SDL_SetVideoMode(2560, 1600, 32, SDL_HWSURFACE);
|
_screen = SDL_SetVideoMode(2560, 1600, 32, SDL_HWSURFACE);
|
||||||
XUndefineCursor(display, settings.handle);
|
XUndefineCursor(_display, _context);
|
||||||
|
|
||||||
buffer = nullptr;
|
_buffer = nullptr;
|
||||||
iwidth = 0;
|
_bufferWidth = 0;
|
||||||
iheight = 0;
|
_bufferHeight = 0;
|
||||||
resize(settings.width = 256, settings.height = 256);
|
resize(_width = 256, _height = 256);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
XCloseDisplay(display);
|
if(_buffer) SDL_FreeSurface(_buffer), _buffer = nullptr;
|
||||||
SDL_FreeSurface(buffer);
|
if(_screen) SDL_QuitSubSystem(SDL_INIT_VIDEO), _screen = nullptr;
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
if(_display) XCloseDisplay(_display), _display = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto resize(uint width, uint height) -> void {
|
||||||
|
if(_bufferWidth >= width && _bufferHeight >= height) return;
|
||||||
|
|
||||||
|
_bufferWidth = max(width, _bufferWidth);
|
||||||
|
_bufferHeight = max(height, _bufferHeight);
|
||||||
|
|
||||||
|
if(_buffer) SDL_FreeSurface(_buffer);
|
||||||
|
_buffer = SDL_CreateRGBSurface(
|
||||||
|
SDL_SWSURFACE, _bufferWidth, _bufferHeight, 32,
|
||||||
|
0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _ready = false;
|
||||||
|
uintptr _context = 0;
|
||||||
|
|
||||||
|
Display* _display = nullptr;
|
||||||
|
SDL_Surface* _screen = nullptr;
|
||||||
|
SDL_Surface* _buffer = nullptr;
|
||||||
|
uint _bufferWidth = 0;
|
||||||
|
uint _bufferHeight = 0;
|
||||||
|
uint _width = 0;
|
||||||
|
uint _height = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,6 +28,14 @@ struct VideoXShm : Video {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto clear() -> void {
|
||||||
|
if(!_ready) return;
|
||||||
|
auto dp = _inputBuffer;
|
||||||
|
uint length = _inputWidth * _inputHeight;
|
||||||
|
while(length--) *dp++ = 255u << 24;
|
||||||
|
output();
|
||||||
|
}
|
||||||
|
|
||||||
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
|
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
|
||||||
if(!_inputBuffer || _inputWidth != width || _inputHeight != height) {
|
if(!_inputBuffer || _inputWidth != width || _inputHeight != height) {
|
||||||
if(_inputBuffer) delete[] _inputBuffer;
|
if(_inputBuffer) delete[] _inputBuffer;
|
||||||
|
@ -44,14 +52,6 @@ struct VideoXShm : Video {
|
||||||
auto unlock() -> void {
|
auto unlock() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clear() -> void {
|
|
||||||
if(!_ready) return;
|
|
||||||
auto dp = _inputBuffer;
|
|
||||||
uint length = _inputWidth * _inputHeight;
|
|
||||||
while(length--) *dp++ = 255u << 24;
|
|
||||||
output();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto output() -> void {
|
auto output() -> void {
|
||||||
if(!_ready) return;
|
if(!_ready) return;
|
||||||
size();
|
size();
|
||||||
|
|
Loading…
Reference in New Issue