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:
Tim Allen 2017-07-17 15:11:18 +10:00
parent 17697317d4
commit 4129630d97
29 changed files with 998 additions and 886 deletions

View File

@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "103.14";
static const string Version = "103.15";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -18,20 +18,20 @@ ui_objects += $(if $(call streq,$(platform),windows),ui-resource)
# platform
ifeq ($(platform),windows)
ruby += video.direct3d video.wgl video.directdraw video.gdi
ruby += audio.asio audio.wasapi audio.xaudio2 audio.directsound
ruby += video.direct3d #video.wgl video.directdraw video.gdi
ruby += audio.asio #audio.wasapi audio.xaudio2 audio.directsound
ruby += input.windows
else ifeq ($(platform),macosx)
ruby += video.cgl
ruby += audio.openal
ruby += input.quartz input.carbon
ruby += #video.cgl
ruby += #audio.openal
ruby += #input.quartz input.carbon
else ifeq ($(platform),linux)
ruby += video.glx video.xv video.xshm video.sdl
ruby += audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao
ruby += input.udev input.sdl input.xlib
ruby += video.xshm #video.glx video.xv video.xshm video.sdl
ruby += audio.oss #audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao
ruby += input.sdl input.xlib #input.udev input.sdl input.xlib
else ifeq ($(platform),bsd)
ruby += video.glx video.xv video.xshm video.sdl
ruby += audio.openal audio.oss
ruby += video.xshm #video.glx video.xv video.xshm video.sdl
ruby += audio.oss #audio.alsa
ruby += input.sdl input.xlib
endif

View File

@ -42,13 +42,14 @@ Settings::Settings() {
set("Audio/Driver", ruby::Audio::optimalDriver());
set("Audio/Device", "");
set("Audio/Frequency", 48000);
set("Audio/Latency", 0);
set("Audio/Exclusive", false);
set("Audio/Synchronize", true);
set("Audio/Mute", false);
set("Audio/Volume", 100);
set("Audio/Balance", 50);
set("Audio/Reverb/Enable", false);
set("Audio/Latency", 60);
set("Audio/Resampler", "Sinc");
set("Input/Driver", ruby::Input::optimalDriver());

View File

@ -62,12 +62,12 @@ auto InputManager::appendHotkeys() -> void {
{ auto hotkey = new InputHotkey;
hotkey->name = "Fast Forward";
hotkey->press = [] {
video->set(Video::Synchronize, false);
audio->set(Audio::Synchronize, false);
video->setBlocking(false);
audio->setBlocking(false);
};
hotkey->release = [] {
video->set(Video::Synchronize, settings["Video/Synchronize"].boolean());
audio->set(Audio::Synchronize, settings["Audio/Synchronize"].boolean());
video->setBlocking(settings["Video/Synchronize"].boolean());
audio->setBlocking(settings["Audio/Synchronize"].boolean());
};
hotkeys.append(hotkey);
}

View File

@ -84,11 +84,11 @@ Presentation::Presentation() {
loadShaders();
synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Synchronize"].boolean()).setVisible(false).onToggle([&] {
settings["Video/Synchronize"].setValue(synchronizeVideo.checked());
video->set(Video::Synchronize, synchronizeVideo.checked());
video->setBlocking(synchronizeVideo.checked());
});
synchronizeAudio.setText("Synchronize Audio").setChecked(settings["Audio/Synchronize"].boolean()).onToggle([&] {
settings["Audio/Synchronize"].setValue(synchronizeAudio.checked());
audio->set(Audio::Synchronize, synchronizeAudio.checked());
audio->setBlocking(synchronizeAudio.checked());
});
muteAudio.setText("Mute Audio").setChecked(settings["Audio/Mute"].boolean()).onToggle([&] {
settings["Audio/Mute"].setValue(muteAudio.checked());
@ -229,7 +229,7 @@ auto Presentation::clearViewport() -> void {
}
video->unlock();
video->refresh();
video->output();
}
}
@ -313,11 +313,11 @@ auto Presentation::toggleFullScreen() -> void {
menuBar.setVisible(false);
statusBar.setVisible(false);
setFullScreen(true);
video->set(Video::Exclusive, settings["Video/Fullscreen/Exclusive"].boolean());
video->setExclusive(settings["Video/Fullscreen/Exclusive"].boolean());
if(!input->acquired()) input->acquire();
} else {
if(input->acquired()) input->release();
video->set(Video::Exclusive, false);
video->setExclusive(false);
setFullScreen(false);
menuBar.setVisible(true);
statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean());

View File

@ -74,7 +74,7 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig
}
video->unlock();
video->refresh();
video->output();
}
static uint frameCounter = 0;
@ -90,9 +90,7 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig
}
auto Program::audioSample(const double* samples, uint channels) -> void {
int16 left = sclamp<16>(samples[0] * 32768.0);
int16 right = sclamp<16>(samples[1] * 32768.0);
audio->sample(left, right);
audio->output(samples);
}
auto Program::inputPoll(uint port, uint device, uint input) -> int16 {

View File

@ -19,7 +19,7 @@ auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interfa
mediumPaths.append(locate({medium.name, ".sys/"}));
Emulator::audio.reset(2, audio->get(Audio::Frequency).get<uint>(44100));
Emulator::audio.reset(2, audio->frequency());
inputManager->bind(emulator = &interface);
if(!emulator->load(medium.id)) {
emulator = nullptr;

View File

@ -35,23 +35,23 @@ Program::Program(string_vector args) {
presentation->setVisible();
video = Video::create(settings["Video/Driver"].text());
video->set(Video::Handle, presentation->viewport.handle());
video->set(Video::Synchronize, settings["Video/Synchronize"].boolean());
if(!video->init()) video = Video::create("None");
video->setContext(presentation->viewport.handle());
video->setBlocking(settings["Video/Synchronize"].boolean());
if(!video->ready()) MessageDialog().setText("Failed to initialize video driver").warning();
presentation->clearViewport();
audio = Audio::create(settings["Audio/Driver"].text());
audio->set(Audio::Device, settings["Audio/Device"].text());
audio->set(Audio::Handle, presentation->viewport.handle());
audio->set(Audio::Synchronize, settings["Audio/Synchronize"].boolean());
audio->set(Audio::Latency, 80u);
if(!audio->init()) audio = Audio::create("None");
audio->setContext(presentation->viewport.handle());
audio->setBlocking(settings["Audio/Synchronize"].boolean());
audio->setFrequency(settings["Audio/Frequency"].natural());
audio->setChannels(2);
if(!audio->ready()) MessageDialog().setText("Failed to initialize audio driver").warning();
input = Input::create(settings["Input/Driver"].text());
input->set(Input::Handle, presentation->viewport.handle());
input->setContext(presentation->viewport.handle());
input->onChange({&InputManager::onChange, &inputManager()});
if(!input->init()) input = Input::create("None");
if(!input->ready()) MessageDialog().setText("Failed to initialize input driver").warning();
new InputManager;
new SettingsManager;

View File

@ -67,19 +67,21 @@ auto Program::updateVideoShader() -> void {
&& settings["Video/Shader"].text() != "Blur"
&& directory::exists(settings["Video/Shader"].text())
) {
video->set(Video::Filter, Video::FilterNearest);
video->set(Video::Shader, settings["Video/Shader"].text());
video->setSmooth(false);
video->setShader(settings["Video/Shader"].text());
} else {
video->set(Video::Filter, settings["Video/Shader"].text() == "Blur" ? Video::FilterLinear : Video::FilterNearest);
video->set(Video::Shader, (string)"");
video->setSmooth(settings["Video/Shader"].text() == "Blur");
video->setShader("");
}
}
auto Program::updateAudioDriver() -> void {
if(!audio) return;
audio->clear();
audio->set(Audio::Exclusive, settings["Audio/Exclusive"].boolean());
audio->set(Audio::Latency, (uint)settings["Audio/Latency"].natural());
audio->setDevice(settings["Audio/Device"].text());
audio->setExclusive(settings["Audio/Exclusive"].boolean());
//audio->setFrequency(settings["Audio/Frequency"].natural());
audio->setLatency(settings["Audio/Latency"].natural());
}
auto Program::updateAudioEffects() -> void {
@ -95,12 +97,7 @@ auto Program::updateAudioEffects() -> void {
auto Program::focused() -> bool {
//exclusive mode creates its own top-level window: presentation window will not have focus
if(video->cap(Video::Exclusive)) {
auto value = video->get(Video::Exclusive);
if(value.is<bool>() && value.get<bool>()) return true;
}
if(video->exclusive()) return true;
if(presentation && presentation->focused()) return true;
return false;
}

View File

@ -6,35 +6,37 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) {
driverLabel.setFont(Font().setBold()).setText("Driver Settings");
latencyLabel.setText("Latency:");
latencyCombo.append(ComboButtonItem().setText("0ms"));
latencyCombo.append(ComboButtonItem().setText("20ms"));
latencyCombo.append(ComboButtonItem().setText("40ms"));
latencyCombo.append(ComboButtonItem().setText("60ms"));
latencyCombo.append(ComboButtonItem().setText("80ms"));
latencyCombo.append(ComboButtonItem().setText("100ms"));
switch(settings["Audio/Latency"].natural()) {
case 0: latencyCombo.item(0)->setSelected(); break;
case 20: latencyCombo.item(1)->setSelected(); break;
case 40: latencyCombo.item(2)->setSelected(); break;
case 60: latencyCombo.item(3)->setSelected(); break;
case 80: latencyCombo.item(4)->setSelected(); break;
case 100: latencyCombo.item(5)->setSelected(); break;
auto information = audio->information();
deviceLabel.setText("Device:");
for(auto& device : information.devices) {
deviceList.append(ComboButtonItem().setText(device));
if(device == settings["Audio/Device"].text()) {
deviceList.item(deviceList.itemCount() - 1).setSelected();
}
}
latencyCombo.onChange([&] { updateDriver(); });
deviceList.onChange([&] { updateDriver(); });
frequencyLabel.setText("Frequency:");
auto frequencyValue = audio->get(Audio::Frequency).get<uint>(44100);
frequencyCombo.append(ComboButtonItem().setText({frequencyValue, "hz"}));
frequencyCombo.setEnabled(false);
for(auto& frequency : information.frequencies) {
frequencyList.append(ComboButtonItem().setText(frequency));
if(frequency == settings["Audio/Frequency"].natural()) {
frequencyList.item(frequencyList.itemCount() - 1).setSelected();
}
}
frequencyList.onChange([&] { updateDriver(); });
resamplerLabel.setText("Resampler:");
resamplerCombo.append(ComboButtonItem().setText("IIR - Biquad"));
resamplerCombo.setEnabled(false);
latencyLabel.setText("Latency:");
for(auto& latency : information.latencies) {
latencyList.append(ComboButtonItem().setText(latency));
if(latency == settings["Audio/Latency"].natural()) {
latencyList.item(latencyList.itemCount() - 1).setSelected();
}
}
latencyList.onChange([&] { updateDriver(); });
exclusiveMode.setText("Exclusive mode");
exclusiveMode.setChecked(settings["Audio/Exclusive"].boolean()).onToggle([&] { updateDriver(); });
if(!audio->cap(Audio::Exclusive)) exclusiveMode.remove();
effectsLabel.setFont(Font().setBold()).setText("Effects");
@ -53,17 +55,9 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) {
}
auto AudioSettings::updateDriver() -> void {
if(auto item = latencyCombo.selected()) {
uint latency = 60;
if(item->offset() == 0) latency = 0;
if(item->offset() == 1) latency = 20;
if(item->offset() == 2) latency = 40;
if(item->offset() == 3) latency = 60;
if(item->offset() == 4) latency = 80;
if(item->offset() == 5) latency = 100;
settings["Audio/Latency"].setValue(latency);
}
settings["Audio/Device"].setValue(deviceList.selected().text());
settings["Audio/Frequency"].setValue(frequencyList.selected().text());
settings["Audio/Latency"].setValue(latencyList.selected().text());
settings["Audio/Exclusive"].setValue(exclusiveMode.checked());
program->updateAudioDriver();
}

View File

@ -45,12 +45,12 @@ struct AudioSettings : TabFrameItem {
VerticalLayout layout{this};
Label driverLabel{&layout, Size{~0, 0}, 2};
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Label latencyLabel{&controlLayout, Size{0, 0}};
ComboButton latencyCombo{&controlLayout, Size{~0, 0}};
Label deviceLabel{&controlLayout, Size{0, 0}};
ComboButton deviceList{&controlLayout, Size{~0, 0}};
Label frequencyLabel{&controlLayout, Size{0, 0}};
ComboButton frequencyCombo{&controlLayout, Size{~0, 0}};
Label resamplerLabel{&controlLayout, Size{0, 0}};
ComboButton resamplerCombo{&controlLayout, Size{~0, 0}};
ComboButton frequencyList{&controlLayout, Size{~0, 0}};
Label latencyLabel{&controlLayout, Size{0, 0}};
ComboButton latencyList{&controlLayout, Size{~0, 0}};
CheckLabel exclusiveMode{&layout, Size{~0, 0}};
Label effectsLabel{&layout, Size{~0, 0}, 2};
HorizontalLayout volumeLayout{&layout, Size{~0, 0}};

View File

@ -32,7 +32,6 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) {
fullscreenModeAspectCorrection.setText("Aspect correction").setChecked(settings["Video/Fullscreen/AspectCorrection"].boolean()).onToggle([&] { updateViewport(); });
fullscreenModeIntegralScaling.setText("Integral scaling").setChecked(settings["Video/Fullscreen/IntegralScaling"].boolean()).onToggle([&] { updateViewport(); });
fullscreenModeExclusive.setText("Exclusive mode").setChecked(settings["Video/Fullscreen/Exclusive"].boolean()).onToggle([&] { updateViewport(); });
if(!video->cap(Video::Exclusive)) fullscreenModeExclusive.remove();
updateColor(true);
updateViewport(true);

View File

@ -1,90 +1,273 @@
#include "asio.hpp"
struct AudioASIO : Audio {
~AudioASIO() { term(); }
static AudioASIO* self;
AudioASIO() { self = this; initialize(); }
~AudioASIO() { terminate(); }
struct Settings {
HWND handle = nullptr;
bool synchronize = true;
uint frequency = 48000;
} settings;
auto ready() -> bool { return _ready; }
struct Driver {
string name;
string classID;
};
vector<Driver> drivers;
Driver driver;
IASIO* device = nullptr;
auto cap(const string& name) -> bool {
if(name == Audio::Handle) return true;
if(name == Audio::Synchronize) return true;
if(name == Audio::Frequency) return true;
return false;
auto information() -> Information {
Information information;
for(auto& device : _devices) information.devices.append(device.name);
information.frequencies = {_frequency};
uint latencies[] = {64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 6144}; //factors of 6144
for(auto& latency : latencies) {
if(latency < _active.minimumBufferSize) continue;
if(latency > _active.maximumBufferSize) continue;
information.latencies.append(latency);
}
information.channels = {1, 2};
return information;
}
auto get(const string& name) -> any {
if(name == Audio::Handle) return (uintptr)settings.handle;
if(name == Audio::Synchronize) return settings.synchronize;
if(name == Audio::Frequency) return settings.frequency;
return {};
auto context() -> uintptr { return _context; }
auto blocking() -> bool { return _blocking; }
auto channels() -> uint { return _channels; }
auto frequency() -> uint { return _frequency; }
auto latency() -> uint { return _latency; }
auto setContext(uintptr context) -> bool {
if(_context == context) return true;
_context = context;
return initialize();
}
auto set(const string& name, const any& value) -> bool {
if(name == Audio::Handle && value.is<uintptr>()) {
settings.handle = (HWND)value.get<uintptr>();
return true;
}
if(name == Audio::Synchronize && value.is<bool>()) {
settings.synchronize = value.get<bool>();
return true;
}
if(name == Audio::Frequency && value.is<uint>()) {
settings.frequency = value.get<uint>();
return true;
}
return false;
auto setBlocking(bool blocking) -> bool {
if(_blocking == blocking) return true;
_blocking = blocking;
return initialize();
}
auto sample(int16_t left, int16_t right) -> void {
auto setChannels(uint channels) -> bool {
if(_channels == channels) return true;
_channels = channels;
return initialize();
}
auto setLatency(uint latency) -> bool {
if(_latency == latency) return true;
_latency = latency;
return initialize();
}
auto output(const double samples[]) -> void {
if(!_ready) return;
if(_blocking) {
while(_queue.count >= _latency);
}
for(uint n : range(_channels)) {
_queue.samples[_queue.write][n] = samples[n];
}
_queue.write++;
_queue.count++;
}
auto clear() -> void {
if(!_ready) return;
for(uint n : range(_channels)) {
memory::fill(_channel[n].buffers[0], _latency * _sampleSize);
memory::fill(_channel[n].buffers[1], _latency * _sampleSize);
}
memory::fill(_queue.samples, sizeof(_queue.samples));
_queue.read = 0;
_queue.write = 0;
_queue.count = 0;
}
auto init() -> bool {
private:
auto initialize() -> bool {
terminate();
//enumerate available ASIO drivers from the registry
for(auto candidate : registry::contents("HKLM\\SOFTWARE\\ASIO\\")) {
if(auto classID = registry::read({"HKLM\\SOFTWARE\\ASIO\\", candidate, "CLSID"})) {
drivers.append({candidate.trimRight("\\", 1L), classID});
_devices.append({candidate.trimRight("\\", 1L), classID});
if(candidate == _device) _active = _devices.right();
}
}
if(!drivers) return false;
if(!_devices) return false;
//default to first driver for now
driver = drivers[0];
if(!_active.name) {
_active = _devices.left();
_device = _active.name;
}
CLSID classID;
if(CLSIDFromString((LPOLESTR)utf16_t(driver.classID), (LPCLSID)&classID) != S_OK) return false;
if(CoCreateInstance(classID, 0, CLSCTX_INPROC_SERVER, classID, (void**)&device) != S_OK) return false;
if(CLSIDFromString((LPOLESTR)utf16_t(_active.classID), (LPCLSID)&classID) != S_OK) return false;
if(CoCreateInstance(classID, 0, CLSCTX_INPROC_SERVER, classID, (void**)&_asio) != S_OK) return false;
if(!device->init((void*)settings.handle)) return false;
if(!_asio->init((void*)_context)) return false;
if(_asio->getSampleRate(&_active.sampleRate) != ASE_OK) return false;
if(_asio->getChannels(&_active.inputChannels, &_active.outputChannels) != ASE_OK) return false;
if(_asio->getBufferSize(
&_active.minimumBufferSize,
&_active.maximumBufferSize,
&_active.preferredBufferSize,
&_active.granularity
) != ASE_OK) return false;
//temporary debugging information
char driverName[4096] = {0};
device->getDriverName(driverName);
print("Driver: ", driverName, "\n");
print("Version: ", device->getDriverVersion(), "\n");
print("---\n");
_frequency = _active.sampleRate;
_latency = _latency < _active.minimumBufferSize ? _active.minimumBufferSize : _latency;
_latency = _latency > _active.maximumBufferSize ? _active.maximumBufferSize : _latency;
return true;
for(auto n : range(_channels)) {
_channel[n].isInput = false;
_channel[n].channelNum = n;
_channel[n].buffers[0] = nullptr;
_channel[n].buffers[1] = nullptr;
}
ASIOCallbacks callbacks;
callbacks.bufferSwitch = &AudioASIO::_bufferSwitch;
callbacks.sampleRateDidChange = &AudioASIO::_sampleRateDidChange;
callbacks.asioMessage = &AudioASIO::_asioMessage;
callbacks.bufferSwitchTimeInfo = &AudioASIO::_bufferSwitchTimeInfo;
if(_asio->createBuffers(_channel, _channels, _latency, &callbacks) != ASE_OK) return false;
if(_asio->getLatencies(&_active.inputLatency, &_active.outputLatency) != ASE_OK) return false;
//assume for the sake of sanity that all buffers use the same sample format ...
ASIOChannelInfo channelInformation = {};
channelInformation.channel = 0;
channelInformation.isInput = false;
if(_asio->getChannelInfo(&channelInformation) != ASE_OK) return false;
switch(_sampleFormat = channelInformation.type) {
case ASIOSTInt16LSB: _sampleSize = 2; break;
case ASIOSTInt24LSB: _sampleSize = 3; break;
case ASIOSTInt32LSB: _sampleSize = 4; break;
case ASIOSTFloat32LSB: _sampleSize = 4; break;
case ASIOSTFloat64LSB: _sampleSize = 8; break;
default: return false; //unsupported sample format
}
clear();
if(_asio->start() != ASE_OK) return false;
return _ready = true;
}
auto term() -> void {
auto terminate() -> void {
_ready = false;
_devices.reset();
_active = {};
if(!_asio) return;
_asio->stop();
_asio->disposeBuffers();
_asio->Release();
_asio = nullptr;
}
private:
static auto _bufferSwitch(long doubleBufferInput, ASIOBool directProcess) -> void {
return self->bufferSwitch(doubleBufferInput, directProcess);
}
static auto _sampleRateDidChange(ASIOSampleRate sampleRate) -> void {
return self->sampleRateDidChange(sampleRate);
}
static auto _asioMessage(long selector, long value, void* message, double* optional) -> long {
return self->asioMessage(selector, value, message, optional);
}
static auto _bufferSwitchTimeInfo(ASIOTime* parameters, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime* {
return self->bufferSwitchTimeInfo(parameters, doubleBufferIndex, directProcess);
}
auto bufferSwitch(long doubleBufferInput, ASIOBool directProcess) -> void {
for(uint sampleIndex : range(_latency)) {
double samples[8] = {0};
if(_queue.count) {
for(uint n : range(_channels)) {
samples[n] = _queue.samples[_queue.read][n];
}
_queue.read++;
_queue.count--;
}
for(uint n : range(_channels)) {
auto buffer = (uint8_t*)_channel[n].buffers[doubleBufferInput];
buffer += sampleIndex * _sampleSize;
switch(_sampleFormat) {
case ASIOSTInt16LSB: {
*(int16_t*)buffer = samples[n] * double(1 << 15);
break;
}
case ASIOSTInt24LSB: {
int value = samples[n] * double(1 << 23);
buffer[0] = value >> 0;
buffer[1] = value >> 8;
buffer[2] = value >> 16;
break;
}
case ASIOSTInt32LSB: {
*(int32_t*)buffer = samples[n] * double(1 << 31);
break;
}
case ASIOSTFloat32LSB: {
*(float*)buffer = samples[n];
break;
}
case ASIOSTFloat64LSB: {
*(double*)buffer = samples[n];
break;
}
}
}
}
}
auto sampleRateDidChange(ASIOSampleRate sampleRate) -> void {
}
auto asioMessage(long selector, long value, void* message, double* optional) -> long {
return ASE_OK;
}
auto bufferSwitchTimeInfo(ASIOTime* parameters, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime* {
return nullptr;
}
bool _ready = false;
uintptr _context = 0;
string _device;
bool _blocking = true;
uint _channels = 2;
uint _frequency = 48000;
uint _latency = 0;
struct Queue {
double samples[65536][8];
uint16_t read;
uint16_t write;
std::atomic<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;

View File

@ -52,14 +52,6 @@ enum : long {
ASIOSTLastEntry,
};
struct ASIODriverInfo {
long asioVersion;
long driverVersion;
char name[32];
char errorMessage[124];
void* sysRef;
};
struct ASIOBufferInfo {
ASIOBool isInput;
long channelNum;
@ -123,9 +115,9 @@ struct ASIOTime {
struct ASIOCallbacks {
auto (*bufferSwitch)(long doubleBufferIndex, ASIOBool directProcess) -> void;
auto (*sampleRateDidChange)(ASIOSampleRate sRate) -> void;
auto (*asioMessage)(long selector, long value, void* message, double* opt) -> long;
auto (*bufferSwitchTimeInfo)(ASIOTime* params, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime*;
auto (*sampleRateDidChange)(ASIOSampleRate sampleRate) -> void;
auto (*asioMessage)(long selector, long value, void* message, double* optional) -> long;
auto (*bufferSwitchTimeInfo)(ASIOTime* parameters, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime*;
};
enum : long {
kAsioSelectorSupported = 1,
@ -147,25 +139,25 @@ enum : long {
};
struct IASIO : public IUnknown {
virtual auto init(void* sysHandle) -> ASIOBool;
virtual auto init(void* systemHandle) -> ASIOBool;
virtual auto getDriverName(char* name) -> void;
virtual auto getDriverVersion() -> long;
virtual auto getErrorMessage(char* error) -> void;
virtual auto start() -> ASIOError;
virtual auto stop() -> ASIOError;
virtual auto getChannels(long* numInputChannels, long* numOutputChannels) -> ASIOError = 0;
virtual auto getChannels(long* inputChannels, long* outputChannels) -> ASIOError = 0;
virtual auto getLatencies(long* inputLatency, long* outputLatency) -> ASIOError = 0;
virtual auto getBufferSize(long* minSize, long* maxSize, long* preferredSize, long* granularity) -> ASIOError = 0;
virtual auto getBufferSize(long* minimumSize, long* maximumSize, long* preferredSize, long* granularity) -> ASIOError = 0;
virtual auto canSampleRate(ASIOSampleRate sampleRate) -> ASIOError = 0;
virtual auto getSampleRate(ASIOSampleRate* sampleRate) -> ASIOError = 0;
virtual auto setSampleRate(ASIOSampleRate sampleRate) -> ASIOError = 0;
virtual auto getClockSources(ASIOClockSource* clocks, long* numSources) -> ASIOError = 0;
virtual auto getClockSources(ASIOClockSource* clocks, long* sources) -> ASIOError = 0;
virtual auto setClockSource(long reference) -> ASIOError = 0;
virtual auto getSamplePosition(ASIOSamples* sPos, ASIOTimeStamp* tStamp) -> ASIOError = 0;
virtual auto getChannelInfo(ASIOChannelInfo* info) -> ASIOError = 0;
virtual auto createBuffers(ASIOBufferInfo* bufferInfos, long numChannels, long bufferSize, ASIOCallbacks* callbacks) -> ASIOError = 0;
virtual auto getSamplePosition(ASIOSamples* samplePosition, ASIOTimeStamp* timeStamp) -> ASIOError = 0;
virtual auto getChannelInfo(ASIOChannelInfo* information) -> ASIOError = 0;
virtual auto createBuffers(ASIOBufferInfo* bufferInformation, long channels, long bufferSize, ASIOCallbacks* callbacks) -> ASIOError = 0;
virtual auto disposeBuffers() -> ASIOError = 0;
virtual auto controlPanel() -> ASIOError = 0;
virtual auto future(long selector, void* opt) -> ASIOError = 0;
virtual auto future(long selector, void* optional) -> ASIOError = 0;
virtual auto outputReady() -> ASIOError = 0;
};

View File

@ -14,104 +14,108 @@
#endif
struct AudioOSS : Audio {
~AudioOSS() { term(); }
AudioOSS() { initialize(); }
~AudioOSS() { terminate(); }
struct {
int fd = -1;
int format = AFMT_S16_LE;
int channels = 2;
} device;
auto ready() -> bool { return _ready; }
struct {
string device = "/dev/dsp";
bool synchronize = true;
uint frequency = 48000;
uint latency = 60;
} settings;
auto cap(const string& name) -> bool {
if(name == Audio::Device) return true;
if(name == Audio::Synchronize) return true;
if(name == Audio::Frequency) return true;
if(name == Audio::Latency) return true;
return false;
auto information() -> Information {
Information information;
information.devices = {"/dev/dsp"};
for(auto& device : directory::files("/dev/", "dsp?*")) information.devices.append(string{"/dev/", device});
information.frequencies = {44100, 48000, 96000};
information.latencies = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
information.channels = {1, 2};
return information;
}
auto get(const string& name) -> any {
if(name == Audio::Device) return settings.device;
if(name == Audio::Synchronize) return settings.synchronize;
if(name == Audio::Frequency) return settings.frequency;
if(name == Audio::Latency) return settings.latency;
return {};
auto device() -> string { return _device; }
auto blocking() -> bool { return _blocking; }
auto channels() -> uint { return _channels; }
auto frequency() -> uint { return _frequency; }
auto latency() -> uint { return _latency; }
auto setDevice(string device) -> bool {
if(_device == device) return true;
_device = device;
return initialize();
}
auto set(const string& name, const any& value) -> bool {
if(name == Audio::Device && value.is<string>()) {
settings.device = value.get<string>();
if(!settings.device) settings.device = "/dev/dsp";
return true;
}
if(name == Audio::Synchronize && value.is<bool>()) {
settings.synchronize = value.get<bool>();
updateSynchronization();
return true;
}
if(name == Audio::Frequency && value.is<uint>()) {
settings.frequency = value.get<uint>();
if(device.fd >= 0) init();
return true;
}
if(name == Audio::Latency && value.is<uint>()) {
settings.latency = value.get<uint>();
if(device.fd >= 0) init();
return true;
}
return false;
}
auto sample(int16_t left, int16_t right) -> void {
uint32_t sample = (uint16_t)left << 0 | (uint16_t)right << 16;
auto unused = write(device.fd, &sample, 4);
}
auto clear() -> void {
}
auto init() -> bool {
device.fd = open(settings.device, O_WRONLY, O_NONBLOCK);
if(device.fd < 0) return false;
int cooked = 1;
ioctl(device.fd, SNDCTL_DSP_COOKEDMODE, &cooked);
//policy: 0 = minimum latency (higher CPU usage); 10 = maximum latency (lower CPU usage)
int policy = min(10, settings.latency / 20); //note: latency measurement isn't exact
ioctl(device.fd, SNDCTL_DSP_POLICY, &policy);
int frequency = settings.frequency;
ioctl(device.fd, SNDCTL_DSP_CHANNELS, &device.channels);
ioctl(device.fd, SNDCTL_DSP_SETFMT, &device.format);
ioctl(device.fd, SNDCTL_DSP_SPEED, &frequency);
updateSynchronization();
auto setBlocking(bool blocking) -> bool {
if(_blocking == blocking) return true;
_blocking = blocking;
updateBlocking();
return true;
}
auto term() -> void {
if(device.fd >= 0) {
close(device.fd);
device.fd = -1;
auto setChannels(uint channels) -> bool {
if(_channels == channels) return true;
_channels = channels;
return initialize();
}
auto setFrequency(uint frequency) -> bool {
if(_frequency == frequency) return true;
_frequency = frequency;
return initialize();
}
auto setLatency(uint latency) -> bool {
if(_latency == latency) return true;
_latency = latency;
return initialize();
}
auto output(const double samples[]) -> void {
if(!_ready) return;
for(auto n : range(_channels)) {
int16_t sample = samples[n] * 32768.0;
auto unused = write(_fd, &sample, 2);
}
}
private:
auto updateSynchronization() -> void {
if(device.fd < 0) return;
auto flags = fcntl(device.fd, F_GETFL);
if(flags < 0) return;
settings.synchronize ? flags &=~ O_NONBLOCK : flags |= O_NONBLOCK;
fcntl(device.fd, F_SETFL, flags);
auto initialize() -> bool {
terminate();
_fd = open(_device, O_WRONLY, O_NONBLOCK);
if(_fd < 0) return false;
int cooked = 1;
ioctl(_fd, SNDCTL_DSP_COOKEDMODE, &cooked);
//policy: 0 = minimum latency (higher CPU usage); 10 = maximum latency (lower CPU usage)
int policy = min(10, _latency);
ioctl(_fd, SNDCTL_DSP_POLICY, &policy);
ioctl(_fd, SNDCTL_DSP_CHANNELS, &_channels);
ioctl(_fd, SNDCTL_DSP_SETFMT, &_format);
ioctl(_fd, SNDCTL_DSP_SPEED, &_frequency);
updateBlocking();
return _ready = true;
}
auto terminate() -> void {
_ready = false;
if(_fd < 0) return;
close(_fd);
_fd = -1;
}
auto updateBlocking() -> void {
if(!_ready) return;
auto flags = fcntl(_fd, F_GETFL);
if(flags < 0) return;
_blocking ? flags &=~ O_NONBLOCK : flags |= O_NONBLOCK;
fcntl(_fd, F_SETFL, flags);
}
bool _ready = false;
string _device = "/dev/dsp";
bool _blocking = true;
int _channels = 2;
int _frequency = 48000;
int _latency = 2;
int _fd = -1;
int _format = AFMT_S16_LE;
};

View File

@ -86,7 +86,8 @@ struct InputJoypadDirectInput {
return false;
}
auto init(uintptr_t handle, LPDIRECTINPUT8 context, bool xinputAvailable) -> bool {
auto initialize(uintptr handle, LPDIRECTINPUT8 context, bool xinputAvailable) -> bool {
if(!handle) return false;
this->handle = handle;
this->context = context;
this->xinputAvailable = xinputAvailable;
@ -94,7 +95,7 @@ struct InputJoypadDirectInput {
return true;
}
auto term() -> void {
auto terminate() -> void {
for(auto& jp : joypads) {
jp.device->Unacquire();
if(jp.effect) jp.effect->Release();

View File

@ -42,7 +42,7 @@ struct InputJoypadSDL {
}
}
auto init() -> bool {
auto initialize() -> bool {
SDL_InitSubSystem(SDL_INIT_JOYSTICK);
SDL_JoystickEventState(SDL_IGNORE);
@ -67,7 +67,7 @@ struct InputJoypadSDL {
return true;
}
auto term() -> void {
auto terminate() -> void {
for(auto& jp : joypads) {
SDL_JoystickClose(jp.handle);
}

View File

@ -103,7 +103,7 @@ struct InputJoypadXInput {
return false;
}
auto init() -> bool {
auto initialize() -> bool {
if(!libxinput) libxinput = LoadLibraryA("xinput1_3.dll");
if(!libxinput) return false;
@ -112,7 +112,7 @@ struct InputJoypadXInput {
XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetStateEx);
XInputSetState = (pXInputSetState)GetProcAddress(libxinput, oXInputSetState);
if(!XInputGetStateEx) XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetState);
if(!XInputGetStateEx || !XInputSetState) return term(), false;
if(!XInputGetStateEx || !XInputSetState) return terminate(), false;
//XInput supports a maximum of four controllers
//add all four to devices list now. If they are not connected, they will not show up in poll() results
@ -151,7 +151,7 @@ struct InputJoypadXInput {
return true;
}
auto term() -> void {
auto terminate() -> void {
if(!libxinput) return;
FreeLibrary(libxinput);

View File

@ -39,7 +39,7 @@ struct InputKeyboardRawInput {
devices.append(kb.hid);
}
auto init() -> bool {
auto initialize() -> bool {
rawinput.updateKeyboard = {&InputKeyboardRawInput::update, this};
//Pause sends 0x001d,4 + 0x0045,0; NumLock sends only 0x0045,0
@ -170,7 +170,7 @@ struct InputKeyboardRawInput {
return true;
}
auto term() -> void {
auto terminate() -> void {
rawinput.updateKeyboard.reset();
}
};

View File

@ -36,7 +36,7 @@ struct InputKeyboardXlib {
devices.append(hid);
}
auto init() -> bool {
auto initialize() -> bool {
display = XOpenDisplay(0);
keys.append({"Escape", XK_Escape});
@ -163,7 +163,7 @@ struct InputKeyboardXlib {
return true;
}
auto term() -> void {
auto terminate() -> void {
if(display) {
XCloseDisplay(display);
display = nullptr;

View File

@ -5,7 +5,7 @@ struct InputMouseRawInput {
Input& input;
InputMouseRawInput(Input& input) : input(input) {}
uintptr_t handle = 0;
uintptr handle = 0;
bool mouseAcquired = false;
struct Mouse {
@ -17,6 +17,17 @@ struct InputMouseRawInput {
bool buttons[5] = {0};
} ms;
auto acquired() -> bool {
if(mouseAcquired) {
SetFocus((HWND)handle);
SetCapture((HWND)handle);
RECT rc;
GetWindowRect((HWND)handle, &rc);
ClipCursor(&rc);
}
return GetCapture() == (HWND)handle;
}
auto acquire() -> bool {
if(!mouseAcquired) {
mouseAcquired = true;
@ -35,17 +46,6 @@ struct InputMouseRawInput {
return true;
}
auto acquired() -> bool {
if(mouseAcquired) {
SetFocus((HWND)handle);
SetCapture((HWND)handle);
RECT rc;
GetWindowRect((HWND)handle, &rc);
ClipCursor(&rc);
}
return GetCapture() == (HWND)handle;
}
auto update(RAWINPUT* input) -> void {
if((input->data.mouse.usFlags & 1) == MOUSE_MOVE_RELATIVE) {
ms.relativeX += input->data.mouse.lLastX;
@ -95,7 +95,8 @@ struct InputMouseRawInput {
devices.append(ms.hid);
}
auto init(uintptr_t handle) -> bool {
auto initialize(uintptr handle) -> bool {
if(!handle) return false;
this->handle = handle;
ms.hid->setID(2);
@ -114,7 +115,7 @@ struct InputMouseRawInput {
return true;
}
auto term() -> void {
auto terminate() -> void {
rawinput.updateMouse.reset();
release();
}

View File

@ -7,11 +7,11 @@ struct InputMouseXlib {
shared_pointer<HID::Mouse> hid{new HID::Mouse};
uintptr_t handle = 0;
uintptr handle = 0;
Display* display = nullptr;
Window rootWindow;
Cursor invisibleCursor;
Window rootWindow = 0;
Cursor invisibleCursor = 0;
unsigned screenWidth = 0;
unsigned screenHeight = 0;
@ -24,6 +24,10 @@ struct InputMouseXlib {
unsigned relativeY = 0;
} ms;
auto acquired() -> bool {
return ms.acquired;
}
auto acquire() -> bool {
if(acquired()) return true;
@ -53,10 +57,6 @@ struct InputMouseXlib {
return true;
}
auto acquired() -> bool {
return ms.acquired;
}
auto assign(unsigned groupID, unsigned inputID, int16_t value) -> void {
auto& group = hid->group(groupID);
if(group.input(inputID).value() == value) return;
@ -103,7 +103,10 @@ struct InputMouseXlib {
devices.append(hid);
}
auto init(uintptr_t handle) -> bool {
auto initialize(uintptr handle) -> bool {
terminate();
if(!handle) return false;
this->handle = handle;
display = XOpenDisplay(0);
rootWindow = DefaultRootWindow(display);
@ -143,10 +146,16 @@ struct InputMouseXlib {
return true;
}
auto term() -> void {
auto terminate() -> void {
release();
XFreeCursor(display, invisibleCursor);
XCloseDisplay(display);
if(invisibleCursor) {
XFreeCursor(display, invisibleCursor);
invisibleCursor = 0;
}
if(display) {
XCloseDisplay(display);
display = nullptr;
}
}
};

View File

@ -7,55 +7,36 @@
#include "joypad/sdl.cpp"
struct InputSDL : Input {
InputKeyboardXlib xlibKeyboard;
InputMouseXlib xlibMouse;
InputJoypadSDL sdl;
InputSDL() : xlibKeyboard(*this), xlibMouse(*this), sdl(*this) {}
~InputSDL() { term(); }
InputSDL() : _keyboard(*this), _mouse(*this), _joypad(*this) { initialize(); }
~InputSDL() { terminate(); }
struct Settings {
uintptr_t handle = 0;
} settings;
auto ready() -> bool { return _ready; }
auto cap(const string& name) -> bool {
if(name == Input::Handle) return true;
if(name == Input::KeyboardSupport) return true;
if(name == Input::MouseSupport) return true;
if(name == Input::JoypadSupport) return true;
return false;
}
auto context() -> uintptr { return _context; }
auto get(const string& name) -> any {
if(name == Input::Handle) return (uintptr_t)settings.handle;
return {};
}
auto set(const string& name, const any& value) -> bool {
if(name == Input::Handle && value.is<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 setContext(uintptr context) -> bool {
if(_context == context) return true;
_context = context;
return initialize();
}
auto acquired() -> bool {
return xlibMouse.acquired();
return _mouse.acquired();
}
auto acquire() -> bool {
return _mouse.acquire();
}
auto release() -> bool {
return _mouse.release();
}
auto poll() -> vector<shared_pointer<HID::Device>> {
vector<shared_pointer<HID::Device>> devices;
xlibKeyboard.poll(devices);
xlibMouse.poll(devices);
sdl.poll(devices);
_keyboard.poll(devices);
_mouse.poll(devices);
_joypad.poll(devices);
return devices;
}
@ -63,16 +44,26 @@ struct InputSDL : Input {
return false;
}
auto init() -> bool {
if(!xlibKeyboard.init()) return false;
if(!xlibMouse.init(settings.handle)) return false;
if(!sdl.init()) return false;
return true;
private:
auto initialize() -> bool {
terminate();
if(!_keyboard.initialize()) return false;
if(!_mouse.initialize(_context)) return false;
if(!_joypad.initialize()) return false;
return _ready = true;
}
auto term() -> void {
xlibKeyboard.term();
xlibMouse.term();
sdl.term();
auto terminate() -> void {
_ready = false;
_keyboard.terminate();
_mouse.terminate();
_joypad.terminate();
}
bool _ready = false;
uintptr _context = 0;
InputKeyboardXlib _keyboard;
InputMouseXlib _mouse;
InputJoypadSDL _joypad;
};

View File

@ -9,97 +9,93 @@
#include "joypad/directinput.cpp"
struct InputWindows : Input {
InputKeyboardRawInput rawinputKeyboard;
InputMouseRawInput rawinputMouse;
InputJoypadXInput xinput;
InputJoypadDirectInput directinput;
InputWindows() : rawinputKeyboard(*this), rawinputMouse(*this), xinput(*this), directinput(*this) {}
~InputWindows() { term(); }
InputWindows() : _keyboard(*this), _mouse(*this), _joypadXInput(*this), _joypadDirectInput(*this) { initialize(); }
~InputWindows() { terminate(); }
LPDIRECTINPUT8 directinputContext = nullptr;
auto ready() -> bool { return _ready; }
struct Settings {
uintptr_t handle = 0;
} settings;
auto context() -> uintptr { return _context; }
auto cap(const string& name) -> bool {
if(name == Input::Handle) return true;
if(name == Input::KeyboardSupport) return true;
if(name == Input::MouseSupport) return true;
if(name == Input::JoypadSupport) return true;
if(name == Input::JoypadRumbleSupport) return true;
return false;
}
auto get(const string& name) -> any {
if(name == Input::Handle) return (uintptr_t)settings.handle;
return {};
}
auto set(const string& name, const any& value) -> bool {
if(name == Input::Handle && value.is<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 setContext(uintptr context) -> bool {
if(_context == context) return true;
_context = context;
return initialize();
}
auto acquired() -> bool {
return rawinputMouse.acquired();
return _mouse.acquired();
}
auto acquire() -> bool {
return _mouse.acquire();
}
auto release() -> bool {
return _mouse.release();
}
auto poll() -> vector<shared_pointer<HID::Device>> {
vector<shared_pointer<HID::Device>> devices;
rawinputKeyboard.poll(devices);
rawinputMouse.poll(devices);
xinput.poll(devices);
directinput.poll(devices);
_keyboard.poll(devices);
_mouse.poll(devices);
_joypadXInput.poll(devices);
_joypadDirectInput.poll(devices);
return devices;
}
auto rumble(uint64_t id, bool enable) -> bool {
if(xinput.rumble(id, enable)) return true;
if(directinput.rumble(id, enable)) return true;
if(_joypadXInput.rumble(id, enable)) return true;
if(_joypadDirectInput.rumble(id, enable)) return true;
return false;
}
auto init() -> bool {
if(rawinput.initialized == false) {
private:
auto initialize() -> bool {
terminate();
if(!_context) return false;
if(!rawinput.initialized) {
rawinput.initialized = true;
rawinput.mutex = CreateMutex(NULL, FALSE, NULL);
CreateThread(NULL, 0, RawInputThreadProc, 0, 0, NULL);
rawinput.mutex = CreateMutex(nullptr, false, nullptr);
CreateThread(nullptr, 0, RawInputThreadProc, 0, 0, nullptr);
do {
Sleep(1);
WaitForSingleObject(rawinput.mutex, INFINITE);
ReleaseMutex(rawinput.mutex);
} while(rawinput.ready == false);
} while(!rawinput.ready);
}
DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&directinputContext, 0);
if(directinputContext == nullptr) return false;
DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&_directInputContext, 0);
if(!_directInputContext) return false;
if(rawinputKeyboard.init() == false) return false;
if(rawinputMouse.init(settings.handle) == false) return false;
bool xinputAvailable = xinput.init();
if(directinput.init(settings.handle, directinputContext, xinputAvailable) == false) return false;
return true;
if(!_keyboard.initialize()) return false;
if(!_mouse.initialize(_context)) return false;
bool xinputAvailable = _joypadXInput.initialize();
if(!_joypadDirectInput.initialize(_context, _directInputContext, xinputAvailable)) return false;
return _ready = true;
}
auto term() -> void {
rawinputKeyboard.term();
rawinputMouse.term();
xinput.term();
directinput.term();
auto terminate() -> void {
_ready = false;
if(directinputContext) { directinputContext->Release(); directinputContext = nullptr; }
_keyboard.terminate();
_mouse.terminate();
_joypadXInput.terminate();
_joypadDirectInput.terminate();
if(_directInputContext) {
_directInputContext->Release();
_directInputContext = nullptr;
}
}
bool _ready = false;
uintptr _context = 0;
InputKeyboardRawInput _keyboard;
InputMouseRawInput _mouse;
InputJoypadXInput _joypadXInput;
InputJoypadDirectInput _joypadDirectInput;
LPDIRECTINPUT8 _directInputContext = nullptr;
};

View File

@ -8,51 +8,25 @@
#include "mouse/xlib.cpp"
struct InputXlib : Input {
InputKeyboardXlib xlibKeyboard;
InputMouseXlib xlibMouse;
InputXlib() : xlibKeyboard(*this), xlibMouse(*this) {}
~InputXlib() { term(); }
InputXlib() : _keyboard(*this), _mouse(*this) { initialize(); }
~InputXlib() { terminate(); }
struct Settings {
uintptr_t handle = 0;
} settings;
auto cap(const string& name) -> bool {
if(name == Input::KeyboardSupport) return true;
if(name == Input::MouseSupport) return true;
return false;
}
auto get(const string& name) -> any {
if(name == Input::Handle) return (uintptr_t)settings.handle;
return {};
}
auto set(const string& name, const any& value) -> bool {
if(name == Input::Handle && value.is<uintptr_t>()) {
settings.handle = value.get<uintptr_t>();
return true;
}
return false;
auto acquired() -> bool {
return _mouse.acquired();
}
auto acquire() -> bool {
return xlibMouse.acquire();
return _mouse.acquire();
}
auto release() -> bool {
return xlibMouse.release();
}
auto acquired() -> bool {
return xlibMouse.acquired();
return _mouse.release();
}
auto poll() -> vector<shared_pointer<HID::Device>> {
vector<shared_pointer<HID::Device>> devices;
xlibKeyboard.poll(devices);
xlibMouse.poll(devices);
_keyboard.poll(devices);
_mouse.poll(devices);
return devices;
}
@ -60,14 +34,23 @@ struct InputXlib : Input {
return false;
}
auto init() -> bool {
if(!xlibKeyboard.init()) return false;
if(!xlibMouse.init(settings.handle)) return false;
return true;
private:
auto initialize() -> bool {
terminate();
if(!_keyboard.initialize()) return false;
if(!_mouse.initialize(_context)) return false;
return _ready = true;
}
auto term() -> void {
xlibKeyboard.term();
xlibMouse.term();
auto terminate() -> void {
_ready = false;
_keyboard.terminate();
_mouse.terminate();
}
bool _ready = false;
uintptr _context = 0;
InputKeyboardXlib _keyboard;
InputMouseXlib _mouse;
};

View File

@ -72,16 +72,6 @@ using namespace ruby;
namespace ruby {
const string Video::Exclusive = "Exclusive";
const string Video::Handle = "Handle";
const string Video::Synchronize = "Synchronize";
const string Video::Depth = "Depth";
const string Video::Filter = "Filter";
const string Video::Shader = "Shader";
const uint Video::FilterNearest = 0;
const uint Video::FilterLinear = 1;
auto Video::create(const string& driver) -> Video* {
if(!driver) return create(optimalDriver());
@ -272,13 +262,6 @@ auto Video::availableDrivers() -> string_vector {
namespace ruby {
const string Audio::Device = "Device";
const string Audio::Exclusive = "Exclusive";
const string Audio::Handle = "Handle";
const string Audio::Synchronize = "Synchronize";
const string Audio::Frequency = "Frequency";
const string Audio::Latency = "Latency";
auto Audio::create(const string& driver) -> Audio* {
if(!driver) return create(optimalDriver());
@ -453,12 +436,6 @@ auto Audio::availableDrivers() -> string_vector {
namespace ruby {
const string Input::Handle = "Handle";
const string Input::KeyboardSupport = "KeyboardSupport";
const string Input::MouseSupport = "MouseSupport";
const string Input::JoypadSupport = "JoypadSupport";
const string Input::JoypadRumbleSupport = "JoypadRumbleSupport";
auto Input::create(const string& driver) -> Input* {
if(!driver) return create(optimalDriver());

View File

@ -14,89 +14,101 @@
namespace ruby {
struct Video {
static const nall::string Exclusive;
static const nall::string Handle;
static const nall::string Synchronize;
static const nall::string Depth;
static const nall::string Filter;
static const nall::string Shader;
static const uint FilterNearest;
static const uint FilterLinear;
static auto create(const nall::string& driver = "") -> Video*;
static auto optimalDriver() -> nall::string;
static auto safestDriver() -> nall::string;
static auto availableDrivers() -> nall::string_vector;
struct Information {
};
virtual ~Video() = default;
virtual auto cap(const nall::string& name) -> bool { return false; }
virtual auto get(const nall::string& name) -> nall::any { return false; }
virtual auto set(const nall::string& name, const nall::any& value) -> bool { return false; }
virtual auto ready() -> bool { return true; }
virtual auto information() -> Information { return {}; }
virtual auto exclusive() -> bool { return false; }
virtual auto context() -> uintptr { return 0; }
virtual auto blocking() -> bool { return false; }
virtual auto depth() -> uint { return 24; }
virtual auto smooth() -> bool { return false; }
virtual auto shader() -> nall::string { return ""; }
virtual auto setExclusive(bool exclusive) -> bool { return false; }
virtual auto setContext(uintptr context) -> bool { return false; }
virtual auto setBlocking(bool blocking) -> bool { return false; }
virtual auto setDepth(uint depth) -> bool { return false; }
virtual auto setSmooth(bool smooth) -> bool { return false; }
virtual auto setShader(nall::string shader) -> bool { return false; }
virtual auto clear() -> void {}
virtual auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool { return false; }
virtual auto unlock() -> void {}
virtual auto clear() -> void {}
virtual auto refresh() -> void {}
virtual auto init() -> bool { return true; }
virtual auto term() -> void {}
virtual auto output() -> void {}
};
struct Audio {
static const nall::string Device;
static const nall::string Exclusive;
static const nall::string Handle;
static const nall::string Synchronize;
static const nall::string Frequency;
static const nall::string Latency;
static auto create(const nall::string& driver = "") -> Audio*;
static auto optimalDriver() -> nall::string;
static auto safestDriver() -> nall::string;
static auto availableDrivers() -> nall::string_vector;
struct Information {
nall::vector<nall::string> devices;
nall::vector<uint> frequencies;
nall::vector<uint> latencies;
nall::vector<uint> channels;
};
virtual ~Audio() = default;
virtual auto cap(const nall::string& name) -> bool { return false; }
virtual auto get(const nall::string& name) -> nall::any { return false; }
virtual auto set(const nall::string& name, const nall::any& value) -> bool { return false; }
virtual auto ready() -> bool { return true; }
virtual auto information() -> Information { return {{"None"}, {48000}, {0}, {2}}; }
virtual auto exclusive() -> bool { return false; }
virtual auto context() -> uintptr { return 0; }
virtual auto device() -> nall::string { return "None"; }
virtual auto blocking() -> bool { return false; }
virtual auto channels() -> uint { return 2; }
virtual auto frequency() -> uint { return 48000; }
virtual auto latency() -> uint { return 0; }
virtual auto setExclusive(bool exclusive) -> bool { return false; }
virtual auto setContext(uintptr context) -> bool { return false; }
virtual auto setDevice(nall::string device) -> bool { return false; }
virtual auto setBlocking(bool blocking) -> bool { return false; }
virtual auto setChannels(uint channels) -> bool { return false; }
virtual auto setFrequency(uint frequency) -> bool { return false; }
virtual auto setLatency(uint latency) -> bool { return false; }
virtual auto sample(int16_t left, int16_t right) -> void {}
virtual auto clear() -> void {}
virtual auto init() -> bool { return true; }
virtual auto term() -> void {}
virtual auto output(const double samples[]) -> void {}
};
struct Input {
static const nall::string Handle;
static const nall::string KeyboardSupport;
static const nall::string MouseSupport;
static const nall::string JoypadSupport;
static const nall::string JoypadRumbleSupport;
static auto create(const nall::string& driver = "") -> Input*;
static auto optimalDriver() -> nall::string;
static auto safestDriver() -> nall::string;
static auto availableDrivers() -> nall::string_vector;
struct Information {
};
virtual ~Input() = default;
virtual auto cap(const nall::string& name) -> bool { return false; }
virtual auto get(const nall::string& name) -> nall::any { return false; }
virtual auto set(const nall::string& name, const nall::any& value) -> bool { return false; }
virtual auto ready() -> bool { return true; }
virtual auto information() -> Information { return {}; }
virtual auto context() -> uintptr { return 0; }
virtual auto setContext(uintptr context) -> bool { return false; }
virtual auto acquired() -> bool { return false; }
virtual auto acquire() -> bool { return false; }
virtual auto release() -> bool { return false; }
virtual auto acquired() -> bool { return false; }
virtual auto poll() -> nall::vector<nall::shared_pointer<nall::HID::Device>> { return {}; }
virtual auto rumble(uint64_t id, bool enable) -> bool { return false; }
virtual auto init() -> bool { return true; }
virtual auto term() -> void {}
auto onChange(const nall::function<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 {
if(_onChange) _onChange(device, group, input, oldValue, newValue);

View File

@ -8,183 +8,182 @@ static LRESULT CALLBACK VideoDirect3D_WindowProcedure(HWND hwnd, UINT msg, WPARA
}
struct VideoDirect3D : Video {
VideoDirect3D() {
POINT point = {0, 0};
HMONITOR monitor = MonitorFromPoint(point, MONITOR_DEFAULTTOPRIMARY);
MONITORINFOEX information = {};
information.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(monitor, &information);
monitorWidth = information.rcMonitor.right - information.rcMonitor.left;
monitorHeight = information.rcMonitor.bottom - information.rcMonitor.top;
VideoDirect3D() { initialize(); }
~VideoDirect3D() { terminate(); }
WNDCLASS windowClass = {};
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
windowClass.hCursor = LoadCursor(0, IDC_ARROW);
windowClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
windowClass.hInstance = GetModuleHandle(0);
windowClass.lpfnWndProc = VideoDirect3D_WindowProcedure;
windowClass.lpszClassName = L"VideoDirect3D_Window";
windowClass.lpszMenuName = 0;
windowClass.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&windowClass);
auto ready() -> bool { return _ready; }
settings.exclusiveHandle = CreateWindowEx(WS_EX_TOPMOST, L"VideoDirect3D_Window", L"", WS_POPUP,
information.rcMonitor.left, information.rcMonitor.top, monitorWidth, monitorHeight,
nullptr, nullptr, GetModuleHandle(0), nullptr);
auto exclusive() -> bool { return _exclusive; }
auto context() -> uintptr { return _context; }
auto blocking() -> bool { return _blocking; }
auto smooth() -> bool { return _smooth; }
auto setExclusive(bool exclusive) -> bool {
if(_exclusive == exclusive) return true;
_exclusive = exclusive;
return initialize();
}
~VideoDirect3D() {
term();
DestroyWindow(settings.exclusiveHandle);
auto setContext(uintptr context) -> bool {
if(_context == context) return true;
_context = context;
return initialize();
}
LPDIRECT3D9 context = nullptr;
LPDIRECT3DDEVICE9 device = nullptr;
LPDIRECT3DVERTEXBUFFER9 vertexBuffer = nullptr;
D3DPRESENT_PARAMETERS presentation = {};
D3DCAPS9 capabilities = {};
LPDIRECT3DTEXTURE9 texture = nullptr;
LPDIRECT3DSURFACE9 surface = nullptr;
bool lost = true;
uint windowWidth;
uint windowHeight;
uint textureWidth;
uint textureHeight;
uint monitorWidth;
uint monitorHeight;
struct Vertex {
float x, y, z, rhw; //screen coordinates
float u, v; //texture coordinates
};
struct {
uint32_t textureUsage;
uint32_t texturePool;
uint32_t vertexUsage;
uint32_t vertexPool;
uint32_t filter;
} flags;
struct {
bool exclusive = false;
HWND handle = nullptr;
bool synchronize = false;
uint filter = Video::FilterLinear;
HWND exclusiveHandle = nullptr;
uint width;
uint height;
} settings;
auto cap(const string& name) -> bool {
if(name == Video::Exclusive) return true;
if(name == Video::Handle) return true;
if(name == Video::Synchronize) return true;
if(name == Video::Filter) return true;
if(name == Video::Shader) return false;
return false;
auto setBlocking(bool blocking) -> bool {
_blocking = blocking;
return true;
}
auto get(const string& name) -> any {
if(name == Video::Exclusive) return settings.exclusive;
if(name == Video::Handle) return (uintptr_t)settings.handle;
if(name == Video::Synchronize) return settings.synchronize;
if(name == Video::Filter) return settings.filter;
return {};
auto setSmooth(bool smooth) -> bool {
_smooth = smooth;
if(_ready) updateFilter();
return true;
}
auto set(const string& name, const any& value) -> bool {
if(name == Video::Exclusive && value.is<bool>()) {
settings.exclusive = value.get<bool>();
if(context) init();
return true;
auto clear() -> void {
if(_lost && !recover()) return;
D3DSURFACE_DESC surfaceDescription;
_texture->GetLevelDesc(0, &surfaceDescription);
_texture->GetSurfaceLevel(0, &_surface);
if(_surface) {
_device->ColorFill(_surface, 0, D3DCOLOR_XRGB(0x00, 0x00, 0x00));
_surface->Release();
_surface = nullptr;
}
if(name == Video::Handle && value.is<uintptr>()) {
settings.handle = (HWND)value.get<uintptr>();
return true;
//clear primary display and all backbuffers
for(uint n : range(3)) {
_device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0x00, 0x00, 0x00), 1.0f, 0);
_device->Present(0, 0, 0, 0);
}
if(name == Video::Synchronize && value.is<bool>()) {
settings.synchronize = value.get<bool>();
return true;
}
if(name == Video::Filter && value.is<uint>()) {
settings.filter = value.get<uint>();
if(context) updateFilter();
return true;
}
return false;
}
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
if(_lost && !recover()) return false;
if(width != _inputWidth || height != _inputHeight) {
resize(_inputWidth = width, _inputHeight = height);
}
D3DSURFACE_DESC surfaceDescription;
_texture->GetLevelDesc(0, &surfaceDescription);
_texture->GetSurfaceLevel(0, &_surface);
D3DLOCKED_RECT lockedRectangle;
_surface->LockRect(&lockedRectangle, 0, D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD);
pitch = lockedRectangle.Pitch;
return data = (uint32_t*)lockedRectangle.pBits;
}
auto unlock() -> void {
_surface->UnlockRect();
_surface->Release();
_surface = nullptr;
}
auto output() -> void {
if(_lost && !recover()) return;
RECT rectangle;
GetClientRect((HWND)_context, &rectangle);
//if output size changed, driver must be re-initialized.
//failure to do so causes scaling issues on some video drivers.
if(_windowWidth != rectangle.right || _windowHeight != rectangle.bottom) initialize();
_device->BeginScene();
uint x = 0, y = 0;
if(_exclusive) {
//center output in exclusive mode fullscreen window
x = (_monitorWidth - _windowWidth) / 2;
y = (_monitorHeight - _windowHeight) / 2;
}
setVertex(0, 0, _inputWidth, _inputHeight, _textureWidth, _textureHeight, x, y, _windowWidth, _windowHeight);
_device->SetTexture(0, _texture);
_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
_device->EndScene();
if(_blocking) {
D3DRASTER_STATUS status;
while(true) { //wait for a previous vblank to finish, if necessary
_device->GetRasterStatus(0, &status);
if(!status.InVBlank) break;
}
while(true) { //wait for next vblank to begin
_device->GetRasterStatus(0, &status);
if(status.InVBlank) break;
}
}
if(_device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) _lost = true;
}
private:
auto recover() -> bool {
if(!device) return false;
if(!_device) return false;
if(lost) {
if(vertexBuffer) { vertexBuffer->Release(); vertexBuffer = nullptr; }
if(surface) { surface->Release(); surface = nullptr; }
if(texture) { texture->Release(); texture = nullptr; }
if(device->Reset(&presentation) != D3D_OK) return false;
if(_lost) {
if(_vertexBuffer) { _vertexBuffer->Release(); _vertexBuffer = nullptr; }
if(_surface) { _surface->Release(); _surface = nullptr; }
if(_texture) { _texture->Release(); _texture = nullptr; }
if(_device->Reset(&_presentation) != D3D_OK) return false;
}
lost = false;
_lost = false;
device->SetDialogBoxMode(false);
_device->SetDialogBoxMode(false);
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
_device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
_device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
_device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
_device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
_device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
_device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
device->SetRenderState(D3DRS_LIGHTING, false);
device->SetRenderState(D3DRS_ZENABLE, false);
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
_device->SetRenderState(D3DRS_LIGHTING, false);
_device->SetRenderState(D3DRS_ZENABLE, false);
_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
_device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
device->SetVertexShader(nullptr);
device->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
_device->SetVertexShader(nullptr);
_device->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
device->CreateVertexBuffer(sizeof(Vertex) * 4, flags.vertexUsage, D3DFVF_XYZRHW | D3DFVF_TEX1,
(D3DPOOL)flags.vertexPool, &vertexBuffer, nullptr);
textureWidth = 0;
textureHeight = 0;
resize(settings.width = 256, settings.height = 256);
_device->CreateVertexBuffer(sizeof(Vertex) * 4, _vertexUsage, D3DFVF_XYZRHW | D3DFVF_TEX1,
(D3DPOOL)_vertexPool, &_vertexBuffer, nullptr);
_textureWidth = 0;
_textureHeight = 0;
resize(_inputWidth = 256, _inputHeight = 256);
updateFilter();
clear();
return true;
}
auto resize(uint width, uint height) -> void {
if(textureWidth >= width && textureHeight >= height) return;
if(_textureWidth >= width && _textureHeight >= height) return;
textureWidth = bit::round(max(width, textureWidth));
textureHeight = bit::round(max(height, textureHeight));
_textureWidth = bit::round(max(width, _textureWidth));
_textureHeight = bit::round(max(height, _textureHeight));
if(capabilities.MaxTextureWidth < textureWidth || capabilities.MaxTextureWidth < textureHeight) return;
if(_capabilities.MaxTextureWidth < _textureWidth || _capabilities.MaxTextureWidth < _textureHeight) return;
if(texture) texture->Release();
device->CreateTexture(textureWidth, textureHeight, 1, flags.textureUsage, D3DFMT_X8R8G8B8,
(D3DPOOL)flags.texturePool, &texture, nullptr);
if(_texture) _texture->Release();
_device->CreateTexture(_textureWidth, _textureHeight, 1, _textureUsage, D3DFMT_X8R8G8B8,
(D3DPOOL)_texturePool, &_texture, nullptr);
}
auto updateFilter() -> void {
if(!device) return;
if(lost && !recover()) return;
if(!_device) return;
if(_lost && !recover()) return;
flags.filter = settings.filter == Video::FilterNearest ? D3DTEXF_POINT : D3DTEXF_LINEAR;
device->SetSamplerState(0, D3DSAMP_MINFILTER, flags.filter);
device->SetSamplerState(0, D3DSAMP_MAGFILTER, flags.filter);
auto filter = !_smooth ? D3DTEXF_POINT : D3DTEXF_LINEAR;
_device->SetSamplerState(0, D3DSAMP_MINFILTER, filter);
_device->SetSamplerState(0, D3DSAMP_MAGFILTER, filter);
}
//(x,y) screen coordinates, in pixels
@ -212,164 +211,147 @@ struct VideoDirect3D : Video {
vertex[2].v = vertex[3].v = (double)(py + h) / rh;
LPDIRECT3DVERTEXBUFFER9* vertexPointer = nullptr;
vertexBuffer->Lock(0, sizeof(Vertex) * 4, (void**)&vertexPointer, 0);
_vertexBuffer->Lock(0, sizeof(Vertex) * 4, (void**)&vertexPointer, 0);
memory::copy(vertexPointer, vertex, sizeof(Vertex) * 4);
vertexBuffer->Unlock();
_vertexBuffer->Unlock();
device->SetStreamSource(0, vertexBuffer, 0, sizeof(Vertex));
_device->SetStreamSource(0, _vertexBuffer, 0, sizeof(Vertex));
}
auto clear() -> void {
if(lost && !recover()) return;
auto initialize() -> bool {
terminate();
if(!_context) return false;
D3DSURFACE_DESC surfaceDescription;
texture->GetLevelDesc(0, &surfaceDescription);
texture->GetSurfaceLevel(0, &surface);
POINT point = {0, 0};
HMONITOR monitor = MonitorFromPoint(point, MONITOR_DEFAULTTOPRIMARY);
MONITORINFOEX information = {};
information.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(monitor, &information);
_monitorWidth = information.rcMonitor.right - information.rcMonitor.left;
_monitorHeight = information.rcMonitor.bottom - information.rcMonitor.top;
if(surface) {
device->ColorFill(surface, 0, D3DCOLOR_XRGB(0x00, 0x00, 0x00));
surface->Release();
surface = nullptr;
}
WNDCLASS windowClass = {};
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
windowClass.hCursor = LoadCursor(0, IDC_ARROW);
windowClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
windowClass.hInstance = GetModuleHandle(0);
windowClass.lpfnWndProc = VideoDirect3D_WindowProcedure;
windowClass.lpszClassName = L"VideoDirect3D_Window";
windowClass.lpszMenuName = 0;
windowClass.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&windowClass);
//clear primary display and all backbuffers
for(uint n : range(3)) {
device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0x00, 0x00, 0x00), 1.0f, 0);
device->Present(0, 0, 0, 0);
}
}
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
if(lost && !recover()) return false;
if(width != settings.width || height != settings.height) {
resize(settings.width = width, settings.height = height);
}
D3DSURFACE_DESC surfaceDescription;
texture->GetLevelDesc(0, &surfaceDescription);
texture->GetSurfaceLevel(0, &surface);
D3DLOCKED_RECT lockedRectangle;
surface->LockRect(&lockedRectangle, 0, D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD);
pitch = lockedRectangle.Pitch;
return data = (uint32_t*)lockedRectangle.pBits;
}
auto unlock() -> void {
surface->UnlockRect();
surface->Release();
surface = nullptr;
}
auto refresh() -> void {
if(lost && !recover()) return;
_exclusiveContext = (uintptr)CreateWindowEx(WS_EX_TOPMOST, L"VideoDirect3D_Window", L"", WS_POPUP,
information.rcMonitor.left, information.rcMonitor.top, _monitorWidth, _monitorHeight,
nullptr, nullptr, GetModuleHandle(0), nullptr);
RECT rectangle;
GetClientRect(settings.handle, &rectangle);
GetClientRect((HWND)_context, &rectangle);
_windowWidth = rectangle.right;
_windowHeight = rectangle.bottom;
//if output size changed, driver must be re-initialized.
//failure to do so causes scaling issues on some video drivers.
if(windowWidth != rectangle.right || windowHeight != rectangle.bottom) init();
_instance = Direct3DCreate9(D3D_SDK_VERSION);
if(!_instance) return false;
device->BeginScene();
uint x = 0, y = 0;
if(settings.exclusive) {
//center output in exclusive mode fullscreen window
x = (monitorWidth - windowWidth) / 2;
y = (monitorHeight - windowHeight) / 2;
}
setVertex(0, 0, settings.width, settings.height, textureWidth, textureHeight, x, y, windowWidth, windowHeight);
device->SetTexture(0, texture);
device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
device->EndScene();
memory::fill(&_presentation, sizeof(_presentation));
_presentation.Flags = D3DPRESENTFLAG_VIDEO;
_presentation.SwapEffect = D3DSWAPEFFECT_DISCARD;
_presentation.BackBufferCount = 1;
_presentation.MultiSampleType = D3DMULTISAMPLE_NONE;
_presentation.MultiSampleQuality = 0;
_presentation.EnableAutoDepthStencil = false;
_presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN;
_presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
if(settings.synchronize) {
D3DRASTER_STATUS status;
while(true) { //wait for a previous vblank to finish, if necessary
device->GetRasterStatus(0, &status);
if(!status.InVBlank) break;
}
while(true) { //wait for next vblank to begin
device->GetRasterStatus(0, &status);
if(status.InVBlank) break;
}
}
if(!_exclusive) {
_presentation.hDeviceWindow = (HWND)_context;
_presentation.Windowed = true;
_presentation.BackBufferFormat = D3DFMT_UNKNOWN;
_presentation.BackBufferWidth = 0;
_presentation.BackBufferHeight = 0;
if(device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) lost = true;
}
auto init() -> bool {
term();
RECT rectangle;
GetClientRect(settings.handle, &rectangle);
windowWidth = rectangle.right;
windowHeight = rectangle.bottom;
context = Direct3DCreate9(D3D_SDK_VERSION);
if(!context) return false;
memory::fill(&presentation, sizeof(presentation));
presentation.Flags = D3DPRESENTFLAG_VIDEO;
presentation.SwapEffect = D3DSWAPEFFECT_DISCARD;
presentation.BackBufferCount = 1;
presentation.MultiSampleType = D3DMULTISAMPLE_NONE;
presentation.MultiSampleQuality = 0;
presentation.EnableAutoDepthStencil = false;
presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN;
presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
if(!settings.exclusive) {
presentation.hDeviceWindow = settings.handle;
presentation.Windowed = true;
presentation.BackBufferFormat = D3DFMT_UNKNOWN;
presentation.BackBufferWidth = 0;
presentation.BackBufferHeight = 0;
ShowWindow(settings.exclusiveHandle, SW_HIDE);
if(context->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, settings.handle,
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) {
ShowWindow((HWND)_exclusiveContext, SW_HIDE);
if(_instance->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, (HWND)_context,
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &_presentation, &_device) != D3D_OK) {
return false;
}
} else {
presentation.hDeviceWindow = settings.exclusiveHandle;
presentation.Windowed = false;
presentation.BackBufferFormat = D3DFMT_X8R8G8B8;
presentation.BackBufferWidth = monitorWidth;
presentation.BackBufferHeight = monitorHeight;
presentation.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
_presentation.hDeviceWindow = (HWND)_exclusiveContext;
_presentation.Windowed = false;
_presentation.BackBufferFormat = D3DFMT_X8R8G8B8;
_presentation.BackBufferWidth = _monitorWidth;
_presentation.BackBufferHeight = _monitorHeight;
_presentation.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
ShowWindow(settings.exclusiveHandle, SW_SHOWNORMAL);
if(context->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, settings.exclusiveHandle,
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) {
ShowWindow((HWND)_exclusiveContext, SW_SHOWNORMAL);
if(_instance->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, (HWND)_exclusiveContext,
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &_presentation, &_device) != D3D_OK) {
return false;
}
}
device->GetDeviceCaps(&capabilities);
_device->GetDeviceCaps(&_capabilities);
if(capabilities.Caps2 & D3DCAPS2_DYNAMICTEXTURES) {
flags.textureUsage = D3DUSAGE_DYNAMIC;
flags.texturePool = D3DPOOL_DEFAULT;
flags.vertexUsage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC;
flags.vertexPool = D3DPOOL_DEFAULT;
if(_capabilities.Caps2 & D3DCAPS2_DYNAMICTEXTURES) {
_textureUsage = D3DUSAGE_DYNAMIC;
_texturePool = D3DPOOL_DEFAULT;
_vertexUsage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC;
_vertexPool = D3DPOOL_DEFAULT;
} else {
flags.textureUsage = 0;
flags.texturePool = D3DPOOL_MANAGED;
flags.vertexUsage = D3DUSAGE_WRITEONLY;
flags.vertexPool = D3DPOOL_MANAGED;
_textureUsage = 0;
_texturePool = D3DPOOL_MANAGED;
_vertexUsage = D3DUSAGE_WRITEONLY;
_vertexPool = D3DPOOL_MANAGED;
}
lost = false;
return recover();
_lost = false;
return _ready = recover();
}
auto term() -> void {
if(vertexBuffer) { vertexBuffer->Release(); vertexBuffer = nullptr; }
if(surface) { surface->Release(); surface = nullptr; }
if(texture) { texture->Release(); texture = nullptr; }
if(device) { device->Release(); device = nullptr; }
if(context) { context->Release(); context = nullptr; }
auto terminate() -> void {
if(_vertexBuffer) { _vertexBuffer->Release(); _vertexBuffer = nullptr; }
if(_surface) { _surface->Release(); _surface = nullptr; }
if(_texture) { _texture->Release(); _texture = nullptr; }
if(_device) { _device->Release(); _device = nullptr; }
if(_instance) { _instance->Release(); _instance = nullptr; }
if(_exclusiveContext) { DestroyWindow((HWND)_exclusiveContext); _exclusiveContext = 0; }
}
struct Vertex {
float x, y, z, rhw; //screen coordinates
float u, v; //texture coordinates
};
bool _exclusive = false;
bool _ready = false;
uintptr _context = 0;
bool _blocking = false;
bool _smooth = true;
uintptr _exclusiveContext = 0;
LPDIRECT3D9 _instance = nullptr;
LPDIRECT3DDEVICE9 _device = nullptr;
LPDIRECT3DVERTEXBUFFER9 _vertexBuffer = nullptr;
D3DPRESENT_PARAMETERS _presentation = {};
D3DCAPS9 _capabilities = {};
LPDIRECT3DTEXTURE9 _texture = nullptr;
LPDIRECT3DSURFACE9 _surface = nullptr;
bool _lost = true;
uint _windowWidth;
uint _windowHeight;
uint _textureWidth;
uint _textureHeight;
uint _monitorWidth;
uint _monitorHeight;
uint _inputWidth;
uint _inputHeight;
uint32_t _textureUsage;
uint32_t _texturePool;
uint32_t _vertexUsage;
uint32_t _vertexPool;
};

View File

@ -9,64 +9,35 @@
#include <X11/extensions/XShm.h>
struct VideoXShm : Video {
~VideoXShm() { term(); }
VideoXShm() { initialize(); }
~VideoXShm() { terminate(); }
struct Device {
Display* display = nullptr;
int screen = 0;
int depth = 0;
Visual* visual = nullptr;
Window window = 0;
auto ready() -> bool { return _ready; }
XShmSegmentInfo shmInfo;
XImage* image = nullptr;
uint32_t* buffer = nullptr;
uint width = 0;
uint height = 0;
} device;
auto context() -> uintptr { return _context; }
auto smooth() -> bool { return _smooth; }
struct Settings {
uintptr_t handle = 0;
uint filter = Video::FilterLinear;
uint32_t* buffer = nullptr;
uint width = 0;
uint height = 0;
} settings;
auto cap(const string& name) -> bool {
if(name == Video::Handle) return true;
if(name == Video::Filter) return true;
return false;
auto setContext(uintptr context) -> bool {
if(_context == context) return true;
_context = context;
return initialize();
}
auto get(const string& name) -> any {
if(name == Video::Handle) return settings.handle;
if(name == Video::Filter) return settings.filter;
return {};
}
auto set(const string& name, const any& value) -> bool {
if(name == Video::Handle && value.is<uintptr_t>()) {
settings.handle = value.get<uintptr_t>();
return true;
}
if(name == Video::Filter && value.is<uint>()) {
settings.filter = value.get<uint>();
return true;
}
return false;
auto setSmooth(bool smooth) -> bool {
_smooth = smooth;
return true;
}
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
if(!settings.buffer || settings.width != width || settings.height != height) {
if(settings.buffer) delete[] settings.buffer;
settings.width = width, settings.height = height;
settings.buffer = new uint32_t[width * height + 16]; //+16 is padding for linear interpolation
if(!_inputBuffer || _inputWidth != width || _inputHeight != height) {
if(_inputBuffer) delete[] _inputBuffer;
_inputWidth = width;
_inputHeight = height;
_inputBuffer = new uint32_t[width * height + 16]; //+16 is padding for linear interpolation
}
data = settings.buffer;
pitch = settings.width * sizeof(uint32_t);
data = _inputBuffer;
pitch = _inputWidth * sizeof(uint32_t);
return true;
}
@ -74,124 +45,124 @@ struct VideoXShm : Video {
}
auto clear() -> void {
if(!settings.buffer) return;
uint32_t* dp = settings.buffer;
uint length = settings.width * settings.height;
if(!_ready) return;
auto dp = _inputBuffer;
uint length = _inputWidth * _inputHeight;
while(length--) *dp++ = 255u << 24;
refresh();
output();
}
auto refresh() -> void {
if(!settings.buffer) return;
auto output() -> void {
if(!_ready) return;
size();
float xratio = (float)settings.width / (float)device.width;
float yratio = (float)settings.height / (float)device.height;
float xratio = (float)_inputWidth / (float)_outputWidth;
float yratio = (float)_inputHeight / (float)_outputHeight;
#pragma omp parallel for
for(uint y = 0; y < device.height; y++) {
for(uint y = 0; y < _outputHeight; y++) {
float ystep = y * yratio;
float xstep = 0;
uint32_t* sp = settings.buffer + (uint)ystep * settings.width;
uint32_t* dp = device.buffer + y * device.width;
uint32_t* sp = _inputBuffer + (uint)ystep * _inputWidth;
uint32_t* dp = _outputBuffer + y * _outputWidth;
if(settings.filter == Video::FilterNearest) {
for(uint x = 0; x < device.width; x++) {
if(!_smooth) {
for(uint x = 0; x < _outputWidth; x++) {
*dp++ = 255u << 24 | sp[(uint)xstep];
xstep += xratio;
}
} else { //settings.filter == Video::FilterLinear
for(uint x = 0; x < device.width; x++) {
} else {
for(uint x = 0; x < _outputWidth; x++) {
*dp++ = 255u << 24 | interpolate(xstep - (uint)xstep, sp[(uint)xstep], sp[(uint)xstep + 1]);
xstep += xratio;
}
}
}
GC gc = XCreateGC(device.display, device.window, 0, 0);
XShmPutImage(
device.display, device.window, gc, device.image,
0, 0, 0, 0, device.width, device.height, False
);
XFreeGC(device.display, gc);
XFlush(device.display);
GC gc = XCreateGC(_display, _window, 0, 0);
XShmPutImage(_display, _window, gc, _image, 0, 0, 0, 0, _outputWidth, _outputHeight, False);
XFreeGC(_display, gc);
XFlush(_display);
}
auto init() -> bool {
device.display = XOpenDisplay(0);
device.screen = DefaultScreen(device.display);
private:
auto initialize() -> bool {
terminate();
if(!_context) return false;
_display = XOpenDisplay(0);
_screen = DefaultScreen(_display);
XWindowAttributes getAttributes;
XGetWindowAttributes(device.display, (Window)settings.handle, &getAttributes);
device.depth = getAttributes.depth;
device.visual = getAttributes.visual;
XGetWindowAttributes(_display, (Window)_context, &getAttributes);
_depth = getAttributes.depth;
_visual = getAttributes.visual;
//driver only supports 32-bit pixels
//note that even on 15-bit and 16-bit displays, the window visual's depth should be 32
if(device.depth < 24 || device.depth > 32) {
if(_depth < 24 || _depth > 32) {
free();
return false;
}
XSetWindowAttributes setAttributes = {0};
setAttributes.border_pixel = 0;
device.window = XCreateWindow(device.display, (Window)settings.handle,
_window = XCreateWindow(_display, (Window)_context,
0, 0, 256, 256, 0,
getAttributes.depth, InputOutput, getAttributes.visual,
CWBorderPixel, &setAttributes
);
XSetWindowBackground(device.display, device.window, 0);
XMapWindow(device.display, device.window);
XFlush(device.display);
XSetWindowBackground(_display, _window, 0);
XMapWindow(_display, _window);
XFlush(_display);
while(XPending(device.display)) {
while(XPending(_display)) {
XEvent event;
XNextEvent(device.display, &event);
XNextEvent(_display, &event);
}
if(!size()) return false;
return true;
return _ready = true;
}
auto term() -> void {
auto terminate() -> void {
free();
if(device.display) {
XCloseDisplay(device.display);
device.display = nullptr;
if(_display) {
XCloseDisplay(_display);
_display = nullptr;
}
}
private:
auto size() -> bool {
XWindowAttributes windowAttributes;
XGetWindowAttributes(device.display, settings.handle, &windowAttributes);
XGetWindowAttributes(_display, (Window)_context, &windowAttributes);
if(device.buffer && device.width == windowAttributes.width && device.height == windowAttributes.height) return true;
device.width = windowAttributes.width, device.height = windowAttributes.height;
XResizeWindow(device.display, device.window, device.width, device.height);
if(_outputBuffer && _outputWidth == windowAttributes.width && _outputHeight == windowAttributes.height) return true;
_outputWidth = windowAttributes.width;
_outputHeight = windowAttributes.height;
XResizeWindow(_display, _window, _outputWidth, _outputHeight);
free();
device.shmInfo.shmid = shmget(IPC_PRIVATE, device.width * device.height * sizeof(uint32_t), IPC_CREAT | 0777);
if(device.shmInfo.shmid < 0) return false;
_shmInfo.shmid = shmget(IPC_PRIVATE, _outputWidth * _outputHeight * sizeof(uint32_t), IPC_CREAT | 0777);
if(_shmInfo.shmid < 0) return false;
device.shmInfo.shmaddr = (char*)shmat(device.shmInfo.shmid, 0, 0);
device.shmInfo.readOnly = False;
XShmAttach(device.display, &device.shmInfo);
device.buffer = (uint32_t*)device.shmInfo.shmaddr;
device.image = XShmCreateImage(device.display, device.visual, device.depth,
ZPixmap, device.shmInfo.shmaddr, &device.shmInfo, device.width, device.height
);
_shmInfo.shmaddr = (char*)shmat(_shmInfo.shmid, 0, 0);
_shmInfo.readOnly = False;
XShmAttach(_display, &_shmInfo);
_outputBuffer = (uint32_t*)_shmInfo.shmaddr;
_image = XShmCreateImage(_display, _visual, _depth, ZPixmap, _shmInfo.shmaddr, &_shmInfo, _outputWidth, _outputHeight);
return true;
}
auto free() -> void {
if(!device.buffer) return;
device.buffer = nullptr;
XShmDetach(device.display, &device.shmInfo);
XDestroyImage(device.image);
shmdt(device.shmInfo.shmaddr);
shmctl(device.shmInfo.shmid, IPC_RMID, 0);
if(_outputBuffer) {
_outputBuffer = nullptr;
XShmDetach(_display, &_shmInfo);
XDestroyImage(_image);
shmdt(_shmInfo.shmaddr);
shmctl(_shmInfo.shmid, IPC_RMID, 0);
}
}
alwaysinline auto interpolate(float mu, uint32_t a, uint32_t b) -> uint32_t {
@ -202,4 +173,25 @@ private:
uint8_t cb = ab * (1.0 - mu) + bb * mu;
return cr << 16 | cg << 8 | cb << 0;
}
bool _ready = false;
uintptr _context = 0;
bool _smooth = true;
uint32_t* _inputBuffer = nullptr;
uint _inputWidth = 0;
uint _inputHeight = 0;
Display* _display = nullptr;
int _screen = 0;
int _depth = 0;
Visual* _visual = nullptr;
Window _window = 0;
XShmSegmentInfo _shmInfo;
XImage* _image = nullptr;
uint32_t* _outputBuffer = nullptr;
uint _outputWidth = 0;
uint _outputHeight = 0;
};