diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 56dd3d06..f6f63cc9 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -12,7 +12,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "103.14"; + static const string Version = "103.15"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/target-tomoko/GNUmakefile b/higan/target-tomoko/GNUmakefile index 2b0c2d1e..110b985d 100644 --- a/higan/target-tomoko/GNUmakefile +++ b/higan/target-tomoko/GNUmakefile @@ -18,20 +18,20 @@ ui_objects += $(if $(call streq,$(platform),windows),ui-resource) # platform ifeq ($(platform),windows) - ruby += video.direct3d video.wgl video.directdraw video.gdi - ruby += audio.asio audio.wasapi audio.xaudio2 audio.directsound + ruby += video.direct3d #video.wgl video.directdraw video.gdi + ruby += audio.asio #audio.wasapi audio.xaudio2 audio.directsound ruby += input.windows else ifeq ($(platform),macosx) - ruby += video.cgl - ruby += audio.openal - ruby += input.quartz input.carbon + ruby += #video.cgl + ruby += #audio.openal + ruby += #input.quartz input.carbon else ifeq ($(platform),linux) - ruby += video.glx video.xv video.xshm video.sdl - ruby += audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao - ruby += input.udev input.sdl input.xlib + ruby += video.xshm #video.glx video.xv video.xshm video.sdl + ruby += audio.oss #audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao + ruby += input.sdl input.xlib #input.udev input.sdl input.xlib else ifeq ($(platform),bsd) - ruby += video.glx video.xv video.xshm video.sdl - ruby += audio.openal audio.oss + ruby += video.xshm #video.glx video.xv video.xshm video.sdl + ruby += audio.oss #audio.alsa ruby += input.sdl input.xlib endif diff --git a/higan/target-tomoko/configuration/configuration.cpp b/higan/target-tomoko/configuration/configuration.cpp index 3b93d83a..ce0cc90c 100644 --- a/higan/target-tomoko/configuration/configuration.cpp +++ b/higan/target-tomoko/configuration/configuration.cpp @@ -42,13 +42,14 @@ Settings::Settings() { set("Audio/Driver", ruby::Audio::optimalDriver()); set("Audio/Device", ""); + set("Audio/Frequency", 48000); + set("Audio/Latency", 0); set("Audio/Exclusive", false); set("Audio/Synchronize", true); set("Audio/Mute", false); set("Audio/Volume", 100); set("Audio/Balance", 50); set("Audio/Reverb/Enable", false); - set("Audio/Latency", 60); set("Audio/Resampler", "Sinc"); set("Input/Driver", ruby::Input::optimalDriver()); diff --git a/higan/target-tomoko/input/hotkeys.cpp b/higan/target-tomoko/input/hotkeys.cpp index 543590ca..d33fe898 100644 --- a/higan/target-tomoko/input/hotkeys.cpp +++ b/higan/target-tomoko/input/hotkeys.cpp @@ -62,12 +62,12 @@ auto InputManager::appendHotkeys() -> void { { auto hotkey = new InputHotkey; hotkey->name = "Fast Forward"; hotkey->press = [] { - video->set(Video::Synchronize, false); - audio->set(Audio::Synchronize, false); + video->setBlocking(false); + audio->setBlocking(false); }; hotkey->release = [] { - video->set(Video::Synchronize, settings["Video/Synchronize"].boolean()); - audio->set(Audio::Synchronize, settings["Audio/Synchronize"].boolean()); + video->setBlocking(settings["Video/Synchronize"].boolean()); + audio->setBlocking(settings["Audio/Synchronize"].boolean()); }; hotkeys.append(hotkey); } diff --git a/higan/target-tomoko/presentation/presentation.cpp b/higan/target-tomoko/presentation/presentation.cpp index f5a367f2..72f4b5eb 100644 --- a/higan/target-tomoko/presentation/presentation.cpp +++ b/higan/target-tomoko/presentation/presentation.cpp @@ -84,11 +84,11 @@ Presentation::Presentation() { loadShaders(); synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Synchronize"].boolean()).setVisible(false).onToggle([&] { settings["Video/Synchronize"].setValue(synchronizeVideo.checked()); - video->set(Video::Synchronize, synchronizeVideo.checked()); + video->setBlocking(synchronizeVideo.checked()); }); synchronizeAudio.setText("Synchronize Audio").setChecked(settings["Audio/Synchronize"].boolean()).onToggle([&] { settings["Audio/Synchronize"].setValue(synchronizeAudio.checked()); - audio->set(Audio::Synchronize, synchronizeAudio.checked()); + audio->setBlocking(synchronizeAudio.checked()); }); muteAudio.setText("Mute Audio").setChecked(settings["Audio/Mute"].boolean()).onToggle([&] { settings["Audio/Mute"].setValue(muteAudio.checked()); @@ -229,7 +229,7 @@ auto Presentation::clearViewport() -> void { } video->unlock(); - video->refresh(); + video->output(); } } @@ -313,11 +313,11 @@ auto Presentation::toggleFullScreen() -> void { menuBar.setVisible(false); statusBar.setVisible(false); setFullScreen(true); - video->set(Video::Exclusive, settings["Video/Fullscreen/Exclusive"].boolean()); + video->setExclusive(settings["Video/Fullscreen/Exclusive"].boolean()); if(!input->acquired()) input->acquire(); } else { if(input->acquired()) input->release(); - video->set(Video::Exclusive, false); + video->setExclusive(false); setFullScreen(false); menuBar.setVisible(true); statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean()); diff --git a/higan/target-tomoko/program/interface.cpp b/higan/target-tomoko/program/interface.cpp index 89c1c233..2f7a3aaf 100644 --- a/higan/target-tomoko/program/interface.cpp +++ b/higan/target-tomoko/program/interface.cpp @@ -74,7 +74,7 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig } video->unlock(); - video->refresh(); + video->output(); } static uint frameCounter = 0; @@ -90,9 +90,7 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig } auto Program::audioSample(const double* samples, uint channels) -> void { - int16 left = sclamp<16>(samples[0] * 32768.0); - int16 right = sclamp<16>(samples[1] * 32768.0); - audio->sample(left, right); + audio->output(samples); } auto Program::inputPoll(uint port, uint device, uint input) -> int16 { diff --git a/higan/target-tomoko/program/medium.cpp b/higan/target-tomoko/program/medium.cpp index 2fce5481..799b76eb 100644 --- a/higan/target-tomoko/program/medium.cpp +++ b/higan/target-tomoko/program/medium.cpp @@ -19,7 +19,7 @@ auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interfa mediumPaths.append(locate({medium.name, ".sys/"})); - Emulator::audio.reset(2, audio->get(Audio::Frequency).get(44100)); + Emulator::audio.reset(2, audio->frequency()); inputManager->bind(emulator = &interface); if(!emulator->load(medium.id)) { emulator = nullptr; diff --git a/higan/target-tomoko/program/program.cpp b/higan/target-tomoko/program/program.cpp index fd455097..b1a4fc5b 100644 --- a/higan/target-tomoko/program/program.cpp +++ b/higan/target-tomoko/program/program.cpp @@ -35,23 +35,23 @@ Program::Program(string_vector args) { presentation->setVisible(); video = Video::create(settings["Video/Driver"].text()); - video->set(Video::Handle, presentation->viewport.handle()); - video->set(Video::Synchronize, settings["Video/Synchronize"].boolean()); - if(!video->init()) video = Video::create("None"); + video->setContext(presentation->viewport.handle()); + video->setBlocking(settings["Video/Synchronize"].boolean()); + if(!video->ready()) MessageDialog().setText("Failed to initialize video driver").warning(); presentation->clearViewport(); audio = Audio::create(settings["Audio/Driver"].text()); - audio->set(Audio::Device, settings["Audio/Device"].text()); - audio->set(Audio::Handle, presentation->viewport.handle()); - audio->set(Audio::Synchronize, settings["Audio/Synchronize"].boolean()); - audio->set(Audio::Latency, 80u); - if(!audio->init()) audio = Audio::create("None"); + audio->setContext(presentation->viewport.handle()); + audio->setBlocking(settings["Audio/Synchronize"].boolean()); + audio->setFrequency(settings["Audio/Frequency"].natural()); + audio->setChannels(2); + if(!audio->ready()) MessageDialog().setText("Failed to initialize audio driver").warning(); input = Input::create(settings["Input/Driver"].text()); - input->set(Input::Handle, presentation->viewport.handle()); + input->setContext(presentation->viewport.handle()); input->onChange({&InputManager::onChange, &inputManager()}); - if(!input->init()) input = Input::create("None"); + if(!input->ready()) MessageDialog().setText("Failed to initialize input driver").warning(); new InputManager; new SettingsManager; diff --git a/higan/target-tomoko/program/utility.cpp b/higan/target-tomoko/program/utility.cpp index 9056be12..ec9453b5 100644 --- a/higan/target-tomoko/program/utility.cpp +++ b/higan/target-tomoko/program/utility.cpp @@ -67,19 +67,21 @@ auto Program::updateVideoShader() -> void { && settings["Video/Shader"].text() != "Blur" && directory::exists(settings["Video/Shader"].text()) ) { - video->set(Video::Filter, Video::FilterNearest); - video->set(Video::Shader, settings["Video/Shader"].text()); + video->setSmooth(false); + video->setShader(settings["Video/Shader"].text()); } else { - video->set(Video::Filter, settings["Video/Shader"].text() == "Blur" ? Video::FilterLinear : Video::FilterNearest); - video->set(Video::Shader, (string)""); + video->setSmooth(settings["Video/Shader"].text() == "Blur"); + video->setShader(""); } } auto Program::updateAudioDriver() -> void { if(!audio) return; audio->clear(); - audio->set(Audio::Exclusive, settings["Audio/Exclusive"].boolean()); - audio->set(Audio::Latency, (uint)settings["Audio/Latency"].natural()); + audio->setDevice(settings["Audio/Device"].text()); + audio->setExclusive(settings["Audio/Exclusive"].boolean()); +//audio->setFrequency(settings["Audio/Frequency"].natural()); + audio->setLatency(settings["Audio/Latency"].natural()); } auto Program::updateAudioEffects() -> void { @@ -95,12 +97,7 @@ auto Program::updateAudioEffects() -> void { auto Program::focused() -> bool { //exclusive mode creates its own top-level window: presentation window will not have focus - if(video->cap(Video::Exclusive)) { - auto value = video->get(Video::Exclusive); - if(value.is() && value.get()) return true; - } - + if(video->exclusive()) return true; if(presentation && presentation->focused()) return true; - return false; } diff --git a/higan/target-tomoko/settings/audio.cpp b/higan/target-tomoko/settings/audio.cpp index 768466b8..5f850fdf 100644 --- a/higan/target-tomoko/settings/audio.cpp +++ b/higan/target-tomoko/settings/audio.cpp @@ -6,35 +6,37 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) { driverLabel.setFont(Font().setBold()).setText("Driver Settings"); - latencyLabel.setText("Latency:"); - latencyCombo.append(ComboButtonItem().setText("0ms")); - latencyCombo.append(ComboButtonItem().setText("20ms")); - latencyCombo.append(ComboButtonItem().setText("40ms")); - latencyCombo.append(ComboButtonItem().setText("60ms")); - latencyCombo.append(ComboButtonItem().setText("80ms")); - latencyCombo.append(ComboButtonItem().setText("100ms")); - switch(settings["Audio/Latency"].natural()) { - case 0: latencyCombo.item(0)->setSelected(); break; - case 20: latencyCombo.item(1)->setSelected(); break; - case 40: latencyCombo.item(2)->setSelected(); break; - case 60: latencyCombo.item(3)->setSelected(); break; - case 80: latencyCombo.item(4)->setSelected(); break; - case 100: latencyCombo.item(5)->setSelected(); break; + auto information = audio->information(); + + deviceLabel.setText("Device:"); + for(auto& device : information.devices) { + deviceList.append(ComboButtonItem().setText(device)); + if(device == settings["Audio/Device"].text()) { + deviceList.item(deviceList.itemCount() - 1).setSelected(); + } } - latencyCombo.onChange([&] { updateDriver(); }); + deviceList.onChange([&] { updateDriver(); }); frequencyLabel.setText("Frequency:"); - auto frequencyValue = audio->get(Audio::Frequency).get(44100); - frequencyCombo.append(ComboButtonItem().setText({frequencyValue, "hz"})); - frequencyCombo.setEnabled(false); + for(auto& frequency : information.frequencies) { + frequencyList.append(ComboButtonItem().setText(frequency)); + if(frequency == settings["Audio/Frequency"].natural()) { + frequencyList.item(frequencyList.itemCount() - 1).setSelected(); + } + } + frequencyList.onChange([&] { updateDriver(); }); - resamplerLabel.setText("Resampler:"); - resamplerCombo.append(ComboButtonItem().setText("IIR - Biquad")); - resamplerCombo.setEnabled(false); + latencyLabel.setText("Latency:"); + for(auto& latency : information.latencies) { + latencyList.append(ComboButtonItem().setText(latency)); + if(latency == settings["Audio/Latency"].natural()) { + latencyList.item(latencyList.itemCount() - 1).setSelected(); + } + } + latencyList.onChange([&] { updateDriver(); }); exclusiveMode.setText("Exclusive mode"); exclusiveMode.setChecked(settings["Audio/Exclusive"].boolean()).onToggle([&] { updateDriver(); }); - if(!audio->cap(Audio::Exclusive)) exclusiveMode.remove(); effectsLabel.setFont(Font().setBold()).setText("Effects"); @@ -53,17 +55,9 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) { } auto AudioSettings::updateDriver() -> void { - if(auto item = latencyCombo.selected()) { - uint latency = 60; - if(item->offset() == 0) latency = 0; - if(item->offset() == 1) latency = 20; - if(item->offset() == 2) latency = 40; - if(item->offset() == 3) latency = 60; - if(item->offset() == 4) latency = 80; - if(item->offset() == 5) latency = 100; - settings["Audio/Latency"].setValue(latency); - } - + settings["Audio/Device"].setValue(deviceList.selected().text()); + settings["Audio/Frequency"].setValue(frequencyList.selected().text()); + settings["Audio/Latency"].setValue(latencyList.selected().text()); settings["Audio/Exclusive"].setValue(exclusiveMode.checked()); program->updateAudioDriver(); } diff --git a/higan/target-tomoko/settings/settings.hpp b/higan/target-tomoko/settings/settings.hpp index 6eeaff53..7910c967 100644 --- a/higan/target-tomoko/settings/settings.hpp +++ b/higan/target-tomoko/settings/settings.hpp @@ -45,12 +45,12 @@ struct AudioSettings : TabFrameItem { VerticalLayout layout{this}; Label driverLabel{&layout, Size{~0, 0}, 2}; HorizontalLayout controlLayout{&layout, Size{~0, 0}}; - Label latencyLabel{&controlLayout, Size{0, 0}}; - ComboButton latencyCombo{&controlLayout, Size{~0, 0}}; + Label deviceLabel{&controlLayout, Size{0, 0}}; + ComboButton deviceList{&controlLayout, Size{~0, 0}}; Label frequencyLabel{&controlLayout, Size{0, 0}}; - ComboButton frequencyCombo{&controlLayout, Size{~0, 0}}; - Label resamplerLabel{&controlLayout, Size{0, 0}}; - ComboButton resamplerCombo{&controlLayout, Size{~0, 0}}; + ComboButton frequencyList{&controlLayout, Size{~0, 0}}; + Label latencyLabel{&controlLayout, Size{0, 0}}; + ComboButton latencyList{&controlLayout, Size{~0, 0}}; CheckLabel exclusiveMode{&layout, Size{~0, 0}}; Label effectsLabel{&layout, Size{~0, 0}, 2}; HorizontalLayout volumeLayout{&layout, Size{~0, 0}}; diff --git a/higan/target-tomoko/settings/video.cpp b/higan/target-tomoko/settings/video.cpp index 05f388cf..e572557d 100644 --- a/higan/target-tomoko/settings/video.cpp +++ b/higan/target-tomoko/settings/video.cpp @@ -32,7 +32,6 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) { fullscreenModeAspectCorrection.setText("Aspect correction").setChecked(settings["Video/Fullscreen/AspectCorrection"].boolean()).onToggle([&] { updateViewport(); }); fullscreenModeIntegralScaling.setText("Integral scaling").setChecked(settings["Video/Fullscreen/IntegralScaling"].boolean()).onToggle([&] { updateViewport(); }); fullscreenModeExclusive.setText("Exclusive mode").setChecked(settings["Video/Fullscreen/Exclusive"].boolean()).onToggle([&] { updateViewport(); }); - if(!video->cap(Video::Exclusive)) fullscreenModeExclusive.remove(); updateColor(true); updateViewport(true); diff --git a/ruby/audio/asio.cpp b/ruby/audio/asio.cpp index aa456dcf..b5a5fd48 100644 --- a/ruby/audio/asio.cpp +++ b/ruby/audio/asio.cpp @@ -1,90 +1,273 @@ #include "asio.hpp" struct AudioASIO : Audio { - ~AudioASIO() { term(); } + static AudioASIO* self; + AudioASIO() { self = this; initialize(); } + ~AudioASIO() { terminate(); } - struct Settings { - HWND handle = nullptr; - bool synchronize = true; - uint frequency = 48000; - } settings; + auto ready() -> bool { return _ready; } - struct Driver { - string name; - string classID; - }; - vector drivers; - - Driver driver; - IASIO* device = nullptr; - - auto cap(const string& name) -> bool { - if(name == Audio::Handle) return true; - if(name == Audio::Synchronize) return true; - if(name == Audio::Frequency) return true; - return false; + auto information() -> Information { + Information information; + for(auto& device : _devices) information.devices.append(device.name); + information.frequencies = {_frequency}; + uint latencies[] = {64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 6144}; //factors of 6144 + for(auto& latency : latencies) { + if(latency < _active.minimumBufferSize) continue; + if(latency > _active.maximumBufferSize) continue; + information.latencies.append(latency); + } + information.channels = {1, 2}; + return information; } - auto get(const string& name) -> any { - if(name == Audio::Handle) return (uintptr)settings.handle; - if(name == Audio::Synchronize) return settings.synchronize; - if(name == Audio::Frequency) return settings.frequency; - return {}; + auto context() -> uintptr { return _context; } + auto blocking() -> bool { return _blocking; } + auto channels() -> uint { return _channels; } + auto frequency() -> uint { return _frequency; } + auto latency() -> uint { return _latency; } + + auto setContext(uintptr context) -> bool { + if(_context == context) return true; + _context = context; + return initialize(); } - auto set(const string& name, const any& value) -> bool { - if(name == Audio::Handle && value.is()) { - settings.handle = (HWND)value.get(); - return true; - } - - if(name == Audio::Synchronize && value.is()) { - settings.synchronize = value.get(); - return true; - } - - if(name == Audio::Frequency && value.is()) { - settings.frequency = value.get(); - return true; - } - - return false; + auto setBlocking(bool blocking) -> bool { + if(_blocking == blocking) return true; + _blocking = blocking; + return initialize(); } - auto sample(int16_t left, int16_t right) -> void { + auto setChannels(uint channels) -> bool { + if(_channels == channels) return true; + _channels = channels; + return initialize(); + } + + auto setLatency(uint latency) -> bool { + if(_latency == latency) return true; + _latency = latency; + return initialize(); + } + + auto output(const double samples[]) -> void { + if(!_ready) return; + if(_blocking) { + while(_queue.count >= _latency); + } + for(uint n : range(_channels)) { + _queue.samples[_queue.write][n] = samples[n]; + } + _queue.write++; + _queue.count++; } auto clear() -> void { + if(!_ready) return; + for(uint n : range(_channels)) { + memory::fill(_channel[n].buffers[0], _latency * _sampleSize); + memory::fill(_channel[n].buffers[1], _latency * _sampleSize); + } + memory::fill(_queue.samples, sizeof(_queue.samples)); + _queue.read = 0; + _queue.write = 0; + _queue.count = 0; } - auto init() -> bool { +private: + auto initialize() -> bool { + terminate(); + //enumerate available ASIO drivers from the registry for(auto candidate : registry::contents("HKLM\\SOFTWARE\\ASIO\\")) { if(auto classID = registry::read({"HKLM\\SOFTWARE\\ASIO\\", candidate, "CLSID"})) { - drivers.append({candidate.trimRight("\\", 1L), classID}); + _devices.append({candidate.trimRight("\\", 1L), classID}); + if(candidate == _device) _active = _devices.right(); } } - if(!drivers) return false; + if(!_devices) return false; - //default to first driver for now - driver = drivers[0]; + if(!_active.name) { + _active = _devices.left(); + _device = _active.name; + } CLSID classID; - if(CLSIDFromString((LPOLESTR)utf16_t(driver.classID), (LPCLSID)&classID) != S_OK) return false; - if(CoCreateInstance(classID, 0, CLSCTX_INPROC_SERVER, classID, (void**)&device) != S_OK) return false; + if(CLSIDFromString((LPOLESTR)utf16_t(_active.classID), (LPCLSID)&classID) != S_OK) return false; + if(CoCreateInstance(classID, 0, CLSCTX_INPROC_SERVER, classID, (void**)&_asio) != S_OK) return false; - if(!device->init((void*)settings.handle)) return false; + if(!_asio->init((void*)_context)) return false; + if(_asio->getSampleRate(&_active.sampleRate) != ASE_OK) return false; + if(_asio->getChannels(&_active.inputChannels, &_active.outputChannels) != ASE_OK) return false; + if(_asio->getBufferSize( + &_active.minimumBufferSize, + &_active.maximumBufferSize, + &_active.preferredBufferSize, + &_active.granularity + ) != ASE_OK) return false; - //temporary debugging information - char driverName[4096] = {0}; - device->getDriverName(driverName); - print("Driver: ", driverName, "\n"); - print("Version: ", device->getDriverVersion(), "\n"); - print("---\n"); + _frequency = _active.sampleRate; + _latency = _latency < _active.minimumBufferSize ? _active.minimumBufferSize : _latency; + _latency = _latency > _active.maximumBufferSize ? _active.maximumBufferSize : _latency; - return true; + for(auto n : range(_channels)) { + _channel[n].isInput = false; + _channel[n].channelNum = n; + _channel[n].buffers[0] = nullptr; + _channel[n].buffers[1] = nullptr; + } + ASIOCallbacks callbacks; + callbacks.bufferSwitch = &AudioASIO::_bufferSwitch; + callbacks.sampleRateDidChange = &AudioASIO::_sampleRateDidChange; + callbacks.asioMessage = &AudioASIO::_asioMessage; + callbacks.bufferSwitchTimeInfo = &AudioASIO::_bufferSwitchTimeInfo; + if(_asio->createBuffers(_channel, _channels, _latency, &callbacks) != ASE_OK) return false; + if(_asio->getLatencies(&_active.inputLatency, &_active.outputLatency) != ASE_OK) return false; + + //assume for the sake of sanity that all buffers use the same sample format ... + ASIOChannelInfo channelInformation = {}; + channelInformation.channel = 0; + channelInformation.isInput = false; + if(_asio->getChannelInfo(&channelInformation) != ASE_OK) return false; + switch(_sampleFormat = channelInformation.type) { + case ASIOSTInt16LSB: _sampleSize = 2; break; + case ASIOSTInt24LSB: _sampleSize = 3; break; + case ASIOSTInt32LSB: _sampleSize = 4; break; + case ASIOSTFloat32LSB: _sampleSize = 4; break; + case ASIOSTFloat64LSB: _sampleSize = 8; break; + default: return false; //unsupported sample format + } + + clear(); + if(_asio->start() != ASE_OK) return false; + return _ready = true; } - auto term() -> void { + auto terminate() -> void { + _ready = false; + _devices.reset(); + _active = {}; + if(!_asio) return; + _asio->stop(); + _asio->disposeBuffers(); + _asio->Release(); + _asio = nullptr; } + +private: + static auto _bufferSwitch(long doubleBufferInput, ASIOBool directProcess) -> void { + return self->bufferSwitch(doubleBufferInput, directProcess); + } + + static auto _sampleRateDidChange(ASIOSampleRate sampleRate) -> void { + return self->sampleRateDidChange(sampleRate); + } + + static auto _asioMessage(long selector, long value, void* message, double* optional) -> long { + return self->asioMessage(selector, value, message, optional); + } + + static auto _bufferSwitchTimeInfo(ASIOTime* parameters, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime* { + return self->bufferSwitchTimeInfo(parameters, doubleBufferIndex, directProcess); + } + + auto bufferSwitch(long doubleBufferInput, ASIOBool directProcess) -> void { + for(uint sampleIndex : range(_latency)) { + double samples[8] = {0}; + if(_queue.count) { + for(uint n : range(_channels)) { + samples[n] = _queue.samples[_queue.read][n]; + } + _queue.read++; + _queue.count--; + } + + for(uint n : range(_channels)) { + auto buffer = (uint8_t*)_channel[n].buffers[doubleBufferInput]; + buffer += sampleIndex * _sampleSize; + + switch(_sampleFormat) { + case ASIOSTInt16LSB: { + *(int16_t*)buffer = samples[n] * double(1 << 15); + break; + } + + case ASIOSTInt24LSB: { + int value = samples[n] * double(1 << 23); + buffer[0] = value >> 0; + buffer[1] = value >> 8; + buffer[2] = value >> 16; + break; + } + + case ASIOSTInt32LSB: { + *(int32_t*)buffer = samples[n] * double(1 << 31); + break; + } + + case ASIOSTFloat32LSB: { + *(float*)buffer = samples[n]; + break; + } + + case ASIOSTFloat64LSB: { + *(double*)buffer = samples[n]; + break; + } + } + } + } + } + + auto sampleRateDidChange(ASIOSampleRate sampleRate) -> void { + } + + auto asioMessage(long selector, long value, void* message, double* optional) -> long { + return ASE_OK; + } + + auto bufferSwitchTimeInfo(ASIOTime* parameters, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime* { + return nullptr; + } + + bool _ready = false; + uintptr _context = 0; + string _device; + bool _blocking = true; + uint _channels = 2; + uint _frequency = 48000; + uint _latency = 0; + + struct Queue { + double samples[65536][8]; + uint16_t read; + uint16_t write; + std::atomic count; + }; + + struct Device { + string name; + string classID; + + ASIOSampleRate sampleRate; + long inputChannels; + long outputChannels; + long inputLatency; + long outputLatency; + long minimumBufferSize; + long maximumBufferSize; + long preferredBufferSize; + long granularity; + }; + + Queue _queue; + vector _devices; + Device _active; + IASIO* _asio = nullptr; + ASIOBufferInfo _channel[8]; + long _sampleFormat; + long _sampleSize; }; + +AudioASIO* AudioASIO::self = nullptr; diff --git a/ruby/audio/asio.hpp b/ruby/audio/asio.hpp index dfda9c3f..6d73a547 100644 --- a/ruby/audio/asio.hpp +++ b/ruby/audio/asio.hpp @@ -52,14 +52,6 @@ enum : long { ASIOSTLastEntry, }; -struct ASIODriverInfo { - long asioVersion; - long driverVersion; - char name[32]; - char errorMessage[124]; - void* sysRef; -}; - struct ASIOBufferInfo { ASIOBool isInput; long channelNum; @@ -123,9 +115,9 @@ struct ASIOTime { struct ASIOCallbacks { auto (*bufferSwitch)(long doubleBufferIndex, ASIOBool directProcess) -> void; - auto (*sampleRateDidChange)(ASIOSampleRate sRate) -> void; - auto (*asioMessage)(long selector, long value, void* message, double* opt) -> long; - auto (*bufferSwitchTimeInfo)(ASIOTime* params, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime*; + auto (*sampleRateDidChange)(ASIOSampleRate sampleRate) -> void; + auto (*asioMessage)(long selector, long value, void* message, double* optional) -> long; + auto (*bufferSwitchTimeInfo)(ASIOTime* parameters, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime*; }; enum : long { kAsioSelectorSupported = 1, @@ -147,25 +139,25 @@ enum : long { }; struct IASIO : public IUnknown { - virtual auto init(void* sysHandle) -> ASIOBool; + virtual auto init(void* systemHandle) -> ASIOBool; virtual auto getDriverName(char* name) -> void; virtual auto getDriverVersion() -> long; virtual auto getErrorMessage(char* error) -> void; virtual auto start() -> ASIOError; virtual auto stop() -> ASIOError; - virtual auto getChannels(long* numInputChannels, long* numOutputChannels) -> ASIOError = 0; + virtual auto getChannels(long* inputChannels, long* outputChannels) -> ASIOError = 0; virtual auto getLatencies(long* inputLatency, long* outputLatency) -> ASIOError = 0; - virtual auto getBufferSize(long* minSize, long* maxSize, long* preferredSize, long* granularity) -> ASIOError = 0; + virtual auto getBufferSize(long* minimumSize, long* maximumSize, long* preferredSize, long* granularity) -> ASIOError = 0; virtual auto canSampleRate(ASIOSampleRate sampleRate) -> ASIOError = 0; virtual auto getSampleRate(ASIOSampleRate* sampleRate) -> ASIOError = 0; virtual auto setSampleRate(ASIOSampleRate sampleRate) -> ASIOError = 0; - virtual auto getClockSources(ASIOClockSource* clocks, long* numSources) -> ASIOError = 0; + virtual auto getClockSources(ASIOClockSource* clocks, long* sources) -> ASIOError = 0; virtual auto setClockSource(long reference) -> ASIOError = 0; - virtual auto getSamplePosition(ASIOSamples* sPos, ASIOTimeStamp* tStamp) -> ASIOError = 0; - virtual auto getChannelInfo(ASIOChannelInfo* info) -> ASIOError = 0; - virtual auto createBuffers(ASIOBufferInfo* bufferInfos, long numChannels, long bufferSize, ASIOCallbacks* callbacks) -> ASIOError = 0; + virtual auto getSamplePosition(ASIOSamples* samplePosition, ASIOTimeStamp* timeStamp) -> ASIOError = 0; + virtual auto getChannelInfo(ASIOChannelInfo* information) -> ASIOError = 0; + virtual auto createBuffers(ASIOBufferInfo* bufferInformation, long channels, long bufferSize, ASIOCallbacks* callbacks) -> ASIOError = 0; virtual auto disposeBuffers() -> ASIOError = 0; virtual auto controlPanel() -> ASIOError = 0; - virtual auto future(long selector, void* opt) -> ASIOError = 0; + virtual auto future(long selector, void* optional) -> ASIOError = 0; virtual auto outputReady() -> ASIOError = 0; }; diff --git a/ruby/audio/oss.cpp b/ruby/audio/oss.cpp index 9e077f36..2e84994f 100644 --- a/ruby/audio/oss.cpp +++ b/ruby/audio/oss.cpp @@ -14,104 +14,108 @@ #endif struct AudioOSS : Audio { - ~AudioOSS() { term(); } + AudioOSS() { initialize(); } + ~AudioOSS() { terminate(); } - struct { - int fd = -1; - int format = AFMT_S16_LE; - int channels = 2; - } device; + auto ready() -> bool { return _ready; } - struct { - string device = "/dev/dsp"; - bool synchronize = true; - uint frequency = 48000; - uint latency = 60; - } settings; - - auto cap(const string& name) -> bool { - if(name == Audio::Device) return true; - if(name == Audio::Synchronize) return true; - if(name == Audio::Frequency) return true; - if(name == Audio::Latency) return true; - return false; + auto information() -> Information { + Information information; + information.devices = {"/dev/dsp"}; + for(auto& device : directory::files("/dev/", "dsp?*")) information.devices.append(string{"/dev/", device}); + information.frequencies = {44100, 48000, 96000}; + information.latencies = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + information.channels = {1, 2}; + return information; } - auto get(const string& name) -> any { - if(name == Audio::Device) return settings.device; - if(name == Audio::Synchronize) return settings.synchronize; - if(name == Audio::Frequency) return settings.frequency; - if(name == Audio::Latency) return settings.latency; - return {}; + auto device() -> string { return _device; } + auto blocking() -> bool { return _blocking; } + auto channels() -> uint { return _channels; } + auto frequency() -> uint { return _frequency; } + auto latency() -> uint { return _latency; } + + auto setDevice(string device) -> bool { + if(_device == device) return true; + _device = device; + return initialize(); } - auto set(const string& name, const any& value) -> bool { - if(name == Audio::Device && value.is()) { - settings.device = value.get(); - if(!settings.device) settings.device = "/dev/dsp"; - return true; - } - - if(name == Audio::Synchronize && value.is()) { - settings.synchronize = value.get(); - updateSynchronization(); - return true; - } - - if(name == Audio::Frequency && value.is()) { - settings.frequency = value.get(); - if(device.fd >= 0) init(); - return true; - } - - if(name == Audio::Latency && value.is()) { - settings.latency = value.get(); - if(device.fd >= 0) init(); - return true; - } - - return false; - } - - auto sample(int16_t left, int16_t right) -> void { - uint32_t sample = (uint16_t)left << 0 | (uint16_t)right << 16; - auto unused = write(device.fd, &sample, 4); - } - - auto clear() -> void { - } - - auto init() -> bool { - device.fd = open(settings.device, O_WRONLY, O_NONBLOCK); - if(device.fd < 0) return false; - - int cooked = 1; - ioctl(device.fd, SNDCTL_DSP_COOKEDMODE, &cooked); - //policy: 0 = minimum latency (higher CPU usage); 10 = maximum latency (lower CPU usage) - int policy = min(10, settings.latency / 20); //note: latency measurement isn't exact - ioctl(device.fd, SNDCTL_DSP_POLICY, &policy); - int frequency = settings.frequency; - ioctl(device.fd, SNDCTL_DSP_CHANNELS, &device.channels); - ioctl(device.fd, SNDCTL_DSP_SETFMT, &device.format); - ioctl(device.fd, SNDCTL_DSP_SPEED, &frequency); - - updateSynchronization(); + auto setBlocking(bool blocking) -> bool { + if(_blocking == blocking) return true; + _blocking = blocking; + updateBlocking(); return true; } - auto term() -> void { - if(device.fd >= 0) { - close(device.fd); - device.fd = -1; + auto setChannels(uint channels) -> bool { + if(_channels == channels) return true; + _channels = channels; + return initialize(); + } + + auto setFrequency(uint frequency) -> bool { + if(_frequency == frequency) return true; + _frequency = frequency; + return initialize(); + } + + auto setLatency(uint latency) -> bool { + if(_latency == latency) return true; + _latency = latency; + return initialize(); + } + + auto output(const double samples[]) -> void { + if(!_ready) return; + for(auto n : range(_channels)) { + int16_t sample = samples[n] * 32768.0; + auto unused = write(_fd, &sample, 2); } } private: - auto updateSynchronization() -> void { - if(device.fd < 0) return; - auto flags = fcntl(device.fd, F_GETFL); - if(flags < 0) return; - settings.synchronize ? flags &=~ O_NONBLOCK : flags |= O_NONBLOCK; - fcntl(device.fd, F_SETFL, flags); + auto initialize() -> bool { + terminate(); + + _fd = open(_device, O_WRONLY, O_NONBLOCK); + if(_fd < 0) return false; + + int cooked = 1; + ioctl(_fd, SNDCTL_DSP_COOKEDMODE, &cooked); + //policy: 0 = minimum latency (higher CPU usage); 10 = maximum latency (lower CPU usage) + int policy = min(10, _latency); + ioctl(_fd, SNDCTL_DSP_POLICY, &policy); + ioctl(_fd, SNDCTL_DSP_CHANNELS, &_channels); + ioctl(_fd, SNDCTL_DSP_SETFMT, &_format); + ioctl(_fd, SNDCTL_DSP_SPEED, &_frequency); + + updateBlocking(); + return _ready = true; } + + auto terminate() -> void { + _ready = false; + if(_fd < 0) return; + close(_fd); + _fd = -1; + } + + auto updateBlocking() -> void { + if(!_ready) return; + auto flags = fcntl(_fd, F_GETFL); + if(flags < 0) return; + _blocking ? flags &=~ O_NONBLOCK : flags |= O_NONBLOCK; + fcntl(_fd, F_SETFL, flags); + } + + bool _ready = false; + string _device = "/dev/dsp"; + bool _blocking = true; + int _channels = 2; + int _frequency = 48000; + int _latency = 2; + + int _fd = -1; + int _format = AFMT_S16_LE; }; diff --git a/ruby/input/joypad/directinput.cpp b/ruby/input/joypad/directinput.cpp index e8eeaeb2..375b1dd4 100644 --- a/ruby/input/joypad/directinput.cpp +++ b/ruby/input/joypad/directinput.cpp @@ -86,7 +86,8 @@ struct InputJoypadDirectInput { return false; } - auto init(uintptr_t handle, LPDIRECTINPUT8 context, bool xinputAvailable) -> bool { + auto initialize(uintptr handle, LPDIRECTINPUT8 context, bool xinputAvailable) -> bool { + if(!handle) return false; this->handle = handle; this->context = context; this->xinputAvailable = xinputAvailable; @@ -94,7 +95,7 @@ struct InputJoypadDirectInput { return true; } - auto term() -> void { + auto terminate() -> void { for(auto& jp : joypads) { jp.device->Unacquire(); if(jp.effect) jp.effect->Release(); diff --git a/ruby/input/joypad/sdl.cpp b/ruby/input/joypad/sdl.cpp index a41273fb..ed018344 100644 --- a/ruby/input/joypad/sdl.cpp +++ b/ruby/input/joypad/sdl.cpp @@ -42,7 +42,7 @@ struct InputJoypadSDL { } } - auto init() -> bool { + auto initialize() -> bool { SDL_InitSubSystem(SDL_INIT_JOYSTICK); SDL_JoystickEventState(SDL_IGNORE); @@ -67,7 +67,7 @@ struct InputJoypadSDL { return true; } - auto term() -> void { + auto terminate() -> void { for(auto& jp : joypads) { SDL_JoystickClose(jp.handle); } diff --git a/ruby/input/joypad/xinput.cpp b/ruby/input/joypad/xinput.cpp index 0999c74a..8ccfcf2b 100644 --- a/ruby/input/joypad/xinput.cpp +++ b/ruby/input/joypad/xinput.cpp @@ -103,7 +103,7 @@ struct InputJoypadXInput { return false; } - auto init() -> bool { + auto initialize() -> bool { if(!libxinput) libxinput = LoadLibraryA("xinput1_3.dll"); if(!libxinput) return false; @@ -112,7 +112,7 @@ struct InputJoypadXInput { XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetStateEx); XInputSetState = (pXInputSetState)GetProcAddress(libxinput, oXInputSetState); if(!XInputGetStateEx) XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetState); - if(!XInputGetStateEx || !XInputSetState) return term(), false; + if(!XInputGetStateEx || !XInputSetState) return terminate(), false; //XInput supports a maximum of four controllers //add all four to devices list now. If they are not connected, they will not show up in poll() results @@ -151,7 +151,7 @@ struct InputJoypadXInput { return true; } - auto term() -> void { + auto terminate() -> void { if(!libxinput) return; FreeLibrary(libxinput); diff --git a/ruby/input/keyboard/rawinput.cpp b/ruby/input/keyboard/rawinput.cpp index 0ad1441b..2289b891 100644 --- a/ruby/input/keyboard/rawinput.cpp +++ b/ruby/input/keyboard/rawinput.cpp @@ -39,7 +39,7 @@ struct InputKeyboardRawInput { devices.append(kb.hid); } - auto init() -> bool { + auto initialize() -> bool { rawinput.updateKeyboard = {&InputKeyboardRawInput::update, this}; //Pause sends 0x001d,4 + 0x0045,0; NumLock sends only 0x0045,0 @@ -170,7 +170,7 @@ struct InputKeyboardRawInput { return true; } - auto term() -> void { + auto terminate() -> void { rawinput.updateKeyboard.reset(); } }; diff --git a/ruby/input/keyboard/xlib.cpp b/ruby/input/keyboard/xlib.cpp index 547ed436..8df9c3c4 100644 --- a/ruby/input/keyboard/xlib.cpp +++ b/ruby/input/keyboard/xlib.cpp @@ -36,7 +36,7 @@ struct InputKeyboardXlib { devices.append(hid); } - auto init() -> bool { + auto initialize() -> bool { display = XOpenDisplay(0); keys.append({"Escape", XK_Escape}); @@ -163,7 +163,7 @@ struct InputKeyboardXlib { return true; } - auto term() -> void { + auto terminate() -> void { if(display) { XCloseDisplay(display); display = nullptr; diff --git a/ruby/input/mouse/rawinput.cpp b/ruby/input/mouse/rawinput.cpp index 671ee46a..b35589a7 100644 --- a/ruby/input/mouse/rawinput.cpp +++ b/ruby/input/mouse/rawinput.cpp @@ -5,7 +5,7 @@ struct InputMouseRawInput { Input& input; InputMouseRawInput(Input& input) : input(input) {} - uintptr_t handle = 0; + uintptr handle = 0; bool mouseAcquired = false; struct Mouse { @@ -17,6 +17,17 @@ struct InputMouseRawInput { bool buttons[5] = {0}; } ms; + auto acquired() -> bool { + if(mouseAcquired) { + SetFocus((HWND)handle); + SetCapture((HWND)handle); + RECT rc; + GetWindowRect((HWND)handle, &rc); + ClipCursor(&rc); + } + return GetCapture() == (HWND)handle; + } + auto acquire() -> bool { if(!mouseAcquired) { mouseAcquired = true; @@ -35,17 +46,6 @@ struct InputMouseRawInput { return true; } - auto acquired() -> bool { - if(mouseAcquired) { - SetFocus((HWND)handle); - SetCapture((HWND)handle); - RECT rc; - GetWindowRect((HWND)handle, &rc); - ClipCursor(&rc); - } - return GetCapture() == (HWND)handle; - } - auto update(RAWINPUT* input) -> void { if((input->data.mouse.usFlags & 1) == MOUSE_MOVE_RELATIVE) { ms.relativeX += input->data.mouse.lLastX; @@ -95,7 +95,8 @@ struct InputMouseRawInput { devices.append(ms.hid); } - auto init(uintptr_t handle) -> bool { + auto initialize(uintptr handle) -> bool { + if(!handle) return false; this->handle = handle; ms.hid->setID(2); @@ -114,7 +115,7 @@ struct InputMouseRawInput { return true; } - auto term() -> void { + auto terminate() -> void { rawinput.updateMouse.reset(); release(); } diff --git a/ruby/input/mouse/xlib.cpp b/ruby/input/mouse/xlib.cpp index 75f151ae..a60239a3 100644 --- a/ruby/input/mouse/xlib.cpp +++ b/ruby/input/mouse/xlib.cpp @@ -7,11 +7,11 @@ struct InputMouseXlib { shared_pointer hid{new HID::Mouse}; - uintptr_t handle = 0; + uintptr handle = 0; Display* display = nullptr; - Window rootWindow; - Cursor invisibleCursor; + Window rootWindow = 0; + Cursor invisibleCursor = 0; unsigned screenWidth = 0; unsigned screenHeight = 0; @@ -24,6 +24,10 @@ struct InputMouseXlib { unsigned relativeY = 0; } ms; + auto acquired() -> bool { + return ms.acquired; + } + auto acquire() -> bool { if(acquired()) return true; @@ -53,10 +57,6 @@ struct InputMouseXlib { return true; } - auto acquired() -> bool { - return ms.acquired; - } - auto assign(unsigned groupID, unsigned inputID, int16_t value) -> void { auto& group = hid->group(groupID); if(group.input(inputID).value() == value) return; @@ -103,7 +103,10 @@ struct InputMouseXlib { devices.append(hid); } - auto init(uintptr_t handle) -> bool { + auto initialize(uintptr handle) -> bool { + terminate(); + if(!handle) return false; + this->handle = handle; display = XOpenDisplay(0); rootWindow = DefaultRootWindow(display); @@ -143,10 +146,16 @@ struct InputMouseXlib { return true; } - auto term() -> void { + auto terminate() -> void { release(); - XFreeCursor(display, invisibleCursor); - XCloseDisplay(display); + if(invisibleCursor) { + XFreeCursor(display, invisibleCursor); + invisibleCursor = 0; + } + if(display) { + XCloseDisplay(display); + display = nullptr; + } } }; diff --git a/ruby/input/sdl.cpp b/ruby/input/sdl.cpp index ea0aba17..e30601ff 100644 --- a/ruby/input/sdl.cpp +++ b/ruby/input/sdl.cpp @@ -7,55 +7,36 @@ #include "joypad/sdl.cpp" struct InputSDL : Input { - InputKeyboardXlib xlibKeyboard; - InputMouseXlib xlibMouse; - InputJoypadSDL sdl; - InputSDL() : xlibKeyboard(*this), xlibMouse(*this), sdl(*this) {} - ~InputSDL() { term(); } + InputSDL() : _keyboard(*this), _mouse(*this), _joypad(*this) { initialize(); } + ~InputSDL() { terminate(); } - struct Settings { - uintptr_t handle = 0; - } settings; + auto ready() -> bool { return _ready; } - auto cap(const string& name) -> bool { - if(name == Input::Handle) return true; - if(name == Input::KeyboardSupport) return true; - if(name == Input::MouseSupport) return true; - if(name == Input::JoypadSupport) return true; - return false; - } + auto context() -> uintptr { return _context; } - auto get(const string& name) -> any { - if(name == Input::Handle) return (uintptr_t)settings.handle; - return {}; - } - - auto set(const string& name, const any& value) -> bool { - if(name == Input::Handle && value.is()) { - settings.handle = value.get(); - return true; - } - - return false; - } - - auto acquire() -> bool { - return xlibMouse.acquire(); - } - - auto release() -> bool { - return xlibMouse.release(); + auto setContext(uintptr context) -> bool { + if(_context == context) return true; + _context = context; + return initialize(); } auto acquired() -> bool { - return xlibMouse.acquired(); + return _mouse.acquired(); + } + + auto acquire() -> bool { + return _mouse.acquire(); + } + + auto release() -> bool { + return _mouse.release(); } auto poll() -> vector> { vector> devices; - xlibKeyboard.poll(devices); - xlibMouse.poll(devices); - sdl.poll(devices); + _keyboard.poll(devices); + _mouse.poll(devices); + _joypad.poll(devices); return devices; } @@ -63,16 +44,26 @@ struct InputSDL : Input { return false; } - auto init() -> bool { - if(!xlibKeyboard.init()) return false; - if(!xlibMouse.init(settings.handle)) return false; - if(!sdl.init()) return false; - return true; +private: + auto initialize() -> bool { + terminate(); + if(!_keyboard.initialize()) return false; + if(!_mouse.initialize(_context)) return false; + if(!_joypad.initialize()) return false; + return _ready = true; } - auto term() -> void { - xlibKeyboard.term(); - xlibMouse.term(); - sdl.term(); + auto terminate() -> void { + _ready = false; + _keyboard.terminate(); + _mouse.terminate(); + _joypad.terminate(); } + + bool _ready = false; + uintptr _context = 0; + + InputKeyboardXlib _keyboard; + InputMouseXlib _mouse; + InputJoypadSDL _joypad; }; diff --git a/ruby/input/windows.cpp b/ruby/input/windows.cpp index 9d181f1b..63d18945 100644 --- a/ruby/input/windows.cpp +++ b/ruby/input/windows.cpp @@ -9,97 +9,93 @@ #include "joypad/directinput.cpp" struct InputWindows : Input { - InputKeyboardRawInput rawinputKeyboard; - InputMouseRawInput rawinputMouse; - InputJoypadXInput xinput; - InputJoypadDirectInput directinput; - InputWindows() : rawinputKeyboard(*this), rawinputMouse(*this), xinput(*this), directinput(*this) {} - ~InputWindows() { term(); } + InputWindows() : _keyboard(*this), _mouse(*this), _joypadXInput(*this), _joypadDirectInput(*this) { initialize(); } + ~InputWindows() { terminate(); } - LPDIRECTINPUT8 directinputContext = nullptr; + auto ready() -> bool { return _ready; } - struct Settings { - uintptr_t handle = 0; - } settings; + auto context() -> uintptr { return _context; } - auto cap(const string& name) -> bool { - if(name == Input::Handle) return true; - if(name == Input::KeyboardSupport) return true; - if(name == Input::MouseSupport) return true; - if(name == Input::JoypadSupport) return true; - if(name == Input::JoypadRumbleSupport) return true; - return false; - } - - auto get(const string& name) -> any { - if(name == Input::Handle) return (uintptr_t)settings.handle; - return {}; - } - - auto set(const string& name, const any& value) -> bool { - if(name == Input::Handle && value.is()) { - settings.handle = value.get(); - return true; - } - return false; - } - - auto acquire() -> bool { - return rawinputMouse.acquire(); - } - - auto release() -> bool { - return rawinputMouse.release(); + auto setContext(uintptr context) -> bool { + if(_context == context) return true; + _context = context; + return initialize(); } auto acquired() -> bool { - return rawinputMouse.acquired(); + return _mouse.acquired(); + } + + auto acquire() -> bool { + return _mouse.acquire(); + } + + auto release() -> bool { + return _mouse.release(); } auto poll() -> vector> { vector> devices; - rawinputKeyboard.poll(devices); - rawinputMouse.poll(devices); - xinput.poll(devices); - directinput.poll(devices); + _keyboard.poll(devices); + _mouse.poll(devices); + _joypadXInput.poll(devices); + _joypadDirectInput.poll(devices); return devices; } auto rumble(uint64_t id, bool enable) -> bool { - if(xinput.rumble(id, enable)) return true; - if(directinput.rumble(id, enable)) return true; + if(_joypadXInput.rumble(id, enable)) return true; + if(_joypadDirectInput.rumble(id, enable)) return true; return false; } - auto init() -> bool { - if(rawinput.initialized == false) { +private: + auto initialize() -> bool { + terminate(); + if(!_context) return false; + + if(!rawinput.initialized) { rawinput.initialized = true; - rawinput.mutex = CreateMutex(NULL, FALSE, NULL); - CreateThread(NULL, 0, RawInputThreadProc, 0, 0, NULL); + rawinput.mutex = CreateMutex(nullptr, false, nullptr); + CreateThread(nullptr, 0, RawInputThreadProc, 0, 0, nullptr); do { Sleep(1); WaitForSingleObject(rawinput.mutex, INFINITE); ReleaseMutex(rawinput.mutex); - } while(rawinput.ready == false); + } while(!rawinput.ready); } - DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&directinputContext, 0); - if(directinputContext == nullptr) return false; + DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&_directInputContext, 0); + if(!_directInputContext) return false; - if(rawinputKeyboard.init() == false) return false; - if(rawinputMouse.init(settings.handle) == false) return false; - bool xinputAvailable = xinput.init(); - if(directinput.init(settings.handle, directinputContext, xinputAvailable) == false) return false; - return true; + if(!_keyboard.initialize()) return false; + if(!_mouse.initialize(_context)) return false; + bool xinputAvailable = _joypadXInput.initialize(); + if(!_joypadDirectInput.initialize(_context, _directInputContext, xinputAvailable)) return false; + return _ready = true; } - auto term() -> void { - rawinputKeyboard.term(); - rawinputMouse.term(); - xinput.term(); - directinput.term(); + auto terminate() -> void { + _ready = false; - if(directinputContext) { directinputContext->Release(); directinputContext = nullptr; } + _keyboard.terminate(); + _mouse.terminate(); + _joypadXInput.terminate(); + _joypadDirectInput.terminate(); + + if(_directInputContext) { + _directInputContext->Release(); + _directInputContext = nullptr; + } } + + bool _ready = false; + uintptr _context = 0; + + InputKeyboardRawInput _keyboard; + InputMouseRawInput _mouse; + InputJoypadXInput _joypadXInput; + InputJoypadDirectInput _joypadDirectInput; + LPDIRECTINPUT8 _directInputContext = nullptr; }; diff --git a/ruby/input/xlib.cpp b/ruby/input/xlib.cpp index 77919f65..ec6dfbe3 100644 --- a/ruby/input/xlib.cpp +++ b/ruby/input/xlib.cpp @@ -8,51 +8,25 @@ #include "mouse/xlib.cpp" struct InputXlib : Input { - InputKeyboardXlib xlibKeyboard; - InputMouseXlib xlibMouse; - InputXlib() : xlibKeyboard(*this), xlibMouse(*this) {} - ~InputXlib() { term(); } + InputXlib() : _keyboard(*this), _mouse(*this) { initialize(); } + ~InputXlib() { terminate(); } - struct Settings { - uintptr_t handle = 0; - } settings; - - auto cap(const string& name) -> bool { - if(name == Input::KeyboardSupport) return true; - if(name == Input::MouseSupport) return true; - return false; - } - - auto get(const string& name) -> any { - if(name == Input::Handle) return (uintptr_t)settings.handle; - return {}; - } - - auto set(const string& name, const any& value) -> bool { - if(name == Input::Handle && value.is()) { - settings.handle = value.get(); - return true; - } - - return false; + auto acquired() -> bool { + return _mouse.acquired(); } auto acquire() -> bool { - return xlibMouse.acquire(); + return _mouse.acquire(); } auto release() -> bool { - return xlibMouse.release(); - } - - auto acquired() -> bool { - return xlibMouse.acquired(); + return _mouse.release(); } auto poll() -> vector> { vector> devices; - xlibKeyboard.poll(devices); - xlibMouse.poll(devices); + _keyboard.poll(devices); + _mouse.poll(devices); return devices; } @@ -60,14 +34,23 @@ struct InputXlib : Input { return false; } - auto init() -> bool { - if(!xlibKeyboard.init()) return false; - if(!xlibMouse.init(settings.handle)) return false; - return true; +private: + auto initialize() -> bool { + terminate(); + if(!_keyboard.initialize()) return false; + if(!_mouse.initialize(_context)) return false; + return _ready = true; } - auto term() -> void { - xlibKeyboard.term(); - xlibMouse.term(); + auto terminate() -> void { + _ready = false; + _keyboard.terminate(); + _mouse.terminate(); } + + bool _ready = false; + uintptr _context = 0; + + InputKeyboardXlib _keyboard; + InputMouseXlib _mouse; }; diff --git a/ruby/ruby.cpp b/ruby/ruby.cpp index ddb9e81a..41ffdf4a 100644 --- a/ruby/ruby.cpp +++ b/ruby/ruby.cpp @@ -72,16 +72,6 @@ using namespace ruby; namespace ruby { -const string Video::Exclusive = "Exclusive"; -const string Video::Handle = "Handle"; -const string Video::Synchronize = "Synchronize"; -const string Video::Depth = "Depth"; -const string Video::Filter = "Filter"; -const string Video::Shader = "Shader"; - -const uint Video::FilterNearest = 0; -const uint Video::FilterLinear = 1; - auto Video::create(const string& driver) -> Video* { if(!driver) return create(optimalDriver()); @@ -272,13 +262,6 @@ auto Video::availableDrivers() -> string_vector { namespace ruby { -const string Audio::Device = "Device"; -const string Audio::Exclusive = "Exclusive"; -const string Audio::Handle = "Handle"; -const string Audio::Synchronize = "Synchronize"; -const string Audio::Frequency = "Frequency"; -const string Audio::Latency = "Latency"; - auto Audio::create(const string& driver) -> Audio* { if(!driver) return create(optimalDriver()); @@ -453,12 +436,6 @@ auto Audio::availableDrivers() -> string_vector { namespace ruby { -const string Input::Handle = "Handle"; -const string Input::KeyboardSupport = "KeyboardSupport"; -const string Input::MouseSupport = "MouseSupport"; -const string Input::JoypadSupport = "JoypadSupport"; -const string Input::JoypadRumbleSupport = "JoypadRumbleSupport"; - auto Input::create(const string& driver) -> Input* { if(!driver) return create(optimalDriver()); diff --git a/ruby/ruby.hpp b/ruby/ruby.hpp index dd66d8fc..4d798883 100644 --- a/ruby/ruby.hpp +++ b/ruby/ruby.hpp @@ -14,89 +14,101 @@ namespace ruby { struct Video { - static const nall::string Exclusive; - static const nall::string Handle; - static const nall::string Synchronize; - static const nall::string Depth; - static const nall::string Filter; - static const nall::string Shader; - - static const uint FilterNearest; - static const uint FilterLinear; - static auto create(const nall::string& driver = "") -> Video*; static auto optimalDriver() -> nall::string; static auto safestDriver() -> nall::string; static auto availableDrivers() -> nall::string_vector; + struct Information { + }; + virtual ~Video() = default; - virtual auto cap(const nall::string& name) -> bool { return false; } - virtual auto get(const nall::string& name) -> nall::any { return false; } - virtual auto set(const nall::string& name, const nall::any& value) -> bool { return false; } + virtual auto ready() -> bool { return true; } + virtual auto information() -> Information { return {}; } + virtual auto exclusive() -> bool { return false; } + virtual auto context() -> uintptr { return 0; } + virtual auto blocking() -> bool { return false; } + virtual auto depth() -> uint { return 24; } + virtual auto smooth() -> bool { return false; } + virtual auto shader() -> nall::string { return ""; } + + virtual auto setExclusive(bool exclusive) -> bool { return false; } + virtual auto setContext(uintptr context) -> bool { return false; } + virtual auto setBlocking(bool blocking) -> bool { return false; } + virtual auto setDepth(uint depth) -> bool { return false; } + virtual auto setSmooth(bool smooth) -> bool { return false; } + virtual auto setShader(nall::string shader) -> bool { return false; } + + virtual auto clear() -> void {} virtual auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool { return false; } virtual auto unlock() -> void {} - virtual auto clear() -> void {} - virtual auto refresh() -> void {} - - virtual auto init() -> bool { return true; } - virtual auto term() -> void {} + virtual auto output() -> void {} }; struct Audio { - static const nall::string Device; - static const nall::string Exclusive; - static const nall::string Handle; - static const nall::string Synchronize; - static const nall::string Frequency; - static const nall::string Latency; - static auto create(const nall::string& driver = "") -> Audio*; static auto optimalDriver() -> nall::string; static auto safestDriver() -> nall::string; static auto availableDrivers() -> nall::string_vector; + struct Information { + nall::vector devices; + nall::vector frequencies; + nall::vector latencies; + nall::vector channels; + }; + virtual ~Audio() = default; - virtual auto cap(const nall::string& name) -> bool { return false; } - virtual auto get(const nall::string& name) -> nall::any { return false; } - virtual auto set(const nall::string& name, const nall::any& value) -> bool { return false; } + virtual auto ready() -> bool { return true; } + virtual auto information() -> Information { return {{"None"}, {48000}, {0}, {2}}; } + + virtual auto exclusive() -> bool { return false; } + virtual auto context() -> uintptr { return 0; } + virtual auto device() -> nall::string { return "None"; } + virtual auto blocking() -> bool { return false; } + virtual auto channels() -> uint { return 2; } + virtual auto frequency() -> uint { return 48000; } + virtual auto latency() -> uint { return 0; } + + virtual auto setExclusive(bool exclusive) -> bool { return false; } + virtual auto setContext(uintptr context) -> bool { return false; } + virtual auto setDevice(nall::string device) -> bool { return false; } + virtual auto setBlocking(bool blocking) -> bool { return false; } + virtual auto setChannels(uint channels) -> bool { return false; } + virtual auto setFrequency(uint frequency) -> bool { return false; } + virtual auto setLatency(uint latency) -> bool { return false; } - virtual auto sample(int16_t left, int16_t right) -> void {} virtual auto clear() -> void {} - - virtual auto init() -> bool { return true; } - virtual auto term() -> void {} + virtual auto output(const double samples[]) -> void {} }; struct Input { - static const nall::string Handle; - static const nall::string KeyboardSupport; - static const nall::string MouseSupport; - static const nall::string JoypadSupport; - static const nall::string JoypadRumbleSupport; - static auto create(const nall::string& driver = "") -> Input*; static auto optimalDriver() -> nall::string; static auto safestDriver() -> nall::string; static auto availableDrivers() -> nall::string_vector; + struct Information { + }; + virtual ~Input() = default; - virtual auto cap(const nall::string& name) -> bool { return false; } - virtual auto get(const nall::string& name) -> nall::any { return false; } - virtual auto set(const nall::string& name, const nall::any& value) -> bool { return false; } + virtual auto ready() -> bool { return true; } + virtual auto information() -> Information { return {}; } + virtual auto context() -> uintptr { return 0; } + + virtual auto setContext(uintptr context) -> bool { return false; } + + virtual auto acquired() -> bool { return false; } virtual auto acquire() -> bool { return false; } virtual auto release() -> bool { return false; } - virtual auto acquired() -> bool { return false; } virtual auto poll() -> nall::vector> { return {}; } virtual auto rumble(uint64_t id, bool enable) -> bool { return false; } - virtual auto init() -> bool { return true; } - virtual auto term() -> void {} - auto onChange(const nall::function, uint, uint, int16_t, int16_t)>& callback) { _onChange = callback; } auto doChange(nall::shared_pointer device, uint group, uint input, int16_t oldValue, int16_t newValue) -> void { if(_onChange) _onChange(device, group, input, oldValue, newValue); diff --git a/ruby/video/direct3d.cpp b/ruby/video/direct3d.cpp index 3f42c8cb..60bf3ba2 100644 --- a/ruby/video/direct3d.cpp +++ b/ruby/video/direct3d.cpp @@ -8,183 +8,182 @@ static LRESULT CALLBACK VideoDirect3D_WindowProcedure(HWND hwnd, UINT msg, WPARA } struct VideoDirect3D : Video { - VideoDirect3D() { - POINT point = {0, 0}; - HMONITOR monitor = MonitorFromPoint(point, MONITOR_DEFAULTTOPRIMARY); - MONITORINFOEX information = {}; - information.cbSize = sizeof(MONITORINFOEX); - GetMonitorInfo(monitor, &information); - monitorWidth = information.rcMonitor.right - information.rcMonitor.left; - monitorHeight = information.rcMonitor.bottom - information.rcMonitor.top; + VideoDirect3D() { initialize(); } + ~VideoDirect3D() { terminate(); } - WNDCLASS windowClass = {}; - windowClass.cbClsExtra = 0; - windowClass.cbWndExtra = 0; - windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); - windowClass.hCursor = LoadCursor(0, IDC_ARROW); - windowClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION); - windowClass.hInstance = GetModuleHandle(0); - windowClass.lpfnWndProc = VideoDirect3D_WindowProcedure; - windowClass.lpszClassName = L"VideoDirect3D_Window"; - windowClass.lpszMenuName = 0; - windowClass.style = CS_HREDRAW | CS_VREDRAW; - RegisterClass(&windowClass); + auto ready() -> bool { return _ready; } - settings.exclusiveHandle = CreateWindowEx(WS_EX_TOPMOST, L"VideoDirect3D_Window", L"", WS_POPUP, - information.rcMonitor.left, information.rcMonitor.top, monitorWidth, monitorHeight, - nullptr, nullptr, GetModuleHandle(0), nullptr); + auto exclusive() -> bool { return _exclusive; } + auto context() -> uintptr { return _context; } + auto blocking() -> bool { return _blocking; } + auto smooth() -> bool { return _smooth; } + + auto setExclusive(bool exclusive) -> bool { + if(_exclusive == exclusive) return true; + _exclusive = exclusive; + return initialize(); } - ~VideoDirect3D() { - term(); - DestroyWindow(settings.exclusiveHandle); + auto setContext(uintptr context) -> bool { + if(_context == context) return true; + _context = context; + return initialize(); } - LPDIRECT3D9 context = nullptr; - LPDIRECT3DDEVICE9 device = nullptr; - LPDIRECT3DVERTEXBUFFER9 vertexBuffer = nullptr; - D3DPRESENT_PARAMETERS presentation = {}; - D3DCAPS9 capabilities = {}; - LPDIRECT3DTEXTURE9 texture = nullptr; - LPDIRECT3DSURFACE9 surface = nullptr; - - bool lost = true; - uint windowWidth; - uint windowHeight; - uint textureWidth; - uint textureHeight; - uint monitorWidth; - uint monitorHeight; - - struct Vertex { - float x, y, z, rhw; //screen coordinates - float u, v; //texture coordinates - }; - - struct { - uint32_t textureUsage; - uint32_t texturePool; - uint32_t vertexUsage; - uint32_t vertexPool; - uint32_t filter; - } flags; - - struct { - bool exclusive = false; - HWND handle = nullptr; - bool synchronize = false; - uint filter = Video::FilterLinear; - - HWND exclusiveHandle = nullptr; - uint width; - uint height; - } settings; - - auto cap(const string& name) -> bool { - if(name == Video::Exclusive) return true; - if(name == Video::Handle) return true; - if(name == Video::Synchronize) return true; - if(name == Video::Filter) return true; - if(name == Video::Shader) return false; - return false; + auto setBlocking(bool blocking) -> bool { + _blocking = blocking; + return true; } - auto get(const string& name) -> any { - if(name == Video::Exclusive) return settings.exclusive; - if(name == Video::Handle) return (uintptr_t)settings.handle; - if(name == Video::Synchronize) return settings.synchronize; - if(name == Video::Filter) return settings.filter; - return {}; + auto setSmooth(bool smooth) -> bool { + _smooth = smooth; + if(_ready) updateFilter(); + return true; } - auto set(const string& name, const any& value) -> bool { - if(name == Video::Exclusive && value.is()) { - settings.exclusive = value.get(); - if(context) init(); - return true; + auto clear() -> void { + if(_lost && !recover()) return; + + D3DSURFACE_DESC surfaceDescription; + _texture->GetLevelDesc(0, &surfaceDescription); + _texture->GetSurfaceLevel(0, &_surface); + + if(_surface) { + _device->ColorFill(_surface, 0, D3DCOLOR_XRGB(0x00, 0x00, 0x00)); + _surface->Release(); + _surface = nullptr; } - if(name == Video::Handle && value.is()) { - settings.handle = (HWND)value.get(); - return true; + //clear primary display and all backbuffers + for(uint n : range(3)) { + _device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0x00, 0x00, 0x00), 1.0f, 0); + _device->Present(0, 0, 0, 0); } - - if(name == Video::Synchronize && value.is()) { - settings.synchronize = value.get(); - return true; - } - - if(name == Video::Filter && value.is()) { - settings.filter = value.get(); - if(context) updateFilter(); - return true; - } - - return false; } + auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool { + if(_lost && !recover()) return false; + + if(width != _inputWidth || height != _inputHeight) { + resize(_inputWidth = width, _inputHeight = height); + } + + D3DSURFACE_DESC surfaceDescription; + _texture->GetLevelDesc(0, &surfaceDescription); + _texture->GetSurfaceLevel(0, &_surface); + + D3DLOCKED_RECT lockedRectangle; + _surface->LockRect(&lockedRectangle, 0, D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD); + pitch = lockedRectangle.Pitch; + return data = (uint32_t*)lockedRectangle.pBits; + } + + auto unlock() -> void { + _surface->UnlockRect(); + _surface->Release(); + _surface = nullptr; + } + + auto output() -> void { + if(_lost && !recover()) return; + + RECT rectangle; + GetClientRect((HWND)_context, &rectangle); + + //if output size changed, driver must be re-initialized. + //failure to do so causes scaling issues on some video drivers. + if(_windowWidth != rectangle.right || _windowHeight != rectangle.bottom) initialize(); + + _device->BeginScene(); + uint x = 0, y = 0; + if(_exclusive) { + //center output in exclusive mode fullscreen window + x = (_monitorWidth - _windowWidth) / 2; + y = (_monitorHeight - _windowHeight) / 2; + } + setVertex(0, 0, _inputWidth, _inputHeight, _textureWidth, _textureHeight, x, y, _windowWidth, _windowHeight); + _device->SetTexture(0, _texture); + _device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + _device->EndScene(); + + if(_blocking) { + D3DRASTER_STATUS status; + while(true) { //wait for a previous vblank to finish, if necessary + _device->GetRasterStatus(0, &status); + if(!status.InVBlank) break; + } + while(true) { //wait for next vblank to begin + _device->GetRasterStatus(0, &status); + if(status.InVBlank) break; + } + } + + if(_device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) _lost = true; + } + +private: auto recover() -> bool { - if(!device) return false; + if(!_device) return false; - if(lost) { - if(vertexBuffer) { vertexBuffer->Release(); vertexBuffer = nullptr; } - if(surface) { surface->Release(); surface = nullptr; } - if(texture) { texture->Release(); texture = nullptr; } - if(device->Reset(&presentation) != D3D_OK) return false; + if(_lost) { + if(_vertexBuffer) { _vertexBuffer->Release(); _vertexBuffer = nullptr; } + if(_surface) { _surface->Release(); _surface = nullptr; } + if(_texture) { _texture->Release(); _texture = nullptr; } + if(_device->Reset(&_presentation) != D3D_OK) return false; } - lost = false; + _lost = false; - device->SetDialogBoxMode(false); + _device->SetDialogBoxMode(false); - device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); - device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); - device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + _device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + _device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + _device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); - device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); - device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + _device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + _device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + _device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); - device->SetRenderState(D3DRS_LIGHTING, false); - device->SetRenderState(D3DRS_ZENABLE, false); - device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + _device->SetRenderState(D3DRS_LIGHTING, false); + _device->SetRenderState(D3DRS_ZENABLE, false); + _device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); - device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); - device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); - device->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + _device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + _device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + _device->SetRenderState(D3DRS_ALPHABLENDENABLE, false); - device->SetVertexShader(nullptr); - device->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1); + _device->SetVertexShader(nullptr); + _device->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1); - device->CreateVertexBuffer(sizeof(Vertex) * 4, flags.vertexUsage, D3DFVF_XYZRHW | D3DFVF_TEX1, - (D3DPOOL)flags.vertexPool, &vertexBuffer, nullptr); - textureWidth = 0; - textureHeight = 0; - resize(settings.width = 256, settings.height = 256); + _device->CreateVertexBuffer(sizeof(Vertex) * 4, _vertexUsage, D3DFVF_XYZRHW | D3DFVF_TEX1, + (D3DPOOL)_vertexPool, &_vertexBuffer, nullptr); + _textureWidth = 0; + _textureHeight = 0; + resize(_inputWidth = 256, _inputHeight = 256); updateFilter(); clear(); return true; } auto resize(uint width, uint height) -> void { - if(textureWidth >= width && textureHeight >= height) return; + if(_textureWidth >= width && _textureHeight >= height) return; - textureWidth = bit::round(max(width, textureWidth)); - textureHeight = bit::round(max(height, textureHeight)); + _textureWidth = bit::round(max(width, _textureWidth)); + _textureHeight = bit::round(max(height, _textureHeight)); - if(capabilities.MaxTextureWidth < textureWidth || capabilities.MaxTextureWidth < textureHeight) return; + if(_capabilities.MaxTextureWidth < _textureWidth || _capabilities.MaxTextureWidth < _textureHeight) return; - if(texture) texture->Release(); - device->CreateTexture(textureWidth, textureHeight, 1, flags.textureUsage, D3DFMT_X8R8G8B8, - (D3DPOOL)flags.texturePool, &texture, nullptr); + if(_texture) _texture->Release(); + _device->CreateTexture(_textureWidth, _textureHeight, 1, _textureUsage, D3DFMT_X8R8G8B8, + (D3DPOOL)_texturePool, &_texture, nullptr); } auto updateFilter() -> void { - if(!device) return; - if(lost && !recover()) return; + if(!_device) return; + if(_lost && !recover()) return; - flags.filter = settings.filter == Video::FilterNearest ? D3DTEXF_POINT : D3DTEXF_LINEAR; - device->SetSamplerState(0, D3DSAMP_MINFILTER, flags.filter); - device->SetSamplerState(0, D3DSAMP_MAGFILTER, flags.filter); + auto filter = !_smooth ? D3DTEXF_POINT : D3DTEXF_LINEAR; + _device->SetSamplerState(0, D3DSAMP_MINFILTER, filter); + _device->SetSamplerState(0, D3DSAMP_MAGFILTER, filter); } //(x,y) screen coordinates, in pixels @@ -212,164 +211,147 @@ struct VideoDirect3D : Video { vertex[2].v = vertex[3].v = (double)(py + h) / rh; LPDIRECT3DVERTEXBUFFER9* vertexPointer = nullptr; - vertexBuffer->Lock(0, sizeof(Vertex) * 4, (void**)&vertexPointer, 0); + _vertexBuffer->Lock(0, sizeof(Vertex) * 4, (void**)&vertexPointer, 0); memory::copy(vertexPointer, vertex, sizeof(Vertex) * 4); - vertexBuffer->Unlock(); + _vertexBuffer->Unlock(); - device->SetStreamSource(0, vertexBuffer, 0, sizeof(Vertex)); + _device->SetStreamSource(0, _vertexBuffer, 0, sizeof(Vertex)); } - auto clear() -> void { - if(lost && !recover()) return; + auto initialize() -> bool { + terminate(); + if(!_context) return false; - D3DSURFACE_DESC surfaceDescription; - texture->GetLevelDesc(0, &surfaceDescription); - texture->GetSurfaceLevel(0, &surface); + POINT point = {0, 0}; + HMONITOR monitor = MonitorFromPoint(point, MONITOR_DEFAULTTOPRIMARY); + MONITORINFOEX information = {}; + information.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(monitor, &information); + _monitorWidth = information.rcMonitor.right - information.rcMonitor.left; + _monitorHeight = information.rcMonitor.bottom - information.rcMonitor.top; - if(surface) { - device->ColorFill(surface, 0, D3DCOLOR_XRGB(0x00, 0x00, 0x00)); - surface->Release(); - surface = nullptr; - } + WNDCLASS windowClass = {}; + windowClass.cbClsExtra = 0; + windowClass.cbWndExtra = 0; + windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); + windowClass.hCursor = LoadCursor(0, IDC_ARROW); + windowClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION); + windowClass.hInstance = GetModuleHandle(0); + windowClass.lpfnWndProc = VideoDirect3D_WindowProcedure; + windowClass.lpszClassName = L"VideoDirect3D_Window"; + windowClass.lpszMenuName = 0; + windowClass.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&windowClass); - //clear primary display and all backbuffers - for(uint n : range(3)) { - device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0x00, 0x00, 0x00), 1.0f, 0); - device->Present(0, 0, 0, 0); - } - } - - auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool { - if(lost && !recover()) return false; - - if(width != settings.width || height != settings.height) { - resize(settings.width = width, settings.height = height); - } - - D3DSURFACE_DESC surfaceDescription; - texture->GetLevelDesc(0, &surfaceDescription); - texture->GetSurfaceLevel(0, &surface); - - D3DLOCKED_RECT lockedRectangle; - surface->LockRect(&lockedRectangle, 0, D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD); - pitch = lockedRectangle.Pitch; - return data = (uint32_t*)lockedRectangle.pBits; - } - - auto unlock() -> void { - surface->UnlockRect(); - surface->Release(); - surface = nullptr; - } - - auto refresh() -> void { - if(lost && !recover()) return; + _exclusiveContext = (uintptr)CreateWindowEx(WS_EX_TOPMOST, L"VideoDirect3D_Window", L"", WS_POPUP, + information.rcMonitor.left, information.rcMonitor.top, _monitorWidth, _monitorHeight, + nullptr, nullptr, GetModuleHandle(0), nullptr); RECT rectangle; - GetClientRect(settings.handle, &rectangle); + GetClientRect((HWND)_context, &rectangle); + _windowWidth = rectangle.right; + _windowHeight = rectangle.bottom; - //if output size changed, driver must be re-initialized. - //failure to do so causes scaling issues on some video drivers. - if(windowWidth != rectangle.right || windowHeight != rectangle.bottom) init(); + _instance = Direct3DCreate9(D3D_SDK_VERSION); + if(!_instance) return false; - device->BeginScene(); - uint x = 0, y = 0; - if(settings.exclusive) { - //center output in exclusive mode fullscreen window - x = (monitorWidth - windowWidth) / 2; - y = (monitorHeight - windowHeight) / 2; - } - setVertex(0, 0, settings.width, settings.height, textureWidth, textureHeight, x, y, windowWidth, windowHeight); - device->SetTexture(0, texture); - device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); - device->EndScene(); + memory::fill(&_presentation, sizeof(_presentation)); + _presentation.Flags = D3DPRESENTFLAG_VIDEO; + _presentation.SwapEffect = D3DSWAPEFFECT_DISCARD; + _presentation.BackBufferCount = 1; + _presentation.MultiSampleType = D3DMULTISAMPLE_NONE; + _presentation.MultiSampleQuality = 0; + _presentation.EnableAutoDepthStencil = false; + _presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN; + _presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; - if(settings.synchronize) { - D3DRASTER_STATUS status; - while(true) { //wait for a previous vblank to finish, if necessary - device->GetRasterStatus(0, &status); - if(!status.InVBlank) break; - } - while(true) { //wait for next vblank to begin - device->GetRasterStatus(0, &status); - if(status.InVBlank) break; - } - } + if(!_exclusive) { + _presentation.hDeviceWindow = (HWND)_context; + _presentation.Windowed = true; + _presentation.BackBufferFormat = D3DFMT_UNKNOWN; + _presentation.BackBufferWidth = 0; + _presentation.BackBufferHeight = 0; - if(device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) lost = true; - } - - auto init() -> bool { - term(); - - RECT rectangle; - GetClientRect(settings.handle, &rectangle); - windowWidth = rectangle.right; - windowHeight = rectangle.bottom; - - context = Direct3DCreate9(D3D_SDK_VERSION); - if(!context) return false; - - memory::fill(&presentation, sizeof(presentation)); - presentation.Flags = D3DPRESENTFLAG_VIDEO; - presentation.SwapEffect = D3DSWAPEFFECT_DISCARD; - presentation.BackBufferCount = 1; - presentation.MultiSampleType = D3DMULTISAMPLE_NONE; - presentation.MultiSampleQuality = 0; - presentation.EnableAutoDepthStencil = false; - presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN; - presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; - - if(!settings.exclusive) { - presentation.hDeviceWindow = settings.handle; - presentation.Windowed = true; - presentation.BackBufferFormat = D3DFMT_UNKNOWN; - presentation.BackBufferWidth = 0; - presentation.BackBufferHeight = 0; - - ShowWindow(settings.exclusiveHandle, SW_HIDE); - if(context->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, settings.handle, - D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) { + ShowWindow((HWND)_exclusiveContext, SW_HIDE); + if(_instance->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, (HWND)_context, + D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &_presentation, &_device) != D3D_OK) { return false; } } else { - presentation.hDeviceWindow = settings.exclusiveHandle; - presentation.Windowed = false; - presentation.BackBufferFormat = D3DFMT_X8R8G8B8; - presentation.BackBufferWidth = monitorWidth; - presentation.BackBufferHeight = monitorHeight; - presentation.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; + _presentation.hDeviceWindow = (HWND)_exclusiveContext; + _presentation.Windowed = false; + _presentation.BackBufferFormat = D3DFMT_X8R8G8B8; + _presentation.BackBufferWidth = _monitorWidth; + _presentation.BackBufferHeight = _monitorHeight; + _presentation.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; - ShowWindow(settings.exclusiveHandle, SW_SHOWNORMAL); - if(context->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, settings.exclusiveHandle, - D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) { + ShowWindow((HWND)_exclusiveContext, SW_SHOWNORMAL); + if(_instance->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, (HWND)_exclusiveContext, + D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &_presentation, &_device) != D3D_OK) { return false; } } - device->GetDeviceCaps(&capabilities); + _device->GetDeviceCaps(&_capabilities); - if(capabilities.Caps2 & D3DCAPS2_DYNAMICTEXTURES) { - flags.textureUsage = D3DUSAGE_DYNAMIC; - flags.texturePool = D3DPOOL_DEFAULT; - flags.vertexUsage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC; - flags.vertexPool = D3DPOOL_DEFAULT; + if(_capabilities.Caps2 & D3DCAPS2_DYNAMICTEXTURES) { + _textureUsage = D3DUSAGE_DYNAMIC; + _texturePool = D3DPOOL_DEFAULT; + _vertexUsage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC; + _vertexPool = D3DPOOL_DEFAULT; } else { - flags.textureUsage = 0; - flags.texturePool = D3DPOOL_MANAGED; - flags.vertexUsage = D3DUSAGE_WRITEONLY; - flags.vertexPool = D3DPOOL_MANAGED; + _textureUsage = 0; + _texturePool = D3DPOOL_MANAGED; + _vertexUsage = D3DUSAGE_WRITEONLY; + _vertexPool = D3DPOOL_MANAGED; } - lost = false; - return recover(); + _lost = false; + return _ready = recover(); } - auto term() -> void { - if(vertexBuffer) { vertexBuffer->Release(); vertexBuffer = nullptr; } - if(surface) { surface->Release(); surface = nullptr; } - if(texture) { texture->Release(); texture = nullptr; } - if(device) { device->Release(); device = nullptr; } - if(context) { context->Release(); context = nullptr; } + auto terminate() -> void { + if(_vertexBuffer) { _vertexBuffer->Release(); _vertexBuffer = nullptr; } + if(_surface) { _surface->Release(); _surface = nullptr; } + if(_texture) { _texture->Release(); _texture = nullptr; } + if(_device) { _device->Release(); _device = nullptr; } + if(_instance) { _instance->Release(); _instance = nullptr; } + if(_exclusiveContext) { DestroyWindow((HWND)_exclusiveContext); _exclusiveContext = 0; } } + + struct Vertex { + float x, y, z, rhw; //screen coordinates + float u, v; //texture coordinates + }; + + bool _exclusive = false; + bool _ready = false; + uintptr _context = 0; + bool _blocking = false; + bool _smooth = true; + + uintptr _exclusiveContext = 0; + + LPDIRECT3D9 _instance = nullptr; + LPDIRECT3DDEVICE9 _device = nullptr; + LPDIRECT3DVERTEXBUFFER9 _vertexBuffer = nullptr; + D3DPRESENT_PARAMETERS _presentation = {}; + D3DCAPS9 _capabilities = {}; + LPDIRECT3DTEXTURE9 _texture = nullptr; + LPDIRECT3DSURFACE9 _surface = nullptr; + + bool _lost = true; + uint _windowWidth; + uint _windowHeight; + uint _textureWidth; + uint _textureHeight; + uint _monitorWidth; + uint _monitorHeight; + uint _inputWidth; + uint _inputHeight; + + uint32_t _textureUsage; + uint32_t _texturePool; + uint32_t _vertexUsage; + uint32_t _vertexPool; }; diff --git a/ruby/video/xshm.cpp b/ruby/video/xshm.cpp index 9f8f8b6c..9a493b11 100644 --- a/ruby/video/xshm.cpp +++ b/ruby/video/xshm.cpp @@ -9,64 +9,35 @@ #include struct VideoXShm : Video { - ~VideoXShm() { term(); } + VideoXShm() { initialize(); } + ~VideoXShm() { terminate(); } - struct Device { - Display* display = nullptr; - int screen = 0; - int depth = 0; - Visual* visual = nullptr; - Window window = 0; + auto ready() -> bool { return _ready; } - XShmSegmentInfo shmInfo; - XImage* image = nullptr; - uint32_t* buffer = nullptr; - uint width = 0; - uint height = 0; - } device; + auto context() -> uintptr { return _context; } + auto smooth() -> bool { return _smooth; } - struct Settings { - uintptr_t handle = 0; - uint filter = Video::FilterLinear; - - uint32_t* buffer = nullptr; - uint width = 0; - uint height = 0; - } settings; - - auto cap(const string& name) -> bool { - if(name == Video::Handle) return true; - if(name == Video::Filter) return true; - return false; + auto setContext(uintptr context) -> bool { + if(_context == context) return true; + _context = context; + return initialize(); } - auto get(const string& name) -> any { - if(name == Video::Handle) return settings.handle; - if(name == Video::Filter) return settings.filter; - return {}; - } - - auto set(const string& name, const any& value) -> bool { - if(name == Video::Handle && value.is()) { - settings.handle = value.get(); - return true; - } - if(name == Video::Filter && value.is()) { - settings.filter = value.get(); - return true; - } - return false; + auto setSmooth(bool smooth) -> bool { + _smooth = smooth; + return true; } auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool { - if(!settings.buffer || settings.width != width || settings.height != height) { - if(settings.buffer) delete[] settings.buffer; - settings.width = width, settings.height = height; - settings.buffer = new uint32_t[width * height + 16]; //+16 is padding for linear interpolation + if(!_inputBuffer || _inputWidth != width || _inputHeight != height) { + if(_inputBuffer) delete[] _inputBuffer; + _inputWidth = width; + _inputHeight = height; + _inputBuffer = new uint32_t[width * height + 16]; //+16 is padding for linear interpolation } - data = settings.buffer; - pitch = settings.width * sizeof(uint32_t); + data = _inputBuffer; + pitch = _inputWidth * sizeof(uint32_t); return true; } @@ -74,124 +45,124 @@ struct VideoXShm : Video { } auto clear() -> void { - if(!settings.buffer) return; - uint32_t* dp = settings.buffer; - uint length = settings.width * settings.height; + if(!_ready) return; + auto dp = _inputBuffer; + uint length = _inputWidth * _inputHeight; while(length--) *dp++ = 255u << 24; - refresh(); + output(); } - auto refresh() -> void { - if(!settings.buffer) return; + auto output() -> void { + if(!_ready) return; size(); - float xratio = (float)settings.width / (float)device.width; - float yratio = (float)settings.height / (float)device.height; + float xratio = (float)_inputWidth / (float)_outputWidth; + float yratio = (float)_inputHeight / (float)_outputHeight; #pragma omp parallel for - for(uint y = 0; y < device.height; y++) { + for(uint y = 0; y < _outputHeight; y++) { float ystep = y * yratio; float xstep = 0; - uint32_t* sp = settings.buffer + (uint)ystep * settings.width; - uint32_t* dp = device.buffer + y * device.width; + uint32_t* sp = _inputBuffer + (uint)ystep * _inputWidth; + uint32_t* dp = _outputBuffer + y * _outputWidth; - if(settings.filter == Video::FilterNearest) { - for(uint x = 0; x < device.width; x++) { + if(!_smooth) { + for(uint x = 0; x < _outputWidth; x++) { *dp++ = 255u << 24 | sp[(uint)xstep]; xstep += xratio; } - } else { //settings.filter == Video::FilterLinear - for(uint x = 0; x < device.width; x++) { + } else { + for(uint x = 0; x < _outputWidth; x++) { *dp++ = 255u << 24 | interpolate(xstep - (uint)xstep, sp[(uint)xstep], sp[(uint)xstep + 1]); xstep += xratio; } } } - GC gc = XCreateGC(device.display, device.window, 0, 0); - XShmPutImage( - device.display, device.window, gc, device.image, - 0, 0, 0, 0, device.width, device.height, False - ); - XFreeGC(device.display, gc); - XFlush(device.display); + GC gc = XCreateGC(_display, _window, 0, 0); + XShmPutImage(_display, _window, gc, _image, 0, 0, 0, 0, _outputWidth, _outputHeight, False); + XFreeGC(_display, gc); + XFlush(_display); } - auto init() -> bool { - device.display = XOpenDisplay(0); - device.screen = DefaultScreen(device.display); +private: + auto initialize() -> bool { + terminate(); + if(!_context) return false; + + _display = XOpenDisplay(0); + _screen = DefaultScreen(_display); XWindowAttributes getAttributes; - XGetWindowAttributes(device.display, (Window)settings.handle, &getAttributes); - device.depth = getAttributes.depth; - device.visual = getAttributes.visual; + XGetWindowAttributes(_display, (Window)_context, &getAttributes); + _depth = getAttributes.depth; + _visual = getAttributes.visual; //driver only supports 32-bit pixels //note that even on 15-bit and 16-bit displays, the window visual's depth should be 32 - if(device.depth < 24 || device.depth > 32) { + if(_depth < 24 || _depth > 32) { free(); return false; } XSetWindowAttributes setAttributes = {0}; setAttributes.border_pixel = 0; - device.window = XCreateWindow(device.display, (Window)settings.handle, + _window = XCreateWindow(_display, (Window)_context, 0, 0, 256, 256, 0, getAttributes.depth, InputOutput, getAttributes.visual, CWBorderPixel, &setAttributes ); - XSetWindowBackground(device.display, device.window, 0); - XMapWindow(device.display, device.window); - XFlush(device.display); + XSetWindowBackground(_display, _window, 0); + XMapWindow(_display, _window); + XFlush(_display); - while(XPending(device.display)) { + while(XPending(_display)) { XEvent event; - XNextEvent(device.display, &event); + XNextEvent(_display, &event); } if(!size()) return false; - return true; + return _ready = true; } - auto term() -> void { + auto terminate() -> void { free(); - if(device.display) { - XCloseDisplay(device.display); - device.display = nullptr; + if(_display) { + XCloseDisplay(_display); + _display = nullptr; } } -private: auto size() -> bool { XWindowAttributes windowAttributes; - XGetWindowAttributes(device.display, settings.handle, &windowAttributes); + XGetWindowAttributes(_display, (Window)_context, &windowAttributes); - if(device.buffer && device.width == windowAttributes.width && device.height == windowAttributes.height) return true; - device.width = windowAttributes.width, device.height = windowAttributes.height; - XResizeWindow(device.display, device.window, device.width, device.height); + if(_outputBuffer && _outputWidth == windowAttributes.width && _outputHeight == windowAttributes.height) return true; + _outputWidth = windowAttributes.width; + _outputHeight = windowAttributes.height; + XResizeWindow(_display, _window, _outputWidth, _outputHeight); free(); - device.shmInfo.shmid = shmget(IPC_PRIVATE, device.width * device.height * sizeof(uint32_t), IPC_CREAT | 0777); - if(device.shmInfo.shmid < 0) return false; + _shmInfo.shmid = shmget(IPC_PRIVATE, _outputWidth * _outputHeight * sizeof(uint32_t), IPC_CREAT | 0777); + if(_shmInfo.shmid < 0) return false; - device.shmInfo.shmaddr = (char*)shmat(device.shmInfo.shmid, 0, 0); - device.shmInfo.readOnly = False; - XShmAttach(device.display, &device.shmInfo); - device.buffer = (uint32_t*)device.shmInfo.shmaddr; - device.image = XShmCreateImage(device.display, device.visual, device.depth, - ZPixmap, device.shmInfo.shmaddr, &device.shmInfo, device.width, device.height - ); + _shmInfo.shmaddr = (char*)shmat(_shmInfo.shmid, 0, 0); + _shmInfo.readOnly = False; + XShmAttach(_display, &_shmInfo); + _outputBuffer = (uint32_t*)_shmInfo.shmaddr; + _image = XShmCreateImage(_display, _visual, _depth, ZPixmap, _shmInfo.shmaddr, &_shmInfo, _outputWidth, _outputHeight); return true; } auto free() -> void { - if(!device.buffer) return; - device.buffer = nullptr; - XShmDetach(device.display, &device.shmInfo); - XDestroyImage(device.image); - shmdt(device.shmInfo.shmaddr); - shmctl(device.shmInfo.shmid, IPC_RMID, 0); + if(_outputBuffer) { + _outputBuffer = nullptr; + XShmDetach(_display, &_shmInfo); + XDestroyImage(_image); + shmdt(_shmInfo.shmaddr); + shmctl(_shmInfo.shmid, IPC_RMID, 0); + } } alwaysinline auto interpolate(float mu, uint32_t a, uint32_t b) -> uint32_t { @@ -202,4 +173,25 @@ private: uint8_t cb = ab * (1.0 - mu) + bb * mu; return cr << 16 | cg << 8 | cb << 0; } + + bool _ready = false; + uintptr _context = 0; + bool _smooth = true; + + uint32_t* _inputBuffer = nullptr; + uint _inputWidth = 0; + uint _inputHeight = 0; + + Display* _display = nullptr; + int _screen = 0; + int _depth = 0; + Visual* _visual = nullptr; + Window _window = 0; + + XShmSegmentInfo _shmInfo; + XImage* _image = nullptr; + + uint32_t* _outputBuffer = nullptr; + uint _outputWidth = 0; + uint _outputHeight = 0; };