mirror of https://github.com/bsnes-emu/bsnes.git
Update to v103r15 release.
byuu says: Changelog: - ruby: rewrote the API interfaces for Video, Audio, Input - ruby/audio: can now select the number of output channels (not useful to higan, sorry) - ruby/asio: various improvements - tomoko: audio settings panel can now select separate audio devices (for ASIO, OSS so far) - tomoko: audio settings panel frequency and latency lists are dynamically populated now Note: due to the ruby API rewrite, most drivers will not compile. Right now, the following work: - video: Direct3D, XShm - audio: ASIO, OSS - input: Windows, SDL, Xlib It takes a really long time to rewrite these (six hours to do the above), so it's going to be a while before we're back at 100% functionality again. Errata: - ASIO needs device(), setDevice() - need to call setDevice() at program startup to populate frequency/latency settings properly - changing the device and/or frequency needs to update the emulator resampler rates The really hard part is going to be the last one: the only way to change the emulator frequency is to flush all the audio streams and then recompute all the coefficients for the resamplers. If this is called during emulation, all audio streams will be erased and thus no sound will be output. I'll most likely be forced to simply ignore device/frequency changes until the user loads another game. It is at least possible to toggle the latency dynamically.
This commit is contained in:
parent
17697317d4
commit
4129630d97
|
@ -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.14";
|
static const string Version = "103.15";
|
||||||
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/";
|
||||||
|
|
|
@ -18,20 +18,20 @@ 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.glx video.xv video.xshm video.sdl
|
ruby += video.xshm #video.glx video.xv video.xshm video.sdl
|
||||||
ruby += audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao
|
ruby += audio.oss #audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao
|
||||||
ruby += input.udev input.sdl input.xlib
|
ruby += input.sdl input.xlib #input.udev input.sdl input.xlib
|
||||||
else ifeq ($(platform),bsd)
|
else ifeq ($(platform),bsd)
|
||||||
ruby += video.glx video.xv video.xshm video.sdl
|
ruby += video.xshm #video.glx video.xv video.xshm video.sdl
|
||||||
ruby += audio.openal audio.oss
|
ruby += audio.oss #audio.alsa
|
||||||
ruby += input.sdl input.xlib
|
ruby += input.sdl input.xlib
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
|
@ -42,13 +42,14 @@ Settings::Settings() {
|
||||||
|
|
||||||
set("Audio/Driver", ruby::Audio::optimalDriver());
|
set("Audio/Driver", ruby::Audio::optimalDriver());
|
||||||
set("Audio/Device", "");
|
set("Audio/Device", "");
|
||||||
|
set("Audio/Frequency", 48000);
|
||||||
|
set("Audio/Latency", 0);
|
||||||
set("Audio/Exclusive", false);
|
set("Audio/Exclusive", false);
|
||||||
set("Audio/Synchronize", true);
|
set("Audio/Synchronize", true);
|
||||||
set("Audio/Mute", false);
|
set("Audio/Mute", false);
|
||||||
set("Audio/Volume", 100);
|
set("Audio/Volume", 100);
|
||||||
set("Audio/Balance", 50);
|
set("Audio/Balance", 50);
|
||||||
set("Audio/Reverb/Enable", false);
|
set("Audio/Reverb/Enable", false);
|
||||||
set("Audio/Latency", 60);
|
|
||||||
set("Audio/Resampler", "Sinc");
|
set("Audio/Resampler", "Sinc");
|
||||||
|
|
||||||
set("Input/Driver", ruby::Input::optimalDriver());
|
set("Input/Driver", ruby::Input::optimalDriver());
|
||||||
|
|
|
@ -62,12 +62,12 @@ auto InputManager::appendHotkeys() -> void {
|
||||||
{ auto hotkey = new InputHotkey;
|
{ auto hotkey = new InputHotkey;
|
||||||
hotkey->name = "Fast Forward";
|
hotkey->name = "Fast Forward";
|
||||||
hotkey->press = [] {
|
hotkey->press = [] {
|
||||||
video->set(Video::Synchronize, false);
|
video->setBlocking(false);
|
||||||
audio->set(Audio::Synchronize, false);
|
audio->setBlocking(false);
|
||||||
};
|
};
|
||||||
hotkey->release = [] {
|
hotkey->release = [] {
|
||||||
video->set(Video::Synchronize, settings["Video/Synchronize"].boolean());
|
video->setBlocking(settings["Video/Synchronize"].boolean());
|
||||||
audio->set(Audio::Synchronize, settings["Audio/Synchronize"].boolean());
|
audio->setBlocking(settings["Audio/Synchronize"].boolean());
|
||||||
};
|
};
|
||||||
hotkeys.append(hotkey);
|
hotkeys.append(hotkey);
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,11 +84,11 @@ Presentation::Presentation() {
|
||||||
loadShaders();
|
loadShaders();
|
||||||
synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Synchronize"].boolean()).setVisible(false).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->set(Video::Synchronize, synchronizeVideo.checked());
|
video->setBlocking(synchronizeVideo.checked());
|
||||||
});
|
});
|
||||||
synchronizeAudio.setText("Synchronize Audio").setChecked(settings["Audio/Synchronize"].boolean()).onToggle([&] {
|
synchronizeAudio.setText("Synchronize Audio").setChecked(settings["Audio/Synchronize"].boolean()).onToggle([&] {
|
||||||
settings["Audio/Synchronize"].setValue(synchronizeAudio.checked());
|
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([&] {
|
muteAudio.setText("Mute Audio").setChecked(settings["Audio/Mute"].boolean()).onToggle([&] {
|
||||||
settings["Audio/Mute"].setValue(muteAudio.checked());
|
settings["Audio/Mute"].setValue(muteAudio.checked());
|
||||||
|
@ -229,7 +229,7 @@ auto Presentation::clearViewport() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
video->unlock();
|
video->unlock();
|
||||||
video->refresh();
|
video->output();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,11 +313,11 @@ auto Presentation::toggleFullScreen() -> void {
|
||||||
menuBar.setVisible(false);
|
menuBar.setVisible(false);
|
||||||
statusBar.setVisible(false);
|
statusBar.setVisible(false);
|
||||||
setFullScreen(true);
|
setFullScreen(true);
|
||||||
video->set(Video::Exclusive, settings["Video/Fullscreen/Exclusive"].boolean());
|
video->setExclusive(settings["Video/Fullscreen/Exclusive"].boolean());
|
||||||
if(!input->acquired()) input->acquire();
|
if(!input->acquired()) input->acquire();
|
||||||
} else {
|
} else {
|
||||||
if(input->acquired()) input->release();
|
if(input->acquired()) input->release();
|
||||||
video->set(Video::Exclusive, false);
|
video->setExclusive(false);
|
||||||
setFullScreen(false);
|
setFullScreen(false);
|
||||||
menuBar.setVisible(true);
|
menuBar.setVisible(true);
|
||||||
statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean());
|
statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean());
|
||||||
|
|
|
@ -74,7 +74,7 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig
|
||||||
}
|
}
|
||||||
|
|
||||||
video->unlock();
|
video->unlock();
|
||||||
video->refresh();
|
video->output();
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint frameCounter = 0;
|
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 {
|
auto Program::audioSample(const double* samples, uint channels) -> void {
|
||||||
int16 left = sclamp<16>(samples[0] * 32768.0);
|
audio->output(samples);
|
||||||
int16 right = sclamp<16>(samples[1] * 32768.0);
|
|
||||||
audio->sample(left, right);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
|
auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
|
||||||
|
|
|
@ -19,7 +19,7 @@ auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interfa
|
||||||
|
|
||||||
mediumPaths.append(locate({medium.name, ".sys/"}));
|
mediumPaths.append(locate({medium.name, ".sys/"}));
|
||||||
|
|
||||||
Emulator::audio.reset(2, audio->get(Audio::Frequency).get<uint>(44100));
|
Emulator::audio.reset(2, audio->frequency());
|
||||||
inputManager->bind(emulator = &interface);
|
inputManager->bind(emulator = &interface);
|
||||||
if(!emulator->load(medium.id)) {
|
if(!emulator->load(medium.id)) {
|
||||||
emulator = nullptr;
|
emulator = nullptr;
|
||||||
|
|
|
@ -35,23 +35,23 @@ Program::Program(string_vector args) {
|
||||||
presentation->setVisible();
|
presentation->setVisible();
|
||||||
|
|
||||||
video = Video::create(settings["Video/Driver"].text());
|
video = Video::create(settings["Video/Driver"].text());
|
||||||
video->set(Video::Handle, presentation->viewport.handle());
|
video->setContext(presentation->viewport.handle());
|
||||||
video->set(Video::Synchronize, settings["Video/Synchronize"].boolean());
|
video->setBlocking(settings["Video/Synchronize"].boolean());
|
||||||
if(!video->init()) video = Video::create("None");
|
if(!video->ready()) MessageDialog().setText("Failed to initialize video driver").warning();
|
||||||
|
|
||||||
presentation->clearViewport();
|
presentation->clearViewport();
|
||||||
|
|
||||||
audio = Audio::create(settings["Audio/Driver"].text());
|
audio = Audio::create(settings["Audio/Driver"].text());
|
||||||
audio->set(Audio::Device, settings["Audio/Device"].text());
|
audio->setContext(presentation->viewport.handle());
|
||||||
audio->set(Audio::Handle, presentation->viewport.handle());
|
audio->setBlocking(settings["Audio/Synchronize"].boolean());
|
||||||
audio->set(Audio::Synchronize, settings["Audio/Synchronize"].boolean());
|
audio->setFrequency(settings["Audio/Frequency"].natural());
|
||||||
audio->set(Audio::Latency, 80u);
|
audio->setChannels(2);
|
||||||
if(!audio->init()) audio = Audio::create("None");
|
if(!audio->ready()) MessageDialog().setText("Failed to initialize audio driver").warning();
|
||||||
|
|
||||||
input = Input::create(settings["Input/Driver"].text());
|
input = Input::create(settings["Input/Driver"].text());
|
||||||
input->set(Input::Handle, presentation->viewport.handle());
|
input->setContext(presentation->viewport.handle());
|
||||||
input->onChange({&InputManager::onChange, &inputManager()});
|
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 InputManager;
|
||||||
new SettingsManager;
|
new SettingsManager;
|
||||||
|
|
|
@ -67,19 +67,21 @@ auto Program::updateVideoShader() -> void {
|
||||||
&& settings["Video/Shader"].text() != "Blur"
|
&& settings["Video/Shader"].text() != "Blur"
|
||||||
&& directory::exists(settings["Video/Shader"].text())
|
&& directory::exists(settings["Video/Shader"].text())
|
||||||
) {
|
) {
|
||||||
video->set(Video::Filter, Video::FilterNearest);
|
video->setSmooth(false);
|
||||||
video->set(Video::Shader, settings["Video/Shader"].text());
|
video->setShader(settings["Video/Shader"].text());
|
||||||
} else {
|
} else {
|
||||||
video->set(Video::Filter, settings["Video/Shader"].text() == "Blur" ? Video::FilterLinear : Video::FilterNearest);
|
video->setSmooth(settings["Video/Shader"].text() == "Blur");
|
||||||
video->set(Video::Shader, (string)"");
|
video->setShader("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::updateAudioDriver() -> void {
|
auto Program::updateAudioDriver() -> void {
|
||||||
if(!audio) return;
|
if(!audio) return;
|
||||||
audio->clear();
|
audio->clear();
|
||||||
audio->set(Audio::Exclusive, settings["Audio/Exclusive"].boolean());
|
audio->setDevice(settings["Audio/Device"].text());
|
||||||
audio->set(Audio::Latency, (uint)settings["Audio/Latency"].natural());
|
audio->setExclusive(settings["Audio/Exclusive"].boolean());
|
||||||
|
//audio->setFrequency(settings["Audio/Frequency"].natural());
|
||||||
|
audio->setLatency(settings["Audio/Latency"].natural());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::updateAudioEffects() -> void {
|
auto Program::updateAudioEffects() -> void {
|
||||||
|
@ -95,12 +97,7 @@ auto Program::updateAudioEffects() -> void {
|
||||||
|
|
||||||
auto Program::focused() -> bool {
|
auto Program::focused() -> bool {
|
||||||
//exclusive mode creates its own top-level window: presentation window will not have focus
|
//exclusive mode creates its own top-level window: presentation window will not have focus
|
||||||
if(video->cap(Video::Exclusive)) {
|
if(video->exclusive()) return true;
|
||||||
auto value = video->get(Video::Exclusive);
|
|
||||||
if(value.is<bool>() && value.get<bool>()) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(presentation && presentation->focused()) return true;
|
if(presentation && presentation->focused()) return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,35 +6,37 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||||
|
|
||||||
driverLabel.setFont(Font().setBold()).setText("Driver Settings");
|
driverLabel.setFont(Font().setBold()).setText("Driver Settings");
|
||||||
|
|
||||||
latencyLabel.setText("Latency:");
|
auto information = audio->information();
|
||||||
latencyCombo.append(ComboButtonItem().setText("0ms"));
|
|
||||||
latencyCombo.append(ComboButtonItem().setText("20ms"));
|
deviceLabel.setText("Device:");
|
||||||
latencyCombo.append(ComboButtonItem().setText("40ms"));
|
for(auto& device : information.devices) {
|
||||||
latencyCombo.append(ComboButtonItem().setText("60ms"));
|
deviceList.append(ComboButtonItem().setText(device));
|
||||||
latencyCombo.append(ComboButtonItem().setText("80ms"));
|
if(device == settings["Audio/Device"].text()) {
|
||||||
latencyCombo.append(ComboButtonItem().setText("100ms"));
|
deviceList.item(deviceList.itemCount() - 1).setSelected();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
latencyCombo.onChange([&] { updateDriver(); });
|
}
|
||||||
|
deviceList.onChange([&] { updateDriver(); });
|
||||||
|
|
||||||
frequencyLabel.setText("Frequency:");
|
frequencyLabel.setText("Frequency:");
|
||||||
auto frequencyValue = audio->get(Audio::Frequency).get<uint>(44100);
|
for(auto& frequency : information.frequencies) {
|
||||||
frequencyCombo.append(ComboButtonItem().setText({frequencyValue, "hz"}));
|
frequencyList.append(ComboButtonItem().setText(frequency));
|
||||||
frequencyCombo.setEnabled(false);
|
if(frequency == settings["Audio/Frequency"].natural()) {
|
||||||
|
frequencyList.item(frequencyList.itemCount() - 1).setSelected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frequencyList.onChange([&] { updateDriver(); });
|
||||||
|
|
||||||
resamplerLabel.setText("Resampler:");
|
latencyLabel.setText("Latency:");
|
||||||
resamplerCombo.append(ComboButtonItem().setText("IIR - Biquad"));
|
for(auto& latency : information.latencies) {
|
||||||
resamplerCombo.setEnabled(false);
|
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.setText("Exclusive mode");
|
||||||
exclusiveMode.setChecked(settings["Audio/Exclusive"].boolean()).onToggle([&] { updateDriver(); });
|
exclusiveMode.setChecked(settings["Audio/Exclusive"].boolean()).onToggle([&] { updateDriver(); });
|
||||||
if(!audio->cap(Audio::Exclusive)) exclusiveMode.remove();
|
|
||||||
|
|
||||||
effectsLabel.setFont(Font().setBold()).setText("Effects");
|
effectsLabel.setFont(Font().setBold()).setText("Effects");
|
||||||
|
|
||||||
|
@ -53,17 +55,9 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto AudioSettings::updateDriver() -> void {
|
auto AudioSettings::updateDriver() -> void {
|
||||||
if(auto item = latencyCombo.selected()) {
|
settings["Audio/Device"].setValue(deviceList.selected().text());
|
||||||
uint latency = 60;
|
settings["Audio/Frequency"].setValue(frequencyList.selected().text());
|
||||||
if(item->offset() == 0) latency = 0;
|
settings["Audio/Latency"].setValue(latencyList.selected().text());
|
||||||
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/Exclusive"].setValue(exclusiveMode.checked());
|
settings["Audio/Exclusive"].setValue(exclusiveMode.checked());
|
||||||
program->updateAudioDriver();
|
program->updateAudioDriver();
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,12 +45,12 @@ struct AudioSettings : TabFrameItem {
|
||||||
VerticalLayout layout{this};
|
VerticalLayout layout{this};
|
||||||
Label driverLabel{&layout, Size{~0, 0}, 2};
|
Label driverLabel{&layout, Size{~0, 0}, 2};
|
||||||
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
|
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
|
||||||
Label latencyLabel{&controlLayout, Size{0, 0}};
|
Label deviceLabel{&controlLayout, Size{0, 0}};
|
||||||
ComboButton latencyCombo{&controlLayout, Size{~0, 0}};
|
ComboButton deviceList{&controlLayout, Size{~0, 0}};
|
||||||
Label frequencyLabel{&controlLayout, Size{0, 0}};
|
Label frequencyLabel{&controlLayout, Size{0, 0}};
|
||||||
ComboButton frequencyCombo{&controlLayout, Size{~0, 0}};
|
ComboButton frequencyList{&controlLayout, Size{~0, 0}};
|
||||||
Label resamplerLabel{&controlLayout, Size{0, 0}};
|
Label latencyLabel{&controlLayout, Size{0, 0}};
|
||||||
ComboButton resamplerCombo{&controlLayout, Size{~0, 0}};
|
ComboButton latencyList{&controlLayout, Size{~0, 0}};
|
||||||
CheckLabel exclusiveMode{&layout, Size{~0, 0}};
|
CheckLabel exclusiveMode{&layout, Size{~0, 0}};
|
||||||
Label effectsLabel{&layout, Size{~0, 0}, 2};
|
Label effectsLabel{&layout, Size{~0, 0}, 2};
|
||||||
HorizontalLayout volumeLayout{&layout, Size{~0, 0}};
|
HorizontalLayout volumeLayout{&layout, Size{~0, 0}};
|
||||||
|
|
|
@ -32,7 +32,6 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||||
fullscreenModeAspectCorrection.setText("Aspect correction").setChecked(settings["Video/Fullscreen/AspectCorrection"].boolean()).onToggle([&] { updateViewport(); });
|
fullscreenModeAspectCorrection.setText("Aspect correction").setChecked(settings["Video/Fullscreen/AspectCorrection"].boolean()).onToggle([&] { updateViewport(); });
|
||||||
fullscreenModeIntegralScaling.setText("Integral scaling").setChecked(settings["Video/Fullscreen/IntegralScaling"].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(); });
|
fullscreenModeExclusive.setText("Exclusive mode").setChecked(settings["Video/Fullscreen/Exclusive"].boolean()).onToggle([&] { updateViewport(); });
|
||||||
if(!video->cap(Video::Exclusive)) fullscreenModeExclusive.remove();
|
|
||||||
|
|
||||||
updateColor(true);
|
updateColor(true);
|
||||||
updateViewport(true);
|
updateViewport(true);
|
||||||
|
|
|
@ -1,90 +1,273 @@
|
||||||
#include "asio.hpp"
|
#include "asio.hpp"
|
||||||
|
|
||||||
struct AudioASIO : Audio {
|
struct AudioASIO : Audio {
|
||||||
~AudioASIO() { term(); }
|
static AudioASIO* self;
|
||||||
|
AudioASIO() { self = this; initialize(); }
|
||||||
|
~AudioASIO() { terminate(); }
|
||||||
|
|
||||||
struct Settings {
|
auto ready() -> bool { return _ready; }
|
||||||
HWND handle = nullptr;
|
|
||||||
bool synchronize = true;
|
|
||||||
uint frequency = 48000;
|
|
||||||
} settings;
|
|
||||||
|
|
||||||
struct Driver {
|
auto information() -> Information {
|
||||||
string name;
|
Information information;
|
||||||
string classID;
|
for(auto& device : _devices) information.devices.append(device.name);
|
||||||
};
|
information.frequencies = {_frequency};
|
||||||
vector<Driver> drivers;
|
uint latencies[] = {64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 6144}; //factors of 6144
|
||||||
|
for(auto& latency : latencies) {
|
||||||
Driver driver;
|
if(latency < _active.minimumBufferSize) continue;
|
||||||
IASIO* device = nullptr;
|
if(latency > _active.maximumBufferSize) continue;
|
||||||
|
information.latencies.append(latency);
|
||||||
auto cap(const string& name) -> bool {
|
}
|
||||||
if(name == Audio::Handle) return true;
|
information.channels = {1, 2};
|
||||||
if(name == Audio::Synchronize) return true;
|
return information;
|
||||||
if(name == Audio::Frequency) return true;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto get(const string& name) -> any {
|
auto context() -> uintptr { return _context; }
|
||||||
if(name == Audio::Handle) return (uintptr)settings.handle;
|
auto blocking() -> bool { return _blocking; }
|
||||||
if(name == Audio::Synchronize) return settings.synchronize;
|
auto channels() -> uint { return _channels; }
|
||||||
if(name == Audio::Frequency) return settings.frequency;
|
auto frequency() -> uint { return _frequency; }
|
||||||
return {};
|
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 {
|
auto setBlocking(bool blocking) -> bool {
|
||||||
if(name == Audio::Handle && value.is<uintptr>()) {
|
if(_blocking == blocking) return true;
|
||||||
settings.handle = (HWND)value.get<uintptr>();
|
_blocking = blocking;
|
||||||
return true;
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
auto setChannels(uint channels) -> bool {
|
||||||
settings.synchronize = value.get<bool>();
|
if(_channels == channels) return true;
|
||||||
return true;
|
_channels = channels;
|
||||||
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == Audio::Frequency && value.is<uint>()) {
|
auto setLatency(uint latency) -> bool {
|
||||||
settings.frequency = value.get<uint>();
|
if(_latency == latency) return true;
|
||||||
return true;
|
_latency = latency;
|
||||||
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
auto output(const double samples[]) -> void {
|
||||||
|
if(!_ready) return;
|
||||||
|
if(_blocking) {
|
||||||
|
while(_queue.count >= _latency);
|
||||||
}
|
}
|
||||||
|
for(uint n : range(_channels)) {
|
||||||
auto sample(int16_t left, int16_t right) -> void {
|
_queue.samples[_queue.write][n] = samples[n];
|
||||||
|
}
|
||||||
|
_queue.write++;
|
||||||
|
_queue.count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clear() -> void {
|
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
|
//enumerate available ASIO drivers from the registry
|
||||||
for(auto candidate : registry::contents("HKLM\\SOFTWARE\\ASIO\\")) {
|
for(auto candidate : registry::contents("HKLM\\SOFTWARE\\ASIO\\")) {
|
||||||
if(auto classID = registry::read({"HKLM\\SOFTWARE\\ASIO\\", candidate, "CLSID"})) {
|
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
|
if(!_active.name) {
|
||||||
driver = drivers[0];
|
_active = _devices.left();
|
||||||
|
_device = _active.name;
|
||||||
|
}
|
||||||
|
|
||||||
CLSID classID;
|
CLSID classID;
|
||||||
if(CLSIDFromString((LPOLESTR)utf16_t(driver.classID), (LPCLSID)&classID) != 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**)&device) != 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
|
_frequency = _active.sampleRate;
|
||||||
char driverName[4096] = {0};
|
_latency = _latency < _active.minimumBufferSize ? _active.minimumBufferSize : _latency;
|
||||||
device->getDriverName(driverName);
|
_latency = _latency > _active.maximumBufferSize ? _active.maximumBufferSize : _latency;
|
||||||
print("Driver: ", driverName, "\n");
|
|
||||||
print("Version: ", device->getDriverVersion(), "\n");
|
|
||||||
print("---\n");
|
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
clear();
|
||||||
|
if(_asio->start() != ASE_OK) return false;
|
||||||
|
return _ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<uint16_t> 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<Device> _devices;
|
||||||
|
Device _active;
|
||||||
|
IASIO* _asio = nullptr;
|
||||||
|
ASIOBufferInfo _channel[8];
|
||||||
|
long _sampleFormat;
|
||||||
|
long _sampleSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
AudioASIO* AudioASIO::self = nullptr;
|
||||||
|
|
|
@ -52,14 +52,6 @@ enum : long {
|
||||||
ASIOSTLastEntry,
|
ASIOSTLastEntry,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ASIODriverInfo {
|
|
||||||
long asioVersion;
|
|
||||||
long driverVersion;
|
|
||||||
char name[32];
|
|
||||||
char errorMessage[124];
|
|
||||||
void* sysRef;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ASIOBufferInfo {
|
struct ASIOBufferInfo {
|
||||||
ASIOBool isInput;
|
ASIOBool isInput;
|
||||||
long channelNum;
|
long channelNum;
|
||||||
|
@ -123,9 +115,9 @@ struct ASIOTime {
|
||||||
|
|
||||||
struct ASIOCallbacks {
|
struct ASIOCallbacks {
|
||||||
auto (*bufferSwitch)(long doubleBufferIndex, ASIOBool directProcess) -> void;
|
auto (*bufferSwitch)(long doubleBufferIndex, ASIOBool directProcess) -> void;
|
||||||
auto (*sampleRateDidChange)(ASIOSampleRate sRate) -> void;
|
auto (*sampleRateDidChange)(ASIOSampleRate sampleRate) -> void;
|
||||||
auto (*asioMessage)(long selector, long value, void* message, double* opt) -> long;
|
auto (*asioMessage)(long selector, long value, void* message, double* optional) -> long;
|
||||||
auto (*bufferSwitchTimeInfo)(ASIOTime* params, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime*;
|
auto (*bufferSwitchTimeInfo)(ASIOTime* parameters, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime*;
|
||||||
};
|
};
|
||||||
enum : long {
|
enum : long {
|
||||||
kAsioSelectorSupported = 1,
|
kAsioSelectorSupported = 1,
|
||||||
|
@ -147,25 +139,25 @@ enum : long {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct IASIO : public IUnknown {
|
struct IASIO : public IUnknown {
|
||||||
virtual auto init(void* sysHandle) -> ASIOBool;
|
virtual auto init(void* systemHandle) -> ASIOBool;
|
||||||
virtual auto getDriverName(char* name) -> void;
|
virtual auto getDriverName(char* name) -> void;
|
||||||
virtual auto getDriverVersion() -> long;
|
virtual auto getDriverVersion() -> long;
|
||||||
virtual auto getErrorMessage(char* error) -> void;
|
virtual auto getErrorMessage(char* error) -> void;
|
||||||
virtual auto start() -> ASIOError;
|
virtual auto start() -> ASIOError;
|
||||||
virtual auto stop() -> 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 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 canSampleRate(ASIOSampleRate sampleRate) -> ASIOError = 0;
|
||||||
virtual auto getSampleRate(ASIOSampleRate* sampleRate) -> ASIOError = 0;
|
virtual auto getSampleRate(ASIOSampleRate* sampleRate) -> ASIOError = 0;
|
||||||
virtual auto setSampleRate(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 setClockSource(long reference) -> ASIOError = 0;
|
||||||
virtual auto getSamplePosition(ASIOSamples* sPos, ASIOTimeStamp* tStamp) -> ASIOError = 0;
|
virtual auto getSamplePosition(ASIOSamples* samplePosition, ASIOTimeStamp* timeStamp) -> ASIOError = 0;
|
||||||
virtual auto getChannelInfo(ASIOChannelInfo* info) -> ASIOError = 0;
|
virtual auto getChannelInfo(ASIOChannelInfo* information) -> ASIOError = 0;
|
||||||
virtual auto createBuffers(ASIOBufferInfo* bufferInfos, long numChannels, long bufferSize, ASIOCallbacks* callbacks) -> ASIOError = 0;
|
virtual auto createBuffers(ASIOBufferInfo* bufferInformation, long channels, long bufferSize, ASIOCallbacks* callbacks) -> ASIOError = 0;
|
||||||
virtual auto disposeBuffers() -> ASIOError = 0;
|
virtual auto disposeBuffers() -> ASIOError = 0;
|
||||||
virtual auto controlPanel() -> 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;
|
virtual auto outputReady() -> ASIOError = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,104 +14,108 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct AudioOSS : Audio {
|
struct AudioOSS : Audio {
|
||||||
~AudioOSS() { term(); }
|
AudioOSS() { initialize(); }
|
||||||
|
~AudioOSS() { terminate(); }
|
||||||
|
|
||||||
struct {
|
auto ready() -> bool { return _ready; }
|
||||||
int fd = -1;
|
|
||||||
int format = AFMT_S16_LE;
|
|
||||||
int channels = 2;
|
|
||||||
} device;
|
|
||||||
|
|
||||||
struct {
|
auto information() -> Information {
|
||||||
string device = "/dev/dsp";
|
Information information;
|
||||||
bool synchronize = true;
|
information.devices = {"/dev/dsp"};
|
||||||
uint frequency = 48000;
|
for(auto& device : directory::files("/dev/", "dsp?*")) information.devices.append(string{"/dev/", device});
|
||||||
uint latency = 60;
|
information.frequencies = {44100, 48000, 96000};
|
||||||
} settings;
|
information.latencies = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||||
|
information.channels = {1, 2};
|
||||||
auto cap(const string& name) -> bool {
|
return information;
|
||||||
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 get(const string& name) -> any {
|
auto device() -> string { return _device; }
|
||||||
if(name == Audio::Device) return settings.device;
|
auto blocking() -> bool { return _blocking; }
|
||||||
if(name == Audio::Synchronize) return settings.synchronize;
|
auto channels() -> uint { return _channels; }
|
||||||
if(name == Audio::Frequency) return settings.frequency;
|
auto frequency() -> uint { return _frequency; }
|
||||||
if(name == Audio::Latency) return settings.latency;
|
auto latency() -> uint { return _latency; }
|
||||||
return {};
|
|
||||||
|
auto setDevice(string device) -> bool {
|
||||||
|
if(_device == device) return true;
|
||||||
|
_device = device;
|
||||||
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto set(const string& name, const any& value) -> bool {
|
auto setBlocking(bool blocking) -> bool {
|
||||||
if(name == Audio::Device && value.is<string>()) {
|
if(_blocking == blocking) return true;
|
||||||
settings.device = value.get<string>();
|
_blocking = blocking;
|
||||||
if(!settings.device) settings.device = "/dev/dsp";
|
updateBlocking();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
auto setChannels(uint channels) -> bool {
|
||||||
settings.synchronize = value.get<bool>();
|
if(_channels == channels) return true;
|
||||||
updateSynchronization();
|
_channels = channels;
|
||||||
return true;
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == Audio::Frequency && value.is<uint>()) {
|
auto setFrequency(uint frequency) -> bool {
|
||||||
settings.frequency = value.get<uint>();
|
if(_frequency == frequency) return true;
|
||||||
if(device.fd >= 0) init();
|
_frequency = frequency;
|
||||||
return true;
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == Audio::Latency && value.is<uint>()) {
|
auto setLatency(uint latency) -> bool {
|
||||||
settings.latency = value.get<uint>();
|
if(_latency == latency) return true;
|
||||||
if(device.fd >= 0) init();
|
_latency = latency;
|
||||||
return true;
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
auto output(const double samples[]) -> void {
|
||||||
}
|
if(!_ready) return;
|
||||||
|
for(auto n : range(_channels)) {
|
||||||
auto sample(int16_t left, int16_t right) -> void {
|
int16_t sample = samples[n] * 32768.0;
|
||||||
uint32_t sample = (uint16_t)left << 0 | (uint16_t)right << 16;
|
auto unused = write(_fd, &sample, 2);
|
||||||
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();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto term() -> void {
|
|
||||||
if(device.fd >= 0) {
|
|
||||||
close(device.fd);
|
|
||||||
device.fd = -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto updateSynchronization() -> void {
|
auto initialize() -> bool {
|
||||||
if(device.fd < 0) return;
|
terminate();
|
||||||
auto flags = fcntl(device.fd, F_GETFL);
|
|
||||||
if(flags < 0) return;
|
_fd = open(_device, O_WRONLY, O_NONBLOCK);
|
||||||
settings.synchronize ? flags &=~ O_NONBLOCK : flags |= O_NONBLOCK;
|
if(_fd < 0) return false;
|
||||||
fcntl(device.fd, F_SETFL, flags);
|
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -86,7 +86,8 @@ struct InputJoypadDirectInput {
|
||||||
return false;
|
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->handle = handle;
|
||||||
this->context = context;
|
this->context = context;
|
||||||
this->xinputAvailable = xinputAvailable;
|
this->xinputAvailable = xinputAvailable;
|
||||||
|
@ -94,7 +95,7 @@ struct InputJoypadDirectInput {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
for(auto& jp : joypads) {
|
for(auto& jp : joypads) {
|
||||||
jp.device->Unacquire();
|
jp.device->Unacquire();
|
||||||
if(jp.effect) jp.effect->Release();
|
if(jp.effect) jp.effect->Release();
|
||||||
|
|
|
@ -42,7 +42,7 @@ struct InputJoypadSDL {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
auto initialize() -> bool {
|
||||||
SDL_InitSubSystem(SDL_INIT_JOYSTICK);
|
SDL_InitSubSystem(SDL_INIT_JOYSTICK);
|
||||||
SDL_JoystickEventState(SDL_IGNORE);
|
SDL_JoystickEventState(SDL_IGNORE);
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ struct InputJoypadSDL {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
for(auto& jp : joypads) {
|
for(auto& jp : joypads) {
|
||||||
SDL_JoystickClose(jp.handle);
|
SDL_JoystickClose(jp.handle);
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ struct InputJoypadXInput {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
auto initialize() -> bool {
|
||||||
if(!libxinput) libxinput = LoadLibraryA("xinput1_3.dll");
|
if(!libxinput) libxinput = LoadLibraryA("xinput1_3.dll");
|
||||||
if(!libxinput) return false;
|
if(!libxinput) return false;
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ struct InputJoypadXInput {
|
||||||
XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetStateEx);
|
XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetStateEx);
|
||||||
XInputSetState = (pXInputSetState)GetProcAddress(libxinput, oXInputSetState);
|
XInputSetState = (pXInputSetState)GetProcAddress(libxinput, oXInputSetState);
|
||||||
if(!XInputGetStateEx) XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetState);
|
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
|
//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
|
//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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
if(!libxinput) return;
|
if(!libxinput) return;
|
||||||
|
|
||||||
FreeLibrary(libxinput);
|
FreeLibrary(libxinput);
|
||||||
|
|
|
@ -39,7 +39,7 @@ struct InputKeyboardRawInput {
|
||||||
devices.append(kb.hid);
|
devices.append(kb.hid);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
auto initialize() -> bool {
|
||||||
rawinput.updateKeyboard = {&InputKeyboardRawInput::update, this};
|
rawinput.updateKeyboard = {&InputKeyboardRawInput::update, this};
|
||||||
|
|
||||||
//Pause sends 0x001d,4 + 0x0045,0; NumLock sends only 0x0045,0
|
//Pause sends 0x001d,4 + 0x0045,0; NumLock sends only 0x0045,0
|
||||||
|
@ -170,7 +170,7 @@ struct InputKeyboardRawInput {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
rawinput.updateKeyboard.reset();
|
rawinput.updateKeyboard.reset();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,7 +36,7 @@ struct InputKeyboardXlib {
|
||||||
devices.append(hid);
|
devices.append(hid);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
auto initialize() -> bool {
|
||||||
display = XOpenDisplay(0);
|
display = XOpenDisplay(0);
|
||||||
|
|
||||||
keys.append({"Escape", XK_Escape});
|
keys.append({"Escape", XK_Escape});
|
||||||
|
@ -163,7 +163,7 @@ struct InputKeyboardXlib {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
if(display) {
|
if(display) {
|
||||||
XCloseDisplay(display);
|
XCloseDisplay(display);
|
||||||
display = nullptr;
|
display = nullptr;
|
||||||
|
|
|
@ -5,7 +5,7 @@ struct InputMouseRawInput {
|
||||||
Input& input;
|
Input& input;
|
||||||
InputMouseRawInput(Input& input) : input(input) {}
|
InputMouseRawInput(Input& input) : input(input) {}
|
||||||
|
|
||||||
uintptr_t handle = 0;
|
uintptr handle = 0;
|
||||||
bool mouseAcquired = false;
|
bool mouseAcquired = false;
|
||||||
|
|
||||||
struct Mouse {
|
struct Mouse {
|
||||||
|
@ -17,6 +17,17 @@ struct InputMouseRawInput {
|
||||||
bool buttons[5] = {0};
|
bool buttons[5] = {0};
|
||||||
} ms;
|
} 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 {
|
auto acquire() -> bool {
|
||||||
if(!mouseAcquired) {
|
if(!mouseAcquired) {
|
||||||
mouseAcquired = true;
|
mouseAcquired = true;
|
||||||
|
@ -35,17 +46,6 @@ struct InputMouseRawInput {
|
||||||
return true;
|
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 {
|
auto update(RAWINPUT* input) -> void {
|
||||||
if((input->data.mouse.usFlags & 1) == MOUSE_MOVE_RELATIVE) {
|
if((input->data.mouse.usFlags & 1) == MOUSE_MOVE_RELATIVE) {
|
||||||
ms.relativeX += input->data.mouse.lLastX;
|
ms.relativeX += input->data.mouse.lLastX;
|
||||||
|
@ -95,7 +95,8 @@ struct InputMouseRawInput {
|
||||||
devices.append(ms.hid);
|
devices.append(ms.hid);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init(uintptr_t handle) -> bool {
|
auto initialize(uintptr handle) -> bool {
|
||||||
|
if(!handle) return false;
|
||||||
this->handle = handle;
|
this->handle = handle;
|
||||||
|
|
||||||
ms.hid->setID(2);
|
ms.hid->setID(2);
|
||||||
|
@ -114,7 +115,7 @@ struct InputMouseRawInput {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
rawinput.updateMouse.reset();
|
rawinput.updateMouse.reset();
|
||||||
release();
|
release();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@ struct InputMouseXlib {
|
||||||
|
|
||||||
shared_pointer<HID::Mouse> hid{new HID::Mouse};
|
shared_pointer<HID::Mouse> hid{new HID::Mouse};
|
||||||
|
|
||||||
uintptr_t handle = 0;
|
uintptr handle = 0;
|
||||||
|
|
||||||
Display* display = nullptr;
|
Display* display = nullptr;
|
||||||
Window rootWindow;
|
Window rootWindow = 0;
|
||||||
Cursor invisibleCursor;
|
Cursor invisibleCursor = 0;
|
||||||
unsigned screenWidth = 0;
|
unsigned screenWidth = 0;
|
||||||
unsigned screenHeight = 0;
|
unsigned screenHeight = 0;
|
||||||
|
|
||||||
|
@ -24,6 +24,10 @@ struct InputMouseXlib {
|
||||||
unsigned relativeY = 0;
|
unsigned relativeY = 0;
|
||||||
} ms;
|
} ms;
|
||||||
|
|
||||||
|
auto acquired() -> bool {
|
||||||
|
return ms.acquired;
|
||||||
|
}
|
||||||
|
|
||||||
auto acquire() -> bool {
|
auto acquire() -> bool {
|
||||||
if(acquired()) return true;
|
if(acquired()) return true;
|
||||||
|
|
||||||
|
@ -53,10 +57,6 @@ struct InputMouseXlib {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto acquired() -> bool {
|
|
||||||
return ms.acquired;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto assign(unsigned groupID, unsigned inputID, int16_t value) -> void {
|
auto assign(unsigned groupID, unsigned inputID, int16_t value) -> void {
|
||||||
auto& group = hid->group(groupID);
|
auto& group = hid->group(groupID);
|
||||||
if(group.input(inputID).value() == value) return;
|
if(group.input(inputID).value() == value) return;
|
||||||
|
@ -103,7 +103,10 @@ struct InputMouseXlib {
|
||||||
devices.append(hid);
|
devices.append(hid);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init(uintptr_t handle) -> bool {
|
auto initialize(uintptr handle) -> bool {
|
||||||
|
terminate();
|
||||||
|
if(!handle) return false;
|
||||||
|
|
||||||
this->handle = handle;
|
this->handle = handle;
|
||||||
display = XOpenDisplay(0);
|
display = XOpenDisplay(0);
|
||||||
rootWindow = DefaultRootWindow(display);
|
rootWindow = DefaultRootWindow(display);
|
||||||
|
@ -143,10 +146,16 @@ struct InputMouseXlib {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
release();
|
release();
|
||||||
|
if(invisibleCursor) {
|
||||||
XFreeCursor(display, invisibleCursor);
|
XFreeCursor(display, invisibleCursor);
|
||||||
|
invisibleCursor = 0;
|
||||||
|
}
|
||||||
|
if(display) {
|
||||||
XCloseDisplay(display);
|
XCloseDisplay(display);
|
||||||
|
display = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,55 +7,36 @@
|
||||||
#include "joypad/sdl.cpp"
|
#include "joypad/sdl.cpp"
|
||||||
|
|
||||||
struct InputSDL : Input {
|
struct InputSDL : Input {
|
||||||
InputKeyboardXlib xlibKeyboard;
|
InputSDL() : _keyboard(*this), _mouse(*this), _joypad(*this) { initialize(); }
|
||||||
InputMouseXlib xlibMouse;
|
~InputSDL() { terminate(); }
|
||||||
InputJoypadSDL sdl;
|
|
||||||
InputSDL() : xlibKeyboard(*this), xlibMouse(*this), sdl(*this) {}
|
|
||||||
~InputSDL() { term(); }
|
|
||||||
|
|
||||||
struct Settings {
|
auto ready() -> bool { return _ready; }
|
||||||
uintptr_t handle = 0;
|
|
||||||
} settings;
|
|
||||||
|
|
||||||
auto cap(const string& name) -> bool {
|
auto context() -> uintptr { return _context; }
|
||||||
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 get(const string& name) -> any {
|
auto setContext(uintptr context) -> bool {
|
||||||
if(name == Input::Handle) return (uintptr_t)settings.handle;
|
if(_context == context) return true;
|
||||||
return {};
|
_context = context;
|
||||||
}
|
return initialize();
|
||||||
|
|
||||||
auto set(const string& name, const any& value) -> bool {
|
|
||||||
if(name == Input::Handle && value.is<uintptr_t>()) {
|
|
||||||
settings.handle = value.get<uintptr_t>();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto acquire() -> bool {
|
|
||||||
return xlibMouse.acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto release() -> bool {
|
|
||||||
return xlibMouse.release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto acquired() -> bool {
|
auto acquired() -> bool {
|
||||||
return xlibMouse.acquired();
|
return _mouse.acquired();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto acquire() -> bool {
|
||||||
|
return _mouse.acquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto release() -> bool {
|
||||||
|
return _mouse.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto poll() -> vector<shared_pointer<HID::Device>> {
|
auto poll() -> vector<shared_pointer<HID::Device>> {
|
||||||
vector<shared_pointer<HID::Device>> devices;
|
vector<shared_pointer<HID::Device>> devices;
|
||||||
xlibKeyboard.poll(devices);
|
_keyboard.poll(devices);
|
||||||
xlibMouse.poll(devices);
|
_mouse.poll(devices);
|
||||||
sdl.poll(devices);
|
_joypad.poll(devices);
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,16 +44,26 @@ struct InputSDL : Input {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
private:
|
||||||
if(!xlibKeyboard.init()) return false;
|
auto initialize() -> bool {
|
||||||
if(!xlibMouse.init(settings.handle)) return false;
|
terminate();
|
||||||
if(!sdl.init()) return false;
|
if(!_keyboard.initialize()) return false;
|
||||||
return true;
|
if(!_mouse.initialize(_context)) return false;
|
||||||
|
if(!_joypad.initialize()) return false;
|
||||||
|
return _ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
xlibKeyboard.term();
|
_ready = false;
|
||||||
xlibMouse.term();
|
_keyboard.terminate();
|
||||||
sdl.term();
|
_mouse.terminate();
|
||||||
|
_joypad.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _ready = false;
|
||||||
|
uintptr _context = 0;
|
||||||
|
|
||||||
|
InputKeyboardXlib _keyboard;
|
||||||
|
InputMouseXlib _mouse;
|
||||||
|
InputJoypadSDL _joypad;
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,97 +9,93 @@
|
||||||
#include "joypad/directinput.cpp"
|
#include "joypad/directinput.cpp"
|
||||||
|
|
||||||
struct InputWindows : Input {
|
struct InputWindows : Input {
|
||||||
InputKeyboardRawInput rawinputKeyboard;
|
InputWindows() : _keyboard(*this), _mouse(*this), _joypadXInput(*this), _joypadDirectInput(*this) { initialize(); }
|
||||||
InputMouseRawInput rawinputMouse;
|
~InputWindows() { terminate(); }
|
||||||
InputJoypadXInput xinput;
|
|
||||||
InputJoypadDirectInput directinput;
|
|
||||||
InputWindows() : rawinputKeyboard(*this), rawinputMouse(*this), xinput(*this), directinput(*this) {}
|
|
||||||
~InputWindows() { term(); }
|
|
||||||
|
|
||||||
LPDIRECTINPUT8 directinputContext = nullptr;
|
auto ready() -> bool { return _ready; }
|
||||||
|
|
||||||
struct Settings {
|
auto context() -> uintptr { return _context; }
|
||||||
uintptr_t handle = 0;
|
|
||||||
} settings;
|
|
||||||
|
|
||||||
auto cap(const string& name) -> bool {
|
auto setContext(uintptr context) -> bool {
|
||||||
if(name == Input::Handle) return true;
|
if(_context == context) return true;
|
||||||
if(name == Input::KeyboardSupport) return true;
|
_context = context;
|
||||||
if(name == Input::MouseSupport) return true;
|
return initialize();
|
||||||
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<uintptr_t>()) {
|
|
||||||
settings.handle = value.get<uintptr_t>();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto acquire() -> bool {
|
|
||||||
return rawinputMouse.acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto release() -> bool {
|
|
||||||
return rawinputMouse.release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto acquired() -> bool {
|
auto acquired() -> bool {
|
||||||
return rawinputMouse.acquired();
|
return _mouse.acquired();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto acquire() -> bool {
|
||||||
|
return _mouse.acquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto release() -> bool {
|
||||||
|
return _mouse.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto poll() -> vector<shared_pointer<HID::Device>> {
|
auto poll() -> vector<shared_pointer<HID::Device>> {
|
||||||
vector<shared_pointer<HID::Device>> devices;
|
vector<shared_pointer<HID::Device>> devices;
|
||||||
rawinputKeyboard.poll(devices);
|
_keyboard.poll(devices);
|
||||||
rawinputMouse.poll(devices);
|
_mouse.poll(devices);
|
||||||
xinput.poll(devices);
|
_joypadXInput.poll(devices);
|
||||||
directinput.poll(devices);
|
_joypadDirectInput.poll(devices);
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto rumble(uint64_t id, bool enable) -> bool {
|
auto rumble(uint64_t id, bool enable) -> bool {
|
||||||
if(xinput.rumble(id, enable)) return true;
|
if(_joypadXInput.rumble(id, enable)) return true;
|
||||||
if(directinput.rumble(id, enable)) return true;
|
if(_joypadDirectInput.rumble(id, enable)) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
private:
|
||||||
if(rawinput.initialized == false) {
|
auto initialize() -> bool {
|
||||||
|
terminate();
|
||||||
|
if(!_context) return false;
|
||||||
|
|
||||||
|
if(!rawinput.initialized) {
|
||||||
rawinput.initialized = true;
|
rawinput.initialized = true;
|
||||||
rawinput.mutex = CreateMutex(NULL, FALSE, NULL);
|
rawinput.mutex = CreateMutex(nullptr, false, nullptr);
|
||||||
CreateThread(NULL, 0, RawInputThreadProc, 0, 0, NULL);
|
CreateThread(nullptr, 0, RawInputThreadProc, 0, 0, nullptr);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
Sleep(1);
|
Sleep(1);
|
||||||
WaitForSingleObject(rawinput.mutex, INFINITE);
|
WaitForSingleObject(rawinput.mutex, INFINITE);
|
||||||
ReleaseMutex(rawinput.mutex);
|
ReleaseMutex(rawinput.mutex);
|
||||||
} while(rawinput.ready == false);
|
} while(!rawinput.ready);
|
||||||
}
|
}
|
||||||
|
|
||||||
DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&directinputContext, 0);
|
DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&_directInputContext, 0);
|
||||||
if(directinputContext == nullptr) return false;
|
if(!_directInputContext) return false;
|
||||||
|
|
||||||
if(rawinputKeyboard.init() == false) return false;
|
if(!_keyboard.initialize()) return false;
|
||||||
if(rawinputMouse.init(settings.handle) == false) return false;
|
if(!_mouse.initialize(_context)) return false;
|
||||||
bool xinputAvailable = xinput.init();
|
bool xinputAvailable = _joypadXInput.initialize();
|
||||||
if(directinput.init(settings.handle, directinputContext, xinputAvailable) == false) return false;
|
if(!_joypadDirectInput.initialize(_context, _directInputContext, xinputAvailable)) return false;
|
||||||
return true;
|
return _ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
rawinputKeyboard.term();
|
_ready = false;
|
||||||
rawinputMouse.term();
|
|
||||||
xinput.term();
|
|
||||||
directinput.term();
|
|
||||||
|
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,51 +8,25 @@
|
||||||
#include "mouse/xlib.cpp"
|
#include "mouse/xlib.cpp"
|
||||||
|
|
||||||
struct InputXlib : Input {
|
struct InputXlib : Input {
|
||||||
InputKeyboardXlib xlibKeyboard;
|
InputXlib() : _keyboard(*this), _mouse(*this) { initialize(); }
|
||||||
InputMouseXlib xlibMouse;
|
~InputXlib() { terminate(); }
|
||||||
InputXlib() : xlibKeyboard(*this), xlibMouse(*this) {}
|
|
||||||
~InputXlib() { term(); }
|
|
||||||
|
|
||||||
struct Settings {
|
auto acquired() -> bool {
|
||||||
uintptr_t handle = 0;
|
return _mouse.acquired();
|
||||||
} 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<uintptr_t>()) {
|
|
||||||
settings.handle = value.get<uintptr_t>();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto acquire() -> bool {
|
auto acquire() -> bool {
|
||||||
return xlibMouse.acquire();
|
return _mouse.acquire();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto release() -> bool {
|
auto release() -> bool {
|
||||||
return xlibMouse.release();
|
return _mouse.release();
|
||||||
}
|
|
||||||
|
|
||||||
auto acquired() -> bool {
|
|
||||||
return xlibMouse.acquired();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto poll() -> vector<shared_pointer<HID::Device>> {
|
auto poll() -> vector<shared_pointer<HID::Device>> {
|
||||||
vector<shared_pointer<HID::Device>> devices;
|
vector<shared_pointer<HID::Device>> devices;
|
||||||
xlibKeyboard.poll(devices);
|
_keyboard.poll(devices);
|
||||||
xlibMouse.poll(devices);
|
_mouse.poll(devices);
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,14 +34,23 @@ struct InputXlib : Input {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
private:
|
||||||
if(!xlibKeyboard.init()) return false;
|
auto initialize() -> bool {
|
||||||
if(!xlibMouse.init(settings.handle)) return false;
|
terminate();
|
||||||
return true;
|
if(!_keyboard.initialize()) return false;
|
||||||
|
if(!_mouse.initialize(_context)) return false;
|
||||||
|
return _ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
xlibKeyboard.term();
|
_ready = false;
|
||||||
xlibMouse.term();
|
_keyboard.terminate();
|
||||||
|
_mouse.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _ready = false;
|
||||||
|
uintptr _context = 0;
|
||||||
|
|
||||||
|
InputKeyboardXlib _keyboard;
|
||||||
|
InputMouseXlib _mouse;
|
||||||
};
|
};
|
||||||
|
|
|
@ -72,16 +72,6 @@ using namespace ruby;
|
||||||
|
|
||||||
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* {
|
auto Video::create(const string& driver) -> Video* {
|
||||||
if(!driver) return create(optimalDriver());
|
if(!driver) return create(optimalDriver());
|
||||||
|
|
||||||
|
@ -272,13 +262,6 @@ auto Video::availableDrivers() -> string_vector {
|
||||||
|
|
||||||
namespace ruby {
|
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* {
|
auto Audio::create(const string& driver) -> Audio* {
|
||||||
if(!driver) return create(optimalDriver());
|
if(!driver) return create(optimalDriver());
|
||||||
|
|
||||||
|
@ -453,12 +436,6 @@ auto Audio::availableDrivers() -> string_vector {
|
||||||
|
|
||||||
namespace ruby {
|
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* {
|
auto Input::create(const string& driver) -> Input* {
|
||||||
if(!driver) return create(optimalDriver());
|
if(!driver) return create(optimalDriver());
|
||||||
|
|
||||||
|
|
102
ruby/ruby.hpp
102
ruby/ruby.hpp
|
@ -14,89 +14,101 @@
|
||||||
namespace ruby {
|
namespace ruby {
|
||||||
|
|
||||||
struct Video {
|
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 create(const nall::string& driver = "") -> Video*;
|
||||||
static auto optimalDriver() -> nall::string;
|
static auto optimalDriver() -> nall::string;
|
||||||
static auto safestDriver() -> nall::string;
|
static auto safestDriver() -> nall::string;
|
||||||
static auto availableDrivers() -> nall::string_vector;
|
static auto availableDrivers() -> nall::string_vector;
|
||||||
|
|
||||||
|
struct Information {
|
||||||
|
};
|
||||||
|
|
||||||
virtual ~Video() = default;
|
virtual ~Video() = default;
|
||||||
|
|
||||||
virtual auto cap(const nall::string& name) -> bool { return false; }
|
virtual auto ready() -> bool { return true; }
|
||||||
virtual auto get(const nall::string& name) -> nall::any { return false; }
|
virtual auto information() -> Information { return {}; }
|
||||||
virtual auto set(const nall::string& name, const nall::any& value) -> bool { return false; }
|
|
||||||
|
|
||||||
|
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 lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool { return false; }
|
||||||
virtual auto unlock() -> void {}
|
virtual auto unlock() -> void {}
|
||||||
virtual auto clear() -> void {}
|
virtual auto output() -> void {}
|
||||||
virtual auto refresh() -> void {}
|
|
||||||
|
|
||||||
virtual auto init() -> bool { return true; }
|
|
||||||
virtual auto term() -> void {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Audio {
|
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 create(const nall::string& driver = "") -> Audio*;
|
||||||
static auto optimalDriver() -> nall::string;
|
static auto optimalDriver() -> nall::string;
|
||||||
static auto safestDriver() -> nall::string;
|
static auto safestDriver() -> nall::string;
|
||||||
static auto availableDrivers() -> nall::string_vector;
|
static auto availableDrivers() -> nall::string_vector;
|
||||||
|
|
||||||
|
struct Information {
|
||||||
|
nall::vector<nall::string> devices;
|
||||||
|
nall::vector<uint> frequencies;
|
||||||
|
nall::vector<uint> latencies;
|
||||||
|
nall::vector<uint> channels;
|
||||||
|
};
|
||||||
|
|
||||||
virtual ~Audio() = default;
|
virtual ~Audio() = default;
|
||||||
|
|
||||||
virtual auto cap(const nall::string& name) -> bool { return false; }
|
virtual auto ready() -> bool { return true; }
|
||||||
virtual auto get(const nall::string& name) -> nall::any { return false; }
|
virtual auto information() -> Information { return {{"None"}, {48000}, {0}, {2}}; }
|
||||||
virtual auto set(const nall::string& name, const nall::any& value) -> bool { return false; }
|
|
||||||
|
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 clear() -> void {}
|
||||||
|
virtual auto output(const double samples[]) -> void {}
|
||||||
virtual auto init() -> bool { return true; }
|
|
||||||
virtual auto term() -> void {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Input {
|
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 create(const nall::string& driver = "") -> Input*;
|
||||||
static auto optimalDriver() -> nall::string;
|
static auto optimalDriver() -> nall::string;
|
||||||
static auto safestDriver() -> nall::string;
|
static auto safestDriver() -> nall::string;
|
||||||
static auto availableDrivers() -> nall::string_vector;
|
static auto availableDrivers() -> nall::string_vector;
|
||||||
|
|
||||||
|
struct Information {
|
||||||
|
};
|
||||||
|
|
||||||
virtual ~Input() = default;
|
virtual ~Input() = default;
|
||||||
|
|
||||||
virtual auto cap(const nall::string& name) -> bool { return false; }
|
virtual auto ready() -> bool { return true; }
|
||||||
virtual auto get(const nall::string& name) -> nall::any { return false; }
|
virtual auto information() -> Information { return {}; }
|
||||||
virtual auto set(const nall::string& name, const nall::any& value) -> bool { return false; }
|
|
||||||
|
|
||||||
|
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 acquire() -> bool { return false; }
|
||||||
virtual auto release() -> bool { return false; }
|
virtual auto release() -> bool { return false; }
|
||||||
virtual auto acquired() -> bool { return false; }
|
|
||||||
virtual auto poll() -> nall::vector<nall::shared_pointer<nall::HID::Device>> { return {}; }
|
virtual auto poll() -> nall::vector<nall::shared_pointer<nall::HID::Device>> { return {}; }
|
||||||
virtual auto rumble(uint64_t id, bool enable) -> bool { return false; }
|
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<void (nall::shared_pointer<nall::HID::Device>, uint, uint, int16_t, int16_t)>& callback) { _onChange = callback; }
|
auto onChange(const nall::function<void (nall::shared_pointer<nall::HID::Device>, uint, uint, int16_t, int16_t)>& callback) { _onChange = callback; }
|
||||||
auto doChange(nall::shared_pointer<nall::HID::Device> device, uint group, uint input, int16_t oldValue, int16_t newValue) -> void {
|
auto doChange(nall::shared_pointer<nall::HID::Device> device, uint group, uint input, int16_t oldValue, int16_t newValue) -> void {
|
||||||
if(_onChange) _onChange(device, group, input, oldValue, newValue);
|
if(_onChange) _onChange(device, group, input, oldValue, newValue);
|
||||||
|
|
|
@ -8,183 +8,182 @@ static LRESULT CALLBACK VideoDirect3D_WindowProcedure(HWND hwnd, UINT msg, WPARA
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VideoDirect3D : Video {
|
struct VideoDirect3D : Video {
|
||||||
VideoDirect3D() {
|
VideoDirect3D() { initialize(); }
|
||||||
POINT point = {0, 0};
|
~VideoDirect3D() { terminate(); }
|
||||||
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;
|
|
||||||
|
|
||||||
WNDCLASS windowClass = {};
|
auto ready() -> bool { return _ready; }
|
||||||
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);
|
|
||||||
|
|
||||||
settings.exclusiveHandle = CreateWindowEx(WS_EX_TOPMOST, L"VideoDirect3D_Window", L"", WS_POPUP,
|
auto exclusive() -> bool { return _exclusive; }
|
||||||
information.rcMonitor.left, information.rcMonitor.top, monitorWidth, monitorHeight,
|
auto context() -> uintptr { return _context; }
|
||||||
nullptr, nullptr, GetModuleHandle(0), nullptr);
|
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() {
|
auto setContext(uintptr context) -> bool {
|
||||||
term();
|
if(_context == context) return true;
|
||||||
DestroyWindow(settings.exclusiveHandle);
|
_context = context;
|
||||||
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
LPDIRECT3D9 context = nullptr;
|
auto setBlocking(bool blocking) -> bool {
|
||||||
LPDIRECT3DDEVICE9 device = nullptr;
|
_blocking = blocking;
|
||||||
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 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 set(const string& name, const any& value) -> bool {
|
|
||||||
if(name == Video::Exclusive && value.is<bool>()) {
|
|
||||||
settings.exclusive = value.get<bool>();
|
|
||||||
if(context) init();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == Video::Handle && value.is<uintptr>()) {
|
auto setSmooth(bool smooth) -> bool {
|
||||||
settings.handle = (HWND)value.get<uintptr>();
|
_smooth = smooth;
|
||||||
|
if(_ready) updateFilter();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(name == Video::Synchronize && value.is<bool>()) {
|
auto clear() -> void {
|
||||||
settings.synchronize = value.get<bool>();
|
if(_lost && !recover()) return;
|
||||||
return true;
|
|
||||||
|
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::Filter && value.is<uint>()) {
|
//clear primary display and all backbuffers
|
||||||
settings.filter = value.get<uint>();
|
for(uint n : range(3)) {
|
||||||
if(context) updateFilter();
|
_device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0x00, 0x00, 0x00), 1.0f, 0);
|
||||||
return true;
|
_device->Present(0, 0, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
auto recover() -> bool {
|
||||||
if(!device) return false;
|
if(!_device) return false;
|
||||||
|
|
||||||
if(lost) {
|
if(_lost) {
|
||||||
if(vertexBuffer) { vertexBuffer->Release(); vertexBuffer = nullptr; }
|
if(_vertexBuffer) { _vertexBuffer->Release(); _vertexBuffer = nullptr; }
|
||||||
if(surface) { surface->Release(); surface = nullptr; }
|
if(_surface) { _surface->Release(); _surface = nullptr; }
|
||||||
if(texture) { texture->Release(); texture = nullptr; }
|
if(_texture) { _texture->Release(); _texture = nullptr; }
|
||||||
if(device->Reset(&presentation) != D3D_OK) return false;
|
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_COLOROP, D3DTOP_SELECTARG1);
|
||||||
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
_device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||||
device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
_device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||||
|
|
||||||
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
_device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||||
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
_device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||||
device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
|
_device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
|
||||||
|
|
||||||
device->SetRenderState(D3DRS_LIGHTING, false);
|
_device->SetRenderState(D3DRS_LIGHTING, false);
|
||||||
device->SetRenderState(D3DRS_ZENABLE, false);
|
_device->SetRenderState(D3DRS_ZENABLE, false);
|
||||||
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||||
|
|
||||||
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||||
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||||
device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
|
_device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
|
||||||
|
|
||||||
device->SetVertexShader(nullptr);
|
_device->SetVertexShader(nullptr);
|
||||||
device->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
|
_device->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
|
||||||
|
|
||||||
device->CreateVertexBuffer(sizeof(Vertex) * 4, flags.vertexUsage, D3DFVF_XYZRHW | D3DFVF_TEX1,
|
_device->CreateVertexBuffer(sizeof(Vertex) * 4, _vertexUsage, D3DFVF_XYZRHW | D3DFVF_TEX1,
|
||||||
(D3DPOOL)flags.vertexPool, &vertexBuffer, nullptr);
|
(D3DPOOL)_vertexPool, &_vertexBuffer, nullptr);
|
||||||
textureWidth = 0;
|
_textureWidth = 0;
|
||||||
textureHeight = 0;
|
_textureHeight = 0;
|
||||||
resize(settings.width = 256, settings.height = 256);
|
resize(_inputWidth = 256, _inputHeight = 256);
|
||||||
updateFilter();
|
updateFilter();
|
||||||
clear();
|
clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto resize(uint width, uint height) -> void {
|
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));
|
_textureWidth = bit::round(max(width, _textureWidth));
|
||||||
textureHeight = bit::round(max(height, textureHeight));
|
_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();
|
if(_texture) _texture->Release();
|
||||||
device->CreateTexture(textureWidth, textureHeight, 1, flags.textureUsage, D3DFMT_X8R8G8B8,
|
_device->CreateTexture(_textureWidth, _textureHeight, 1, _textureUsage, D3DFMT_X8R8G8B8,
|
||||||
(D3DPOOL)flags.texturePool, &texture, nullptr);
|
(D3DPOOL)_texturePool, &_texture, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto updateFilter() -> void {
|
auto updateFilter() -> void {
|
||||||
if(!device) return;
|
if(!_device) return;
|
||||||
if(lost && !recover()) return;
|
if(_lost && !recover()) return;
|
||||||
|
|
||||||
flags.filter = settings.filter == Video::FilterNearest ? D3DTEXF_POINT : D3DTEXF_LINEAR;
|
auto filter = !_smooth ? D3DTEXF_POINT : D3DTEXF_LINEAR;
|
||||||
device->SetSamplerState(0, D3DSAMP_MINFILTER, flags.filter);
|
_device->SetSamplerState(0, D3DSAMP_MINFILTER, filter);
|
||||||
device->SetSamplerState(0, D3DSAMP_MAGFILTER, flags.filter);
|
_device->SetSamplerState(0, D3DSAMP_MAGFILTER, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
//(x,y) screen coordinates, in pixels
|
//(x,y) screen coordinates, in pixels
|
||||||
|
@ -212,164 +211,147 @@ struct VideoDirect3D : Video {
|
||||||
vertex[2].v = vertex[3].v = (double)(py + h) / rh;
|
vertex[2].v = vertex[3].v = (double)(py + h) / rh;
|
||||||
|
|
||||||
LPDIRECT3DVERTEXBUFFER9* vertexPointer = nullptr;
|
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);
|
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 {
|
auto initialize() -> bool {
|
||||||
if(lost && !recover()) return;
|
terminate();
|
||||||
|
if(!_context) return false;
|
||||||
|
|
||||||
D3DSURFACE_DESC surfaceDescription;
|
POINT point = {0, 0};
|
||||||
texture->GetLevelDesc(0, &surfaceDescription);
|
HMONITOR monitor = MonitorFromPoint(point, MONITOR_DEFAULTTOPRIMARY);
|
||||||
texture->GetSurfaceLevel(0, &surface);
|
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) {
|
WNDCLASS windowClass = {};
|
||||||
device->ColorFill(surface, 0, D3DCOLOR_XRGB(0x00, 0x00, 0x00));
|
windowClass.cbClsExtra = 0;
|
||||||
surface->Release();
|
windowClass.cbWndExtra = 0;
|
||||||
surface = nullptr;
|
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
|
_exclusiveContext = (uintptr)CreateWindowEx(WS_EX_TOPMOST, L"VideoDirect3D_Window", L"", WS_POPUP,
|
||||||
for(uint n : range(3)) {
|
information.rcMonitor.left, information.rcMonitor.top, _monitorWidth, _monitorHeight,
|
||||||
device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0x00, 0x00, 0x00), 1.0f, 0);
|
nullptr, nullptr, GetModuleHandle(0), nullptr);
|
||||||
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;
|
|
||||||
|
|
||||||
RECT rectangle;
|
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.
|
_instance = Direct3DCreate9(D3D_SDK_VERSION);
|
||||||
//failure to do so causes scaling issues on some video drivers.
|
if(!_instance) return false;
|
||||||
if(windowWidth != rectangle.right || windowHeight != rectangle.bottom) init();
|
|
||||||
|
|
||||||
device->BeginScene();
|
memory::fill(&_presentation, sizeof(_presentation));
|
||||||
uint x = 0, y = 0;
|
_presentation.Flags = D3DPRESENTFLAG_VIDEO;
|
||||||
if(settings.exclusive) {
|
_presentation.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||||
//center output in exclusive mode fullscreen window
|
_presentation.BackBufferCount = 1;
|
||||||
x = (monitorWidth - windowWidth) / 2;
|
_presentation.MultiSampleType = D3DMULTISAMPLE_NONE;
|
||||||
y = (monitorHeight - windowHeight) / 2;
|
_presentation.MultiSampleQuality = 0;
|
||||||
}
|
_presentation.EnableAutoDepthStencil = false;
|
||||||
setVertex(0, 0, settings.width, settings.height, textureWidth, textureHeight, x, y, windowWidth, windowHeight);
|
_presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN;
|
||||||
device->SetTexture(0, texture);
|
_presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
||||||
device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
|
|
||||||
device->EndScene();
|
|
||||||
|
|
||||||
if(settings.synchronize) {
|
if(!_exclusive) {
|
||||||
D3DRASTER_STATUS status;
|
_presentation.hDeviceWindow = (HWND)_context;
|
||||||
while(true) { //wait for a previous vblank to finish, if necessary
|
_presentation.Windowed = true;
|
||||||
device->GetRasterStatus(0, &status);
|
_presentation.BackBufferFormat = D3DFMT_UNKNOWN;
|
||||||
if(!status.InVBlank) break;
|
_presentation.BackBufferWidth = 0;
|
||||||
}
|
_presentation.BackBufferHeight = 0;
|
||||||
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;
|
ShowWindow((HWND)_exclusiveContext, SW_HIDE);
|
||||||
}
|
if(_instance->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, (HWND)_context,
|
||||||
|
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &_presentation, &_device) != D3D_OK) {
|
||||||
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) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
presentation.hDeviceWindow = settings.exclusiveHandle;
|
_presentation.hDeviceWindow = (HWND)_exclusiveContext;
|
||||||
presentation.Windowed = false;
|
_presentation.Windowed = false;
|
||||||
presentation.BackBufferFormat = D3DFMT_X8R8G8B8;
|
_presentation.BackBufferFormat = D3DFMT_X8R8G8B8;
|
||||||
presentation.BackBufferWidth = monitorWidth;
|
_presentation.BackBufferWidth = _monitorWidth;
|
||||||
presentation.BackBufferHeight = monitorHeight;
|
_presentation.BackBufferHeight = _monitorHeight;
|
||||||
presentation.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
|
_presentation.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
|
||||||
|
|
||||||
ShowWindow(settings.exclusiveHandle, SW_SHOWNORMAL);
|
ShowWindow((HWND)_exclusiveContext, SW_SHOWNORMAL);
|
||||||
if(context->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, settings.exclusiveHandle,
|
if(_instance->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, (HWND)_exclusiveContext,
|
||||||
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) {
|
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &_presentation, &_device) != D3D_OK) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
device->GetDeviceCaps(&capabilities);
|
_device->GetDeviceCaps(&_capabilities);
|
||||||
|
|
||||||
if(capabilities.Caps2 & D3DCAPS2_DYNAMICTEXTURES) {
|
if(_capabilities.Caps2 & D3DCAPS2_DYNAMICTEXTURES) {
|
||||||
flags.textureUsage = D3DUSAGE_DYNAMIC;
|
_textureUsage = D3DUSAGE_DYNAMIC;
|
||||||
flags.texturePool = D3DPOOL_DEFAULT;
|
_texturePool = D3DPOOL_DEFAULT;
|
||||||
flags.vertexUsage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC;
|
_vertexUsage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC;
|
||||||
flags.vertexPool = D3DPOOL_DEFAULT;
|
_vertexPool = D3DPOOL_DEFAULT;
|
||||||
} else {
|
} else {
|
||||||
flags.textureUsage = 0;
|
_textureUsage = 0;
|
||||||
flags.texturePool = D3DPOOL_MANAGED;
|
_texturePool = D3DPOOL_MANAGED;
|
||||||
flags.vertexUsage = D3DUSAGE_WRITEONLY;
|
_vertexUsage = D3DUSAGE_WRITEONLY;
|
||||||
flags.vertexPool = D3DPOOL_MANAGED;
|
_vertexPool = D3DPOOL_MANAGED;
|
||||||
}
|
}
|
||||||
|
|
||||||
lost = false;
|
_lost = false;
|
||||||
return recover();
|
return _ready = recover();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
if(vertexBuffer) { vertexBuffer->Release(); vertexBuffer = nullptr; }
|
if(_vertexBuffer) { _vertexBuffer->Release(); _vertexBuffer = nullptr; }
|
||||||
if(surface) { surface->Release(); surface = nullptr; }
|
if(_surface) { _surface->Release(); _surface = nullptr; }
|
||||||
if(texture) { texture->Release(); texture = nullptr; }
|
if(_texture) { _texture->Release(); _texture = nullptr; }
|
||||||
if(device) { device->Release(); device = nullptr; }
|
if(_device) { _device->Release(); _device = nullptr; }
|
||||||
if(context) { context->Release(); context = 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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,64 +9,35 @@
|
||||||
#include <X11/extensions/XShm.h>
|
#include <X11/extensions/XShm.h>
|
||||||
|
|
||||||
struct VideoXShm : Video {
|
struct VideoXShm : Video {
|
||||||
~VideoXShm() { term(); }
|
VideoXShm() { initialize(); }
|
||||||
|
~VideoXShm() { terminate(); }
|
||||||
|
|
||||||
struct Device {
|
auto ready() -> bool { return _ready; }
|
||||||
Display* display = nullptr;
|
|
||||||
int screen = 0;
|
|
||||||
int depth = 0;
|
|
||||||
Visual* visual = nullptr;
|
|
||||||
Window window = 0;
|
|
||||||
|
|
||||||
XShmSegmentInfo shmInfo;
|
auto context() -> uintptr { return _context; }
|
||||||
XImage* image = nullptr;
|
auto smooth() -> bool { return _smooth; }
|
||||||
uint32_t* buffer = nullptr;
|
|
||||||
uint width = 0;
|
|
||||||
uint height = 0;
|
|
||||||
} device;
|
|
||||||
|
|
||||||
struct Settings {
|
auto setContext(uintptr context) -> bool {
|
||||||
uintptr_t handle = 0;
|
if(_context == context) return true;
|
||||||
uint filter = Video::FilterLinear;
|
_context = context;
|
||||||
|
return initialize();
|
||||||
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 get(const string& name) -> any {
|
auto setSmooth(bool smooth) -> bool {
|
||||||
if(name == Video::Handle) return settings.handle;
|
_smooth = smooth;
|
||||||
if(name == Video::Filter) return settings.filter;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto set(const string& name, const any& value) -> bool {
|
|
||||||
if(name == Video::Handle && value.is<uintptr_t>()) {
|
|
||||||
settings.handle = value.get<uintptr_t>();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if(name == Video::Filter && value.is<uint>()) {
|
|
||||||
settings.filter = value.get<uint>();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(!settings.buffer || settings.width != width || settings.height != height) {
|
if(!_inputBuffer || _inputWidth != width || _inputHeight != height) {
|
||||||
if(settings.buffer) delete[] settings.buffer;
|
if(_inputBuffer) delete[] _inputBuffer;
|
||||||
settings.width = width, settings.height = height;
|
_inputWidth = width;
|
||||||
settings.buffer = new uint32_t[width * height + 16]; //+16 is padding for linear interpolation
|
_inputHeight = height;
|
||||||
|
_inputBuffer = new uint32_t[width * height + 16]; //+16 is padding for linear interpolation
|
||||||
}
|
}
|
||||||
|
|
||||||
data = settings.buffer;
|
data = _inputBuffer;
|
||||||
pitch = settings.width * sizeof(uint32_t);
|
pitch = _inputWidth * sizeof(uint32_t);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,124 +45,124 @@ struct VideoXShm : Video {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clear() -> void {
|
auto clear() -> void {
|
||||||
if(!settings.buffer) return;
|
if(!_ready) return;
|
||||||
uint32_t* dp = settings.buffer;
|
auto dp = _inputBuffer;
|
||||||
uint length = settings.width * settings.height;
|
uint length = _inputWidth * _inputHeight;
|
||||||
while(length--) *dp++ = 255u << 24;
|
while(length--) *dp++ = 255u << 24;
|
||||||
refresh();
|
output();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto refresh() -> void {
|
auto output() -> void {
|
||||||
if(!settings.buffer) return;
|
if(!_ready) return;
|
||||||
size();
|
size();
|
||||||
|
|
||||||
float xratio = (float)settings.width / (float)device.width;
|
float xratio = (float)_inputWidth / (float)_outputWidth;
|
||||||
float yratio = (float)settings.height / (float)device.height;
|
float yratio = (float)_inputHeight / (float)_outputHeight;
|
||||||
|
|
||||||
#pragma omp parallel for
|
#pragma omp parallel for
|
||||||
for(uint y = 0; y < device.height; y++) {
|
for(uint y = 0; y < _outputHeight; y++) {
|
||||||
float ystep = y * yratio;
|
float ystep = y * yratio;
|
||||||
float xstep = 0;
|
float xstep = 0;
|
||||||
|
|
||||||
uint32_t* sp = settings.buffer + (uint)ystep * settings.width;
|
uint32_t* sp = _inputBuffer + (uint)ystep * _inputWidth;
|
||||||
uint32_t* dp = device.buffer + y * device.width;
|
uint32_t* dp = _outputBuffer + y * _outputWidth;
|
||||||
|
|
||||||
if(settings.filter == Video::FilterNearest) {
|
if(!_smooth) {
|
||||||
for(uint x = 0; x < device.width; x++) {
|
for(uint x = 0; x < _outputWidth; x++) {
|
||||||
*dp++ = 255u << 24 | sp[(uint)xstep];
|
*dp++ = 255u << 24 | sp[(uint)xstep];
|
||||||
xstep += xratio;
|
xstep += xratio;
|
||||||
}
|
}
|
||||||
} else { //settings.filter == Video::FilterLinear
|
} else {
|
||||||
for(uint x = 0; x < device.width; x++) {
|
for(uint x = 0; x < _outputWidth; x++) {
|
||||||
*dp++ = 255u << 24 | interpolate(xstep - (uint)xstep, sp[(uint)xstep], sp[(uint)xstep + 1]);
|
*dp++ = 255u << 24 | interpolate(xstep - (uint)xstep, sp[(uint)xstep], sp[(uint)xstep + 1]);
|
||||||
xstep += xratio;
|
xstep += xratio;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GC gc = XCreateGC(device.display, device.window, 0, 0);
|
GC gc = XCreateGC(_display, _window, 0, 0);
|
||||||
XShmPutImage(
|
XShmPutImage(_display, _window, gc, _image, 0, 0, 0, 0, _outputWidth, _outputHeight, False);
|
||||||
device.display, device.window, gc, device.image,
|
XFreeGC(_display, gc);
|
||||||
0, 0, 0, 0, device.width, device.height, False
|
XFlush(_display);
|
||||||
);
|
|
||||||
XFreeGC(device.display, gc);
|
|
||||||
XFlush(device.display);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
private:
|
||||||
device.display = XOpenDisplay(0);
|
auto initialize() -> bool {
|
||||||
device.screen = DefaultScreen(device.display);
|
terminate();
|
||||||
|
if(!_context) return false;
|
||||||
|
|
||||||
|
_display = XOpenDisplay(0);
|
||||||
|
_screen = DefaultScreen(_display);
|
||||||
|
|
||||||
XWindowAttributes getAttributes;
|
XWindowAttributes getAttributes;
|
||||||
XGetWindowAttributes(device.display, (Window)settings.handle, &getAttributes);
|
XGetWindowAttributes(_display, (Window)_context, &getAttributes);
|
||||||
device.depth = getAttributes.depth;
|
_depth = getAttributes.depth;
|
||||||
device.visual = getAttributes.visual;
|
_visual = getAttributes.visual;
|
||||||
//driver only supports 32-bit pixels
|
//driver only supports 32-bit pixels
|
||||||
//note that even on 15-bit and 16-bit displays, the window visual's depth should be 32
|
//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();
|
free();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
XSetWindowAttributes setAttributes = {0};
|
XSetWindowAttributes setAttributes = {0};
|
||||||
setAttributes.border_pixel = 0;
|
setAttributes.border_pixel = 0;
|
||||||
device.window = XCreateWindow(device.display, (Window)settings.handle,
|
_window = XCreateWindow(_display, (Window)_context,
|
||||||
0, 0, 256, 256, 0,
|
0, 0, 256, 256, 0,
|
||||||
getAttributes.depth, InputOutput, getAttributes.visual,
|
getAttributes.depth, InputOutput, getAttributes.visual,
|
||||||
CWBorderPixel, &setAttributes
|
CWBorderPixel, &setAttributes
|
||||||
);
|
);
|
||||||
XSetWindowBackground(device.display, device.window, 0);
|
XSetWindowBackground(_display, _window, 0);
|
||||||
XMapWindow(device.display, device.window);
|
XMapWindow(_display, _window);
|
||||||
XFlush(device.display);
|
XFlush(_display);
|
||||||
|
|
||||||
while(XPending(device.display)) {
|
while(XPending(_display)) {
|
||||||
XEvent event;
|
XEvent event;
|
||||||
XNextEvent(device.display, &event);
|
XNextEvent(_display, &event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!size()) return false;
|
if(!size()) return false;
|
||||||
return true;
|
return _ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
free();
|
free();
|
||||||
if(device.display) {
|
if(_display) {
|
||||||
XCloseDisplay(device.display);
|
XCloseDisplay(_display);
|
||||||
device.display = nullptr;
|
_display = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
auto size() -> bool {
|
auto size() -> bool {
|
||||||
XWindowAttributes windowAttributes;
|
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;
|
if(_outputBuffer && _outputWidth == windowAttributes.width && _outputHeight == windowAttributes.height) return true;
|
||||||
device.width = windowAttributes.width, device.height = windowAttributes.height;
|
_outputWidth = windowAttributes.width;
|
||||||
XResizeWindow(device.display, device.window, device.width, device.height);
|
_outputHeight = windowAttributes.height;
|
||||||
|
XResizeWindow(_display, _window, _outputWidth, _outputHeight);
|
||||||
free();
|
free();
|
||||||
|
|
||||||
device.shmInfo.shmid = shmget(IPC_PRIVATE, device.width * device.height * sizeof(uint32_t), IPC_CREAT | 0777);
|
_shmInfo.shmid = shmget(IPC_PRIVATE, _outputWidth * _outputHeight * sizeof(uint32_t), IPC_CREAT | 0777);
|
||||||
if(device.shmInfo.shmid < 0) return false;
|
if(_shmInfo.shmid < 0) return false;
|
||||||
|
|
||||||
device.shmInfo.shmaddr = (char*)shmat(device.shmInfo.shmid, 0, 0);
|
_shmInfo.shmaddr = (char*)shmat(_shmInfo.shmid, 0, 0);
|
||||||
device.shmInfo.readOnly = False;
|
_shmInfo.readOnly = False;
|
||||||
XShmAttach(device.display, &device.shmInfo);
|
XShmAttach(_display, &_shmInfo);
|
||||||
device.buffer = (uint32_t*)device.shmInfo.shmaddr;
|
_outputBuffer = (uint32_t*)_shmInfo.shmaddr;
|
||||||
device.image = XShmCreateImage(device.display, device.visual, device.depth,
|
_image = XShmCreateImage(_display, _visual, _depth, ZPixmap, _shmInfo.shmaddr, &_shmInfo, _outputWidth, _outputHeight);
|
||||||
ZPixmap, device.shmInfo.shmaddr, &device.shmInfo, device.width, device.height
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto free() -> void {
|
auto free() -> void {
|
||||||
if(!device.buffer) return;
|
if(_outputBuffer) {
|
||||||
device.buffer = nullptr;
|
_outputBuffer = nullptr;
|
||||||
XShmDetach(device.display, &device.shmInfo);
|
XShmDetach(_display, &_shmInfo);
|
||||||
XDestroyImage(device.image);
|
XDestroyImage(_image);
|
||||||
shmdt(device.shmInfo.shmaddr);
|
shmdt(_shmInfo.shmaddr);
|
||||||
shmctl(device.shmInfo.shmid, IPC_RMID, 0);
|
shmctl(_shmInfo.shmid, IPC_RMID, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
alwaysinline auto interpolate(float mu, uint32_t a, uint32_t b) -> uint32_t {
|
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;
|
uint8_t cb = ab * (1.0 - mu) + bb * mu;
|
||||||
return cr << 16 | cg << 8 | cb << 0;
|
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;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue