mirror of https://github.com/bsnes-emu/bsnes.git
Update to 20180808 release.
byuu says: This release fixes the XAudio 2.1 and WASAPI drivers on Windows, and extends XAudio to support device selection (eg headphones, speakers, monitor, etc.) It also adds DRC to XAudio, however it's not currently working. The code is courtesy of Talarubi, I just botched it somewhere upon porting it to the newer version of ruby.
This commit is contained in:
parent
93a6a1ce7e
commit
1e4affe5f9
|
@ -13,7 +13,7 @@ using namespace nall;
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const string Name = "higan";
|
static const string Name = "higan";
|
||||||
static const string Version = "106.57";
|
static const string Version = "106.58";
|
||||||
static const string Author = "byuu";
|
static const string Author = "byuu";
|
||||||
static const string License = "GPLv3";
|
static const string License = "GPLv3";
|
||||||
static const string Website = "https://byuu.org/";
|
static const string Website = "https://byuu.org/";
|
||||||
|
|
|
@ -12,16 +12,17 @@ struct queue {
|
||||||
queue() = default;
|
queue() = default;
|
||||||
queue(const queue& source) { operator=(source); }
|
queue(const queue& source) { operator=(source); }
|
||||||
queue(queue&& source) { operator=(move(source)); }
|
queue(queue&& source) { operator=(move(source)); }
|
||||||
|
~queue() { reset(); }
|
||||||
|
|
||||||
auto operator=(const queue& source) -> queue& {
|
auto operator=(const queue& source) -> queue& {
|
||||||
if(this == &source) return *this;
|
if(this == &source) return *this;
|
||||||
reset();
|
delete[] _data;
|
||||||
|
_data = new T[source._capacity];
|
||||||
_capacity = source._capacity;
|
_capacity = source._capacity;
|
||||||
_size = source._size;
|
_size = source._size;
|
||||||
_data = new T[_capacity];
|
|
||||||
for(uint n : range(_capacity)) _data[n] = source._data[n];
|
|
||||||
_read = source._read;
|
_read = source._read;
|
||||||
_write = source._write;
|
_write = source._write;
|
||||||
|
for(uint n : range(_capacity)) _data[n] = source._data[n];
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,10 +38,6 @@ struct queue {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
~queue() {
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename U = T> auto capacity() const -> uint { return _capacity * sizeof(T) / sizeof(U); }
|
template<typename U = T> auto capacity() const -> uint { return _capacity * sizeof(T) / sizeof(U); }
|
||||||
template<typename U = T> auto size() const -> uint { return _size * sizeof(T) / sizeof(U); }
|
template<typename U = T> auto size() const -> uint { return _size * sizeof(T) / sizeof(U); }
|
||||||
auto empty() const -> bool { return _size == 0; }
|
auto empty() const -> bool { return _size == 0; }
|
||||||
|
@ -62,9 +59,12 @@ struct queue {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto resize(uint capacity, const T& value = {}) -> void {
|
auto resize(uint capacity, const T& value = {}) -> void {
|
||||||
reset();
|
delete[] _data;
|
||||||
|
_data = new T[capacity];
|
||||||
_capacity = capacity;
|
_capacity = capacity;
|
||||||
_data = new T[_capacity];
|
_size = 0;
|
||||||
|
_read = 0;
|
||||||
|
_write = 0;
|
||||||
for(uint n : range(_capacity)) _data[n] = value;
|
for(uint n : range(_capacity)) _data[n] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,13 @@ struct queue {
|
||||||
_write = 0;
|
_write = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto fill(const T& value = {}) -> void {
|
||||||
|
_size = 0;
|
||||||
|
_read = 0;
|
||||||
|
_write = 0;
|
||||||
|
for(uint n : range(_capacity)) _data[n] = value;
|
||||||
|
}
|
||||||
|
|
||||||
auto read() -> T {
|
auto read() -> T {
|
||||||
T value = _data[_read++];
|
T value = _data[_read++];
|
||||||
if(_read >= _capacity) _read = 0;
|
if(_read >= _capacity) _read = 0;
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_PULSEAUDIOSIMPLE)
|
#if defined(AUDIO_PULSEAUDIOSIMPLE)
|
||||||
#include <ruby/audio/pulseaudiosimple.cpp>
|
#include <ruby/audio/pulseaudio-simple.cpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_WASAPI)
|
#if defined(AUDIO_WASAPI)
|
||||||
|
@ -151,7 +151,7 @@ auto Audio::create(string driver) -> bool {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_DIRECTSOUND)
|
#if defined(AUDIO_DIRECTSOUND)
|
||||||
if(driver == "DirectSound") self.instance = new AudioDirectSound(*this);
|
if(driver == "DirectSound 7.0") self.instance = new AudioDirectSound(*this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_OPENAL)
|
#if defined(AUDIO_OPENAL)
|
||||||
|
@ -167,7 +167,7 @@ auto Audio::create(string driver) -> bool {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_PULSEAUDIOSIMPLE)
|
#if defined(AUDIO_PULSEAUDIOSIMPLE)
|
||||||
if(driver == "PulseAudioSimple") self.instance = new AudioPulseAudioSimple(*this);
|
if(driver == "PulseAudio Simple") self.instance = new AudioPulseAudioSimple(*this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_WASAPI)
|
#if defined(AUDIO_WASAPI)
|
||||||
|
@ -175,7 +175,7 @@ auto Audio::create(string driver) -> bool {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_XAUDIO2)
|
#if defined(AUDIO_XAUDIO2)
|
||||||
if(driver == "XAudio2") self.instance = new AudioXAudio2(*this);
|
if(driver == "XAudio 2.1") self.instance = new AudioXAudio2(*this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if(!self.instance) self.instance = new AudioDriver(*this);
|
if(!self.instance) self.instance = new AudioDriver(*this);
|
||||||
|
@ -195,11 +195,11 @@ auto Audio::hasDrivers() -> vector<string> {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_XAUDIO2)
|
#if defined(AUDIO_XAUDIO2)
|
||||||
"XAudio2",
|
"XAudio 2.1",
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_DIRECTSOUND)
|
#if defined(AUDIO_DIRECTSOUND)
|
||||||
"DirectSound",
|
"DirectSound 7.0",
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_ALSA)
|
#if defined(AUDIO_ALSA)
|
||||||
|
@ -219,7 +219,7 @@ auto Audio::hasDrivers() -> vector<string> {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_PULSEAUDIOSIMPLE)
|
#if defined(AUDIO_PULSEAUDIOSIMPLE)
|
||||||
"PulseAudioSimple",
|
"PulseAudio Simple",
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_AO)
|
#if defined(AUDIO_AO)
|
||||||
|
@ -235,9 +235,9 @@ auto Audio::optimalDriver() -> string {
|
||||||
#elif defined(AUDIO_WASAPI)
|
#elif defined(AUDIO_WASAPI)
|
||||||
return "WASAPI";
|
return "WASAPI";
|
||||||
#elif defined(AUDIO_XAUDIO2)
|
#elif defined(AUDIO_XAUDIO2)
|
||||||
return "XAudio2";
|
return "XAudio 2.1";
|
||||||
#elif defined(AUDIO_DIRECTSOUND)
|
#elif defined(AUDIO_DIRECTSOUND)
|
||||||
return "DirectSound";
|
return "DirectSound 7.0";
|
||||||
#elif defined(AUDIO_ALSA)
|
#elif defined(AUDIO_ALSA)
|
||||||
return "ALSA";
|
return "ALSA";
|
||||||
#elif defined(AUDIO_OSS)
|
#elif defined(AUDIO_OSS)
|
||||||
|
@ -247,7 +247,7 @@ auto Audio::optimalDriver() -> string {
|
||||||
#elif defined(AUDIO_PULSEAUDIO)
|
#elif defined(AUDIO_PULSEAUDIO)
|
||||||
return "PulseAudio";
|
return "PulseAudio";
|
||||||
#elif defined(AUDIO_PULSEAUDIOSIMPLE)
|
#elif defined(AUDIO_PULSEAUDIOSIMPLE)
|
||||||
return "PulseAudioSimple";
|
return "PulseAudio Simple";
|
||||||
#elif defined(AUDIO_AO)
|
#elif defined(AUDIO_AO)
|
||||||
return "libao";
|
return "libao";
|
||||||
#else
|
#else
|
||||||
|
@ -257,11 +257,11 @@ auto Audio::optimalDriver() -> string {
|
||||||
|
|
||||||
auto Audio::safestDriver() -> string {
|
auto Audio::safestDriver() -> string {
|
||||||
#if defined(AUDIO_DIRECTSOUND)
|
#if defined(AUDIO_DIRECTSOUND)
|
||||||
return "DirectSound";
|
return "DirectSound 7.0";
|
||||||
#elif defined(AUDIO_WASAPI)
|
#elif defined(AUDIO_WASAPI)
|
||||||
return "WASAPI";
|
return "WASAPI";
|
||||||
#elif defined(AUDIO_XAUDIO2)
|
#elif defined(AUDIO_XAUDIO2)
|
||||||
return "XAudio2";
|
return "XAudio 2.1";
|
||||||
#elif defined(AUDIO_ALSA)
|
#elif defined(AUDIO_ALSA)
|
||||||
return "ALSA";
|
return "ALSA";
|
||||||
#elif defined(AUDIO_OSS)
|
#elif defined(AUDIO_OSS)
|
||||||
|
@ -271,7 +271,7 @@ auto Audio::safestDriver() -> string {
|
||||||
#elif defined(AUDIO_PULSEAUDIO)
|
#elif defined(AUDIO_PULSEAUDIO)
|
||||||
return "PulseAudio";
|
return "PulseAudio";
|
||||||
#elif defined(AUDIO_PULSEAUDIOSIMPLE)
|
#elif defined(AUDIO_PULSEAUDIOSIMPLE)
|
||||||
return "PulseAudioSimple";
|
return "PulseAudio Simple";
|
||||||
#elif defined(AUDIO_AO)
|
#elif defined(AUDIO_AO)
|
||||||
return "libao";
|
return "libao";
|
||||||
#elif defined(AUDIO_ASIO)
|
#elif defined(AUDIO_ASIO)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
struct Audio;
|
struct Audio;
|
||||||
|
|
||||||
struct AudioDriver {
|
struct AudioDriver {
|
||||||
|
enum class Format : uint { none, int16, int32, float32, float64 };
|
||||||
|
|
||||||
AudioDriver(Audio& super) : super(super) {}
|
AudioDriver(Audio& super) : super(super) {}
|
||||||
virtual ~AudioDriver() = default;
|
virtual ~AudioDriver() = default;
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ struct AudioDirectSound : AudioDriver {
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto driver() -> string override { return "DirectSound"; }
|
auto driver() -> string override { return "DirectSound 7.0"; }
|
||||||
auto ready() -> bool override { return _ready; }
|
auto ready() -> bool override { return _ready; }
|
||||||
|
|
||||||
auto hasBlocking() -> bool override { return true; }
|
auto hasBlocking() -> bool override { return true; }
|
||||||
|
|
|
@ -12,7 +12,7 @@ struct AudioPulseAudioSimple : AudioDriver {
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto driver() -> string override { return "PulseAudioSimple"; }
|
auto driver() -> string override { return "PulseAudio Simple"; }
|
||||||
auto ready() -> bool override { return _ready; }
|
auto ready() -> bool override { return _ready; }
|
||||||
|
|
||||||
auto hasFrequencies() -> vector<uint> override {
|
auto hasFrequencies() -> vector<uint> override {
|
|
@ -8,19 +8,17 @@
|
||||||
|
|
||||||
struct AudioWASAPI : AudioDriver {
|
struct AudioWASAPI : AudioDriver {
|
||||||
AudioWASAPI& self = *this;
|
AudioWASAPI& self = *this;
|
||||||
AudioWASAPI(Audio& super) : AudioDriver(super) { enumerate(); }
|
AudioWASAPI(Audio& super) : AudioDriver(super) { construct(); }
|
||||||
~AudioWASAPI() { terminate(); }
|
~AudioWASAPI() { destruct(); }
|
||||||
|
|
||||||
auto create() -> bool override {
|
auto create() -> bool override {
|
||||||
IMMDevice* defaultDevice = nullptr;
|
super.setExclusive(false);
|
||||||
if(self.enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice) != S_OK) return false;
|
super.setDevice(hasDevices().first());
|
||||||
for(auto& device : self.devices) {
|
super.setBlocking(false);
|
||||||
if(device.device != defaultDevice) continue;
|
super.setChannels(2);
|
||||||
super.setDevice(device.name);
|
super.setFrequency(48000);
|
||||||
super.setLatency(40);
|
super.setLatency(40);
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto driver() -> string override { return "WASAPI"; }
|
auto driver() -> string override { return "WASAPI"; }
|
||||||
|
@ -77,41 +75,83 @@ struct AudioWASAPI : AudioDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto enumerate() -> bool {
|
struct Device {
|
||||||
|
string id;
|
||||||
|
string name;
|
||||||
|
};
|
||||||
|
vector<Device> devices;
|
||||||
|
|
||||||
|
auto construct() -> bool {
|
||||||
if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&self.enumerator) != S_OK) return false;
|
if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&self.enumerator) != S_OK) return false;
|
||||||
|
|
||||||
|
IMMDevice* defaultDeviceContext = nullptr;
|
||||||
|
if(self.enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDeviceContext) != S_OK) return false;
|
||||||
|
|
||||||
|
Device defaultDevice;
|
||||||
|
LPWSTR defaultDeviceString = nullptr;
|
||||||
|
defaultDeviceContext->GetId(&defaultDeviceString);
|
||||||
|
defaultDevice.id = (const char*)utf8_t(defaultDeviceString);
|
||||||
|
CoTaskMemFree(defaultDeviceString);
|
||||||
|
|
||||||
IMMDeviceCollection* deviceCollection = nullptr;
|
IMMDeviceCollection* deviceCollection = nullptr;
|
||||||
if(self.enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection) != S_OK) return false;
|
if(self.enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection) != S_OK) return false;
|
||||||
|
|
||||||
uint deviceCount = 0;
|
uint deviceCount = 0;
|
||||||
if(deviceCollection->GetCount(&deviceCount) != S_OK) return false;
|
if(deviceCollection->GetCount(&deviceCount) != S_OK) return false;
|
||||||
|
|
||||||
for(uint deviceIndex : range(deviceCount)) {
|
for(uint deviceIndex : range(deviceCount)) {
|
||||||
IMMDevice* device = nullptr;
|
IMMDevice* deviceContext = nullptr;
|
||||||
if(deviceCollection->Item(deviceIndex, &device) != S_OK) continue;
|
if(deviceCollection->Item(deviceIndex, &deviceContext) != S_OK) continue;
|
||||||
|
|
||||||
|
Device device;
|
||||||
|
LPWSTR deviceString = nullptr;
|
||||||
|
deviceContext->GetId(&deviceString);
|
||||||
|
device.id = (const char*)utf8_t(deviceString);
|
||||||
|
CoTaskMemFree(deviceString);
|
||||||
|
|
||||||
IPropertyStore* propertyStore = nullptr;
|
IPropertyStore* propertyStore = nullptr;
|
||||||
device->OpenPropertyStore(STGM_READ, &propertyStore);
|
deviceContext->OpenPropertyStore(STGM_READ, &propertyStore);
|
||||||
PROPVARIANT propVariant;
|
PROPVARIANT propVariant;
|
||||||
propertyStore->GetValue(PKEY_Device_FriendlyName, &propVariant);
|
propertyStore->GetValue(PKEY_Device_FriendlyName, &propVariant);
|
||||||
Device item;
|
device.name = (const char*)utf8_t(propVariant.pwszVal);
|
||||||
item.name = (const char*)utf8_t(propVariant.pwszVal);
|
|
||||||
item.device = device;
|
|
||||||
self.devices.append(item);
|
|
||||||
propertyStore->Release();
|
propertyStore->Release();
|
||||||
|
|
||||||
|
if(device.id == defaultDevice.id) {
|
||||||
|
self.devices.prepend(device);
|
||||||
|
} else {
|
||||||
|
self.devices.append(device);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceCollection->Release();
|
deviceCollection->Release();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto destruct() -> void {
|
||||||
|
terminate();
|
||||||
|
|
||||||
|
if(self.enumerator) {
|
||||||
|
self.enumerator->Release();
|
||||||
|
self.enumerator = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto initialize() -> bool {
|
auto initialize() -> bool {
|
||||||
terminate();
|
terminate();
|
||||||
|
|
||||||
|
string deviceID;
|
||||||
if(auto index = self.devices.find([&](auto& device) { return device.name == self.device; })) {
|
if(auto index = self.devices.find([&](auto& device) { return device.name == self.device; })) {
|
||||||
self.audioDevice = self.devices[*index].device;
|
deviceID = self.devices[*index].id;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utf16_t deviceString(deviceID);
|
||||||
|
if(self.enumerator->GetDevice(deviceString, &self.audioDevice) != S_OK) return false;
|
||||||
|
|
||||||
if(self.audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&self.audioClient) != S_OK) return false;
|
if(self.audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&self.audioClient) != S_OK) return false;
|
||||||
|
|
||||||
WAVEFORMATEXTENSIBLE waveFormat = {};
|
WAVEFORMATEXTENSIBLE waveFormat{};
|
||||||
if(self.exclusive) {
|
if(self.exclusive) {
|
||||||
IPropertyStore* propertyStore = nullptr;
|
IPropertyStore* propertyStore = nullptr;
|
||||||
if(self.audioDevice->OpenPropertyStore(STGM_READ, &propertyStore) != S_OK) return false;
|
if(self.audioDevice->OpenPropertyStore(STGM_READ, &propertyStore) != S_OK) return false;
|
||||||
|
@ -152,9 +192,8 @@ private:
|
||||||
self.mode = waveFormat.SubFormat.Data1;
|
self.mode = waveFormat.SubFormat.Data1;
|
||||||
self.precision = waveFormat.Format.wBitsPerSample;
|
self.precision = waveFormat.Format.wBitsPerSample;
|
||||||
|
|
||||||
self.isReady = true;
|
|
||||||
clear();
|
clear();
|
||||||
return true;
|
return self.isReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto terminate() -> void {
|
auto terminate() -> void {
|
||||||
|
@ -216,11 +255,6 @@ private:
|
||||||
uint mode = 0;
|
uint mode = 0;
|
||||||
uint precision = 0;
|
uint precision = 0;
|
||||||
|
|
||||||
struct Device {
|
|
||||||
string name;
|
|
||||||
IMMDevice* device;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Queue {
|
struct Queue {
|
||||||
double samples[65536][8];
|
double samples[65536][8];
|
||||||
uint16_t read;
|
uint16_t read;
|
||||||
|
@ -229,7 +263,6 @@ private:
|
||||||
} queue;
|
} queue;
|
||||||
|
|
||||||
IMMDeviceEnumerator* enumerator = nullptr;
|
IMMDeviceEnumerator* enumerator = nullptr;
|
||||||
vector<Device> devices;
|
|
||||||
IMMDevice* audioDevice = nullptr;
|
IMMDevice* audioDevice = nullptr;
|
||||||
IAudioClient* audioClient = nullptr;
|
IAudioClient* audioClient = nullptr;
|
||||||
IAudioRenderClient* renderClient = nullptr;
|
IAudioRenderClient* renderClient = nullptr;
|
||||||
|
|
|
@ -3,21 +3,31 @@
|
||||||
#undef interface
|
#undef interface
|
||||||
|
|
||||||
struct AudioXAudio2 : AudioDriver, public IXAudio2VoiceCallback {
|
struct AudioXAudio2 : AudioDriver, public IXAudio2VoiceCallback {
|
||||||
|
enum : uint { Buffers = 32 };
|
||||||
|
|
||||||
AudioXAudio2& self = *this;
|
AudioXAudio2& self = *this;
|
||||||
AudioXAudio2(Audio& super) : AudioDriver(super) {}
|
AudioXAudio2(Audio& super) : AudioDriver(super) { construct(); }
|
||||||
~AudioXAudio2() { terminate(); }
|
~AudioXAudio2() { destruct(); }
|
||||||
|
|
||||||
auto create() -> bool override {
|
auto create() -> bool override {
|
||||||
|
super.setDevice(hasDevices().first());
|
||||||
super.setChannels(2);
|
super.setChannels(2);
|
||||||
super.setFrequency(48000);
|
super.setFrequency(48000);
|
||||||
super.setLatency(40);
|
super.setLatency(40);
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto driver() -> string override { return "XAudio2"; }
|
auto driver() -> string override { return "XAudio 2.1"; }
|
||||||
auto ready() -> bool override { return self.isReady; }
|
auto ready() -> bool override { return self.isReady; }
|
||||||
|
|
||||||
auto hasBlocking() -> bool override { return true; }
|
auto hasBlocking() -> bool override { return true; }
|
||||||
|
auto hasDynamic() -> bool override { return true; }
|
||||||
|
|
||||||
|
auto hasDevices() -> vector<string> override {
|
||||||
|
vector<string> devices;
|
||||||
|
for(auto& device : self.devices) devices.append(device.name);
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
auto hasFrequencies() -> vector<uint> override {
|
auto hasFrequencies() -> vector<uint> override {
|
||||||
return {44100, 48000, 96000};
|
return {44100, 48000, 96000};
|
||||||
|
@ -27,69 +37,120 @@ struct AudioXAudio2 : AudioDriver, public IXAudio2VoiceCallback {
|
||||||
return {20, 40, 60, 80, 100};
|
return {20, 40, 60, 80, 100};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto setDevice(string device) -> bool override { return initialize(); }
|
||||||
auto setBlocking(bool blocking) -> bool override { return true; }
|
auto setBlocking(bool blocking) -> bool override { return true; }
|
||||||
auto setFrequency(uint frequency) -> bool override { return initialize(); }
|
auto setFrequency(uint frequency) -> bool override { return initialize(); }
|
||||||
auto setLatency(uint latency) -> bool override { return initialize(); }
|
auto setLatency(uint latency) -> bool override { return initialize(); }
|
||||||
|
|
||||||
auto clear() -> void override {
|
auto clear() -> void override {
|
||||||
if(!self.sourceVoice) return;
|
|
||||||
self.sourceVoice->Stop(0);
|
self.sourceVoice->Stop(0);
|
||||||
self.sourceVoice->FlushSourceBuffers(); //calls OnBufferEnd for all currently submitted buffers
|
self.sourceVoice->FlushSourceBuffers(); //calls OnBufferEnd for all currently submitted buffers
|
||||||
|
|
||||||
self.bufferIndex = 0;
|
self.index = 0;
|
||||||
|
self.queue = 0;
|
||||||
self.bufferOffset = 0;
|
for(uint n : range(Buffers)) self.buffers[n].fill();
|
||||||
if(self.buffer) memory::fill<uint32_t>(self.buffer, self.period * self.bufferCount);
|
|
||||||
|
|
||||||
self.sourceVoice->Start(0);
|
self.sourceVoice->Start(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto output(const double samples[]) -> void override {
|
auto level() -> double override {
|
||||||
self.buffer[self.bufferIndex * self.period + self.bufferOffset] = (uint16_t)sclamp<16>(samples[0] * 32767.0) << 0;
|
XAUDIO2_VOICE_STATE state{};
|
||||||
self.buffer[self.bufferIndex * self.period + self.bufferOffset] |= (uint16_t)sclamp<16>(samples[1] * 32767.0) << 16;
|
self.sourceVoice->GetState(&state);
|
||||||
if(++self.bufferOffset < self.period) return;
|
uint level = state.BuffersQueued * self.period - state.SamplesPlayed % self.period;
|
||||||
self.bufferOffset = 0;
|
uint limit = Buffers * self.period;
|
||||||
|
return (double)(limit - level) / limit;
|
||||||
|
}
|
||||||
|
|
||||||
if(self.bufferQueue == self.bufferCount - 1) {
|
auto output(const double samples[]) -> void override {
|
||||||
|
uint32_t frame = 0;
|
||||||
|
frame |= (uint16_t)sclamp<16>(samples[0] * 32767.0) << 0;
|
||||||
|
frame |= (uint16_t)sclamp<16>(samples[1] * 32767.0) << 16;
|
||||||
|
|
||||||
|
auto& buffer = self.buffers[self.index];
|
||||||
|
buffer.write(frame);
|
||||||
|
if(!buffer.full()) return;
|
||||||
|
|
||||||
|
buffer.flush();
|
||||||
|
if(self.queue == Buffers - 1) {
|
||||||
if(self.blocking) {
|
if(self.blocking) {
|
||||||
//wait until there is at least one other free buffer for the next sample
|
//wait until there is at least one other free buffer for the next sample
|
||||||
while(self.bufferQueue == self.bufferCount - 1);
|
while(self.queue == Buffers - 1);
|
||||||
} else { //we need one free buffer for the next sample, so ignore the current contents
|
} else {
|
||||||
|
//there is no free buffer for the next block, so ignore the current contents
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pushBuffer(self.period * 4, self.buffer + self.bufferIndex * self.period);
|
write(buffer.data(), buffer.capacity<uint8_t>());
|
||||||
self.bufferIndex = (self.bufferIndex + 1) % self.bufferCount;
|
self.index = (self.index + 1) % Buffers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto initialize() -> bool {
|
struct Device {
|
||||||
terminate();
|
uint id = 0;
|
||||||
|
uint channels = 0;
|
||||||
|
uint frequency = 0;
|
||||||
|
Format format = Format::none;
|
||||||
|
string name;
|
||||||
|
};
|
||||||
|
vector<Device> devices;
|
||||||
|
|
||||||
self.bufferCount = 8;
|
auto construct() -> void {
|
||||||
self.period = self.frequency * self.latency / self.bufferCount / 1000.0 + 0.5;
|
XAudio2Create(&self.interface, 0 , XAUDIO2_DEFAULT_PROCESSOR);
|
||||||
self.buffer = new uint32_t[self.period * self.bufferCount];
|
|
||||||
self.bufferOffset = 0;
|
|
||||||
self.bufferIndex = 0;
|
|
||||||
self.bufferQueue = 0;
|
|
||||||
|
|
||||||
if(FAILED(XAudio2Create(&self.interface, 0 , XAUDIO2_DEFAULT_PROCESSOR))) return false;
|
|
||||||
|
|
||||||
uint deviceCount = 0;
|
uint deviceCount = 0;
|
||||||
self.interface->GetDeviceCount(&deviceCount);
|
self.interface->GetDeviceCount(&deviceCount);
|
||||||
if(deviceCount == 0) return terminate(), false;
|
|
||||||
|
|
||||||
uint deviceID = 0;
|
|
||||||
for(uint deviceIndex : range(deviceCount)) {
|
for(uint deviceIndex : range(deviceCount)) {
|
||||||
XAUDIO2_DEVICE_DETAILS deviceDetails = {};
|
XAUDIO2_DEVICE_DETAILS deviceDetails{};
|
||||||
self.interface->GetDeviceDetails(deviceIndex, &deviceDetails);
|
self.interface->GetDeviceDetails(deviceIndex, &deviceDetails);
|
||||||
if(deviceDetails.Role & DefaultGameDevice) deviceID = deviceIndex;
|
auto format = deviceDetails.OutputFormat.Format.wFormatTag;
|
||||||
|
auto bits = deviceDetails.OutputFormat.Format.wBitsPerSample;
|
||||||
|
|
||||||
|
Device device;
|
||||||
|
device.id = deviceIndex;
|
||||||
|
device.name = (const char*)utf8_t(deviceDetails.DisplayName);
|
||||||
|
device.channels = deviceDetails.OutputFormat.Format.nChannels;
|
||||||
|
device.frequency = deviceDetails.OutputFormat.Format.nSamplesPerSec;
|
||||||
|
if(format == WAVE_FORMAT_PCM) {
|
||||||
|
if(bits == 16) device.format = Format::int16;
|
||||||
|
if(bits == 32) device.format = Format::int32;
|
||||||
|
} else if(format == WAVE_FORMAT_IEEE_FLOAT) {
|
||||||
|
if(bits == 32) device.format = Format::float32;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ensure devices.first() is the default device
|
||||||
|
if(deviceDetails.Role & DefaultGameDevice) {
|
||||||
|
devices.prepend(device);
|
||||||
|
} else {
|
||||||
|
devices.append(device);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto destruct() -> void {
|
||||||
|
terminate();
|
||||||
|
|
||||||
|
if(self.interface) {
|
||||||
|
self.interface->Release();
|
||||||
|
self.interface = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto initialize() -> bool {
|
||||||
|
terminate();
|
||||||
|
if(!self.interface) return false;
|
||||||
|
|
||||||
|
self.period = self.frequency * self.latency / Buffers / 1000.0 + 0.5;
|
||||||
|
for(uint n : range(Buffers)) buffers[n].resize(self.period);
|
||||||
|
self.index = 0;
|
||||||
|
self.queue = 0;
|
||||||
|
|
||||||
|
if(!hasDevices().find(self.device)) self.device = hasDevices().first();
|
||||||
|
uint deviceID = devices[hasDevices().find(self.device)()].id;
|
||||||
|
|
||||||
if(FAILED(self.interface->CreateMasteringVoice(&self.masterVoice, self.channels, self.frequency, 0, deviceID, nullptr))) return terminate(), false;
|
if(FAILED(self.interface->CreateMasteringVoice(&self.masterVoice, self.channels, self.frequency, 0, deviceID, nullptr))) return terminate(), false;
|
||||||
|
|
||||||
WAVEFORMATEX waveFormat;
|
WAVEFORMATEX waveFormat{};
|
||||||
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
||||||
waveFormat.nChannels = self.channels;
|
waveFormat.nChannels = self.channels;
|
||||||
waveFormat.nSamplesPerSec = self.frequency;
|
waveFormat.nSamplesPerSec = self.frequency;
|
||||||
|
@ -117,33 +178,23 @@ private:
|
||||||
self.masterVoice->DestroyVoice();
|
self.masterVoice->DestroyVoice();
|
||||||
self.masterVoice = nullptr;
|
self.masterVoice = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(self.interface) {
|
|
||||||
self.interface->Release();
|
|
||||||
self.interface = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete[] self.buffer;
|
|
||||||
self.buffer = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pushBuffer(uint bytes, uint32_t* audioData) -> void {
|
auto write(const uint32_t* audioData, uint bytes) -> void {
|
||||||
XAUDIO2_BUFFER buffer = {};
|
XAUDIO2_BUFFER buffer{};
|
||||||
buffer.AudioBytes = bytes;
|
buffer.AudioBytes = bytes;
|
||||||
buffer.pAudioData = (BYTE*)audioData;
|
buffer.pAudioData = (const BYTE*)audioData;
|
||||||
buffer.pContext = 0;
|
buffer.pContext = nullptr;
|
||||||
InterlockedIncrement(&self.bufferQueue);
|
InterlockedIncrement(&self.queue);
|
||||||
self.sourceVoice->SubmitSourceBuffer(&buffer);
|
self.sourceVoice->SubmitSourceBuffer(&buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isReady = false;
|
bool isReady = false;
|
||||||
|
|
||||||
uint32_t* buffer = nullptr;
|
queue<uint32_t> buffers[Buffers];
|
||||||
uint period = 0;
|
uint period = 0; //amount (in 32-bit frames) of samples per buffer
|
||||||
uint bufferCount = 0;
|
uint index = 0; //current buffer for writing samples to
|
||||||
uint bufferOffset = 0;
|
volatile long queue = 0; //how many buffers are queued and ready for playback
|
||||||
uint bufferIndex = 0;
|
|
||||||
volatile long bufferQueue = 0; //how many buffers are queued and ready for playback
|
|
||||||
|
|
||||||
IXAudio2* interface = nullptr;
|
IXAudio2* interface = nullptr;
|
||||||
IXAudio2MasteringVoice* masterVoice = nullptr;
|
IXAudio2MasteringVoice* masterVoice = nullptr;
|
||||||
|
@ -158,6 +209,6 @@ private:
|
||||||
STDMETHODIMP_(void) OnVoiceProcessingPassStart(UINT32 BytesRequired) {}
|
STDMETHODIMP_(void) OnVoiceProcessingPassStart(UINT32 BytesRequired) {}
|
||||||
|
|
||||||
STDMETHODIMP_(void) OnBufferEnd(void* pBufferContext) {
|
STDMETHODIMP_(void) OnBufferEnd(void* pBufferContext) {
|
||||||
InterlockedDecrement(&self.bufferQueue);
|
InterlockedDecrement(&self.queue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,7 @@ struct VideoCGL : VideoDriver, OpenGL {
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto driver() -> string override { return "OpenGL"; }
|
auto driver() -> string override { return "OpenGL 3.2"; }
|
||||||
auto ready() -> bool override { return _ready; }
|
auto ready() -> bool override { return _ready; }
|
||||||
|
|
||||||
auto hasContext() -> bool override { return true; }
|
auto hasContext() -> bool override { return true; }
|
||||||
|
|
|
@ -16,7 +16,7 @@ struct VideoDirect3D : VideoDriver {
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto driver() -> string override { return "Direct3D"; }
|
auto driver() -> string override { return "Direct3D 9.0"; }
|
||||||
auto ready() -> bool override { return _ready; }
|
auto ready() -> bool override { return _ready; }
|
||||||
|
|
||||||
auto hasExclusive() -> bool override { return true; }
|
auto hasExclusive() -> bool override { return true; }
|
||||||
|
|
|
@ -10,7 +10,7 @@ struct VideoDirectDraw : VideoDriver {
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto driver() -> string override { return "DirectDraw"; }
|
auto driver() -> string override { return "DirectDraw 7.0"; }
|
||||||
auto ready() -> bool override { return _ready; }
|
auto ready() -> bool override { return _ready; }
|
||||||
|
|
||||||
auto hasContext() -> bool override { return true; }
|
auto hasContext() -> bool override { return true; }
|
||||||
|
|
|
@ -17,7 +17,7 @@ struct VideoGLX : VideoDriver, OpenGL {
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto driver() -> string override { return "OpenGL"; }
|
auto driver() -> string override { return "OpenGL 3.2"; }
|
||||||
auto ready() -> bool override { return _ready; }
|
auto ready() -> bool override { return _ready; }
|
||||||
|
|
||||||
auto hasContext() -> bool override { return true; }
|
auto hasContext() -> bool override { return true; }
|
||||||
|
|
|
@ -31,7 +31,7 @@ struct VideoGLX2 : VideoDriver {
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto driver() -> string override { return "OpenGL2"; }
|
auto driver() -> string override { return "OpenGL 2.0"; }
|
||||||
auto ready() -> bool override { return _ready; }
|
auto ready() -> bool override { return _ready; }
|
||||||
|
|
||||||
auto hasContext() -> bool override { return true; }
|
auto hasContext() -> bool override { return true; }
|
||||||
|
@ -64,7 +64,6 @@ struct VideoGLX2 : VideoDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clear() -> void override {
|
auto clear() -> void override {
|
||||||
if(!ready()) return;
|
|
||||||
memory::fill<uint32_t>(_glBuffer, _glWidth * _glHeight);
|
memory::fill<uint32_t>(_glBuffer, _glWidth * _glHeight);
|
||||||
glClearColor(0.0, 0.0, 0.0, 1.0);
|
glClearColor(0.0, 0.0, 0.0, 1.0);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
|
@ -124,15 +124,15 @@ auto Video::create(string driver) -> bool {
|
||||||
if(!driver) driver = optimalDriver();
|
if(!driver) driver = optimalDriver();
|
||||||
|
|
||||||
#if defined(VIDEO_CGL)
|
#if defined(VIDEO_CGL)
|
||||||
if(driver == "OpenGL") self.instance = new VideoCGL(*this);
|
if(driver == "OpenGL 3.2") self.instance = new VideoCGL(*this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_DIRECT3D)
|
#if defined(VIDEO_DIRECT3D)
|
||||||
if(driver == "Direct3D") self.instance = new VideoDirect3D(*this);
|
if(driver == "Direct3D 9.0") self.instance = new VideoDirect3D(*this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_DIRECTDRAW)
|
#if defined(VIDEO_DIRECTDRAW)
|
||||||
if(driver == "DirectDraw") self.instance = new VideoDirectDraw(*this);
|
if(driver == "DirectDraw 7.0") self.instance = new VideoDirectDraw(*this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_GDI)
|
#if defined(VIDEO_GDI)
|
||||||
|
@ -140,15 +140,15 @@ auto Video::create(string driver) -> bool {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_GLX)
|
#if defined(VIDEO_GLX)
|
||||||
if(driver == "OpenGL") self.instance = new VideoGLX(*this);
|
if(driver == "OpenGL 3.2") self.instance = new VideoGLX(*this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_GLX2)
|
#if defined(VIDEO_GLX2)
|
||||||
if(driver == "OpenGL2") self.instance = new VideoGLX2(*this);
|
if(driver == "OpenGL 2.0") self.instance = new VideoGLX2(*this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_WGL)
|
#if defined(VIDEO_WGL)
|
||||||
if(driver == "OpenGL") self.instance = new VideoWGL(*this);
|
if(driver == "OpenGL 3.2") self.instance = new VideoWGL(*this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_XSHM)
|
#if defined(VIDEO_XSHM)
|
||||||
|
@ -168,15 +168,15 @@ auto Video::hasDrivers() -> vector<string> {
|
||||||
return {
|
return {
|
||||||
|
|
||||||
#if defined(VIDEO_WGL)
|
#if defined(VIDEO_WGL)
|
||||||
"OpenGL",
|
"OpenGL 3.2",
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_DIRECT3D)
|
#if defined(VIDEO_DIRECT3D)
|
||||||
"Direct3D",
|
"Direct3D 9.0",
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_DIRECTDRAW)
|
#if defined(VIDEO_DIRECTDRAW)
|
||||||
"DirectDraw",
|
"DirectDraw 7.0",
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_GDI)
|
#if defined(VIDEO_GDI)
|
||||||
|
@ -184,15 +184,15 @@ auto Video::hasDrivers() -> vector<string> {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_CGL)
|
#if defined(VIDEO_CGL)
|
||||||
"OpenGL",
|
"OpenGL 3.2",
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_GLX)
|
#if defined(VIDEO_GLX)
|
||||||
"OpenGL",
|
"OpenGL 3.2",
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_GLX2)
|
#if defined(VIDEO_GLX2)
|
||||||
"OpenGL2",
|
"OpenGL 2.0",
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(VIDEO_XVIDEO)
|
#if defined(VIDEO_XVIDEO)
|
||||||
|
@ -208,19 +208,19 @@ auto Video::hasDrivers() -> vector<string> {
|
||||||
|
|
||||||
auto Video::optimalDriver() -> string {
|
auto Video::optimalDriver() -> string {
|
||||||
#if defined(VIDEO_WGL)
|
#if defined(VIDEO_WGL)
|
||||||
return "OpenGL";
|
return "OpenGL 3.2";
|
||||||
#elif defined(VIDEO_DIRECT3D)
|
#elif defined(VIDEO_DIRECT3D)
|
||||||
return "Direct3D";
|
return "Direct3D 9.0";
|
||||||
#elif defined(VIDEO_DIRECTDRAW)
|
#elif defined(VIDEO_DIRECTDRAW)
|
||||||
return "DirectDraw";
|
return "DirectDraw 7.0";
|
||||||
#elif defined(VIDEO_GDI)
|
#elif defined(VIDEO_GDI)
|
||||||
return "GDI";
|
return "GDI";
|
||||||
#elif defined(VIDEO_CGL)
|
#elif defined(VIDEO_CGL)
|
||||||
return "OpenGL";
|
return "OpenGL 3.2";
|
||||||
#elif defined(VIDEO_GLX)
|
#elif defined(VIDEO_GLX)
|
||||||
return "OpenGL";
|
return "OpenGL 3.2";
|
||||||
#elif defined(VIDEO_GLX2)
|
#elif defined(VIDEO_GLX2)
|
||||||
return "OpenGL2";
|
return "OpenGL 2.0";
|
||||||
#elif defined(VIDEO_XVIDEO)
|
#elif defined(VIDEO_XVIDEO)
|
||||||
return "XVideo";
|
return "XVideo";
|
||||||
#elif defined(VIDEO_XSHM)
|
#elif defined(VIDEO_XSHM)
|
||||||
|
@ -232,23 +232,23 @@ auto Video::optimalDriver() -> string {
|
||||||
|
|
||||||
auto Video::safestDriver() -> string {
|
auto Video::safestDriver() -> string {
|
||||||
#if defined(VIDEO_DIRECT3D)
|
#if defined(VIDEO_DIRECT3D)
|
||||||
return "Direct3D";
|
return "Direct3D 9.0";
|
||||||
#elif defined(VIDEO_WGL)
|
#elif defined(VIDEO_WGL)
|
||||||
return "OpenGL";
|
return "OpenGL 3.2";
|
||||||
#elif defined(VIDEO_DIRECTDRAW)
|
#elif defined(VIDEO_DIRECTDRAW)
|
||||||
return "DirectDraw";
|
return "DirectDraw 7.0";
|
||||||
#elif defined(VIDEO_GDI)
|
#elif defined(VIDEO_GDI)
|
||||||
return "GDI";
|
return "GDI";
|
||||||
#elif defined(VIDEO_CGL)
|
#elif defined(VIDEO_CGL)
|
||||||
return "OpenGL";
|
return "OpenGL 3.2";
|
||||||
#elif defined(VIDEO_XSHM)
|
#elif defined(VIDEO_XSHM)
|
||||||
return "XShm";
|
return "XShm";
|
||||||
#elif defined(VIDEO_XVIDEO)
|
#elif defined(VIDEO_XVIDEO)
|
||||||
return "XVideo";
|
return "XVideo";
|
||||||
#elif defined(VIDEO_GLX2)
|
#elif defined(VIDEO_GLX2)
|
||||||
return "OpenGL2";
|
return "OpenGL 2.0";
|
||||||
#elif defined(VIDEO_GLX)
|
#elif defined(VIDEO_GLX)
|
||||||
return "OpenGL";
|
return "OpenGL 3.2";
|
||||||
#else
|
#else
|
||||||
return "None";
|
return "None";
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -12,7 +12,7 @@ struct VideoWGL : VideoDriver, OpenGL {
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto driver() -> string override { return "OpenGL"; }
|
auto driver() -> string override { return "OpenGL 3.2"; }
|
||||||
auto ready() -> bool override { return _ready; }
|
auto ready() -> bool override { return _ready; }
|
||||||
|
|
||||||
auto hasContext() -> bool override { return true; }
|
auto hasContext() -> bool override { return true; }
|
||||||
|
|
Loading…
Reference in New Issue