Initial Commit
This commit is contained in:
commit
08337e38f5
48 changed files with 7856 additions and 0 deletions
48
GNUmakefile
Normal file
48
GNUmakefile
Normal file
|
@ -0,0 +1,48 @@
|
|||
ifeq ($(platform),macosx)
|
||||
rubyflags = $(objcppflags) $(flags)
|
||||
else
|
||||
rubyflags = $(cppflags) $(flags)
|
||||
endif
|
||||
|
||||
rubyflags += $(foreach c,$(subst .,_,$(call strupper,$(ruby))),-D$c)
|
||||
rubyflags += $(if $(findstring .sdl,$(ruby)),$(shell sdl-config --cflags))
|
||||
|
||||
rubylink =
|
||||
|
||||
rubylink += $(if $(findstring video.cgl,$(ruby)),-framework OpenGL)
|
||||
rubylink += $(if $(findstring video.direct3d,$(ruby)),-ld3d9)
|
||||
rubylink += $(if $(findstring video.directdraw,$(ruby)),-lddraw)
|
||||
rubylink += $(if $(findstring video.glx,$(ruby)),-lGL)
|
||||
rubylink += $(if $(findstring video.wgl,$(ruby)),-lopengl32)
|
||||
rubylink += $(if $(findstring video.xv,$(ruby)),-lXv)
|
||||
|
||||
rubylink += $(if $(findstring audio.alsa,$(ruby)),-lasound)
|
||||
rubylink += $(if $(findstring audio.ao,$(ruby)),-lao)
|
||||
rubylink += $(if $(findstring audio.directsound,$(ruby)),-ldsound)
|
||||
rubylink += $(if $(findstring audio.pulseaudio,$(ruby)),-lpulse)
|
||||
rubylink += $(if $(findstring audio.pulseaudiosimple,$(ruby)),-lpulse-simple)
|
||||
rubylink += $(if $(findstring audio.wasapi,$(ruby)),-lavrt -luuid)
|
||||
rubylink += $(if $(findstring audio.xaudio2,$(ruby)),-lole32)
|
||||
|
||||
rubylink += $(if $(findstring input.udev,$(ruby)),-ludev)
|
||||
rubylink += $(if $(findstring input.windows,$(ruby)),-ldinput8 -ldxguid)
|
||||
|
||||
rubylink += $(if $(findstring .sdl,$(ruby)),$(shell sdl-config --libs))
|
||||
|
||||
ifeq ($(platform),windows)
|
||||
rubylink += $(if $(findstring audio.openal,$(ruby)),-lopenal32)
|
||||
endif
|
||||
|
||||
ifeq ($(platform),macosx)
|
||||
rubylink += $(if $(findstring audio.openal,$(ruby)),-framework OpenAL)
|
||||
endif
|
||||
|
||||
ifeq ($(platform),linux)
|
||||
rubylink += -lX11 -lXext
|
||||
rubylink += $(if $(findstring audio.openal,$(ruby)),-lopenal)
|
||||
endif
|
||||
|
||||
ifeq ($(platform),bsd)
|
||||
rubylink += -lX11 -lXext
|
||||
rubylink += $(if $(findstring audio.openal,$(ruby)),-lopenal)
|
||||
endif
|
212
audio/alsa.cpp
Normal file
212
audio/alsa.cpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
#include <alsa/asoundlib.h>
|
||||
|
||||
struct AudioALSA : Audio {
|
||||
~AudioALSA() { term(); }
|
||||
|
||||
struct {
|
||||
snd_pcm_t* handle = nullptr;
|
||||
snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
|
||||
snd_pcm_uframes_t buffer_size;
|
||||
snd_pcm_uframes_t period_size;
|
||||
int channels = 2;
|
||||
const char* name = "default";
|
||||
} device;
|
||||
|
||||
struct {
|
||||
uint32_t* data = nullptr;
|
||||
unsigned length = 0;
|
||||
} buffer;
|
||||
|
||||
struct {
|
||||
bool synchronize = false;
|
||||
unsigned frequency = 22050;
|
||||
unsigned latency = 60;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Audio::Synchronize) return true;
|
||||
if(name == Audio::Frequency) return true;
|
||||
if(name == Audio::Latency) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Audio::Synchronize) return settings.synchronize;
|
||||
if(name == Audio::Frequency) return settings.frequency;
|
||||
if(name == Audio::Latency) return settings.latency;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
||||
if(settings.synchronize != value.get<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
if(device.handle) init();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Frequency && value.is<unsigned>()) {
|
||||
if(settings.frequency != value.get<unsigned>()) {
|
||||
settings.frequency = value.get<unsigned>();
|
||||
if(device.handle) init();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Latency && value.is<unsigned>()) {
|
||||
if(settings.latency != value.get<unsigned>()) {
|
||||
settings.latency = value.get<unsigned>();
|
||||
if(device.handle) init();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sample(uint16_t left, uint16_t right) -> void {
|
||||
if(!device.handle) return;
|
||||
|
||||
buffer.data[buffer.length++] = left + (right << 16);
|
||||
if(buffer.length < device.period_size) return;
|
||||
|
||||
snd_pcm_sframes_t avail;
|
||||
do {
|
||||
avail = snd_pcm_avail_update(device.handle);
|
||||
if(avail < 0) snd_pcm_recover(device.handle, avail, 1);
|
||||
if(avail < buffer.length) {
|
||||
if(settings.synchronize == false) {
|
||||
buffer.length = 0;
|
||||
return;
|
||||
}
|
||||
int error = snd_pcm_wait(device.handle, -1);
|
||||
if(error < 0) snd_pcm_recover(device.handle, error, 1);
|
||||
}
|
||||
} while(avail < buffer.length);
|
||||
|
||||
//below code has issues with PulseAudio sound server
|
||||
#if 0
|
||||
if(settings.synchronize == false) {
|
||||
snd_pcm_sframes_t avail = snd_pcm_avail_update(device.handle);
|
||||
if(avail < device.period_size) {
|
||||
buffer.length = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t* buffer_ptr = buffer.data;
|
||||
int i = 4;
|
||||
|
||||
while((buffer.length > 0) && i--) {
|
||||
snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer_ptr, buffer.length);
|
||||
if(written < 0) {
|
||||
//no samples written
|
||||
snd_pcm_recover(device.handle, written, 1);
|
||||
} else if(written <= buffer.length) {
|
||||
buffer.length -= written;
|
||||
buffer_ptr += written;
|
||||
}
|
||||
}
|
||||
|
||||
if(i < 0) {
|
||||
if(buffer.data == buffer_ptr) {
|
||||
buffer.length--;
|
||||
buffer_ptr++;
|
||||
}
|
||||
memmove(buffer.data, buffer_ptr, buffer.length * sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) {
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
//below code will not work with 24khz frequency rate (ALSA library bug)
|
||||
#if 0
|
||||
if(snd_pcm_set_params(device.handle, device.format, SND_PCM_ACCESS_RW_INTERLEAVED,
|
||||
device.channels, settings.frequency, 1, settings.latency * 1000) < 0) {
|
||||
//failed to set device parameters
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) {
|
||||
device.period_size = settings.latency * 1000 * 1e-6 * settings.frequency / 4;
|
||||
}
|
||||
#endif
|
||||
|
||||
snd_pcm_hw_params_t* hwparams;
|
||||
snd_pcm_sw_params_t* swparams;
|
||||
unsigned rate = settings.frequency;
|
||||
unsigned buffer_time = settings.latency * 1000;
|
||||
unsigned period_time = settings.latency * 1000 / 4;
|
||||
|
||||
snd_pcm_hw_params_alloca(&hwparams);
|
||||
if(snd_pcm_hw_params_any(device.handle, hwparams) < 0) {
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(snd_pcm_hw_params_set_access(device.handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0
|
||||
|| snd_pcm_hw_params_set_format(device.handle, hwparams, device.format) < 0
|
||||
|| snd_pcm_hw_params_set_channels(device.handle, hwparams, device.channels) < 0
|
||||
|| snd_pcm_hw_params_set_rate_near(device.handle, hwparams, &rate, 0) < 0
|
||||
|| snd_pcm_hw_params_set_period_time_near(device.handle, hwparams, &period_time, 0) < 0
|
||||
|| snd_pcm_hw_params_set_buffer_time_near(device.handle, hwparams, &buffer_time, 0) < 0
|
||||
) {
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(snd_pcm_hw_params(device.handle, hwparams) < 0) {
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) {
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
snd_pcm_sw_params_alloca(&swparams);
|
||||
if(snd_pcm_sw_params_current(device.handle, swparams) < 0) {
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(snd_pcm_sw_params_set_start_threshold(device.handle, swparams,
|
||||
(device.buffer_size / device.period_size) * device.period_size) < 0
|
||||
) {
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(snd_pcm_sw_params(device.handle, swparams) < 0) {
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer.data = new uint32_t[device.period_size];
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(device.handle) {
|
||||
//snd_pcm_drain(device.handle); //prevents popping noise; but causes multi-second lag
|
||||
snd_pcm_close(device.handle);
|
||||
device.handle = 0;
|
||||
}
|
||||
|
||||
if(buffer.data) {
|
||||
delete[] buffer.data;
|
||||
buffer.data = 0;
|
||||
}
|
||||
}
|
||||
};
|
73
audio/ao.cpp
Normal file
73
audio/ao.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include <ao/ao.h>
|
||||
|
||||
struct AudioAO : Audio {
|
||||
~AudioAO() { term(); }
|
||||
|
||||
int driver_id;
|
||||
ao_sample_format driver_format;
|
||||
ao_device* audio_device = nullptr;
|
||||
|
||||
struct {
|
||||
unsigned frequency = 22050;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Audio::Frequency) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Audio::Frequency) return settings.frequency;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Audio::Frequency && value.is<unsigned>()) {
|
||||
settings.frequency = value.get<unsigned>();
|
||||
if(audio_device) init();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sample(uint16_t l_sample, uint16_t r_sample) -> void {
|
||||
uint32_t samp = (l_sample << 0) + (r_sample << 16);
|
||||
ao_play(audio_device, (char*)&samp, 4); //This may need to be byte swapped for Big Endian
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
ao_initialize();
|
||||
|
||||
driver_id = ao_default_driver_id(); //ao_driver_id((const char*)driver)
|
||||
if(driver_id < 0) return false;
|
||||
|
||||
driver_format.bits = 16;
|
||||
driver_format.channels = 2;
|
||||
driver_format.rate = settings.frequency;
|
||||
driver_format.byte_format = AO_FMT_LITTLE;
|
||||
|
||||
ao_option* options = nullptr;
|
||||
ao_info *di = ao_driver_info(driver_id);
|
||||
if(!di) return false;
|
||||
if(!strcmp(di->short_name, "alsa")) {
|
||||
ao_append_option(&options, "buffer_time", "100000"); //100ms latency (default was 500ms)
|
||||
}
|
||||
|
||||
audio_device = ao_open_live(driver_id, &driver_format, options);
|
||||
if(!audio_device) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(audio_device) {
|
||||
ao_close(audio_device);
|
||||
audio_device = nullptr;
|
||||
}
|
||||
ao_shutdown();
|
||||
}
|
||||
};
|
184
audio/directsound.cpp
Normal file
184
audio/directsound.cpp
Normal file
|
@ -0,0 +1,184 @@
|
|||
#include <dsound.h>
|
||||
|
||||
struct AudioDS : Audio {
|
||||
~AudioDS() { term(); }
|
||||
|
||||
LPDIRECTSOUND ds = nullptr;
|
||||
LPDIRECTSOUNDBUFFER dsb_p = nullptr;
|
||||
LPDIRECTSOUNDBUFFER dsb_b = nullptr;
|
||||
DSBUFFERDESC dsbd;
|
||||
WAVEFORMATEX wfx;
|
||||
|
||||
struct {
|
||||
uint rings = 0;
|
||||
uint latency = 0;
|
||||
|
||||
uint32_t* buffer = nullptr;
|
||||
uint bufferoffset = 0;
|
||||
|
||||
uint readring = 0;
|
||||
uint writering = 0;
|
||||
int distance = 0;
|
||||
} device;
|
||||
|
||||
struct {
|
||||
HWND handle = nullptr;
|
||||
bool synchronize = false;
|
||||
uint frequency = 22050;
|
||||
uint latency = 120;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Audio::Handle) return true;
|
||||
if(name == Audio::Synchronize) return true;
|
||||
if(name == Audio::Frequency) return true;
|
||||
if(name == Audio::Latency) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Audio::Handle) return (uintptr_t)settings.handle;
|
||||
if(name == Audio::Synchronize) return settings.synchronize;
|
||||
if(name == Audio::Frequency) return settings.frequency;
|
||||
if(name == Audio::Latency) return settings.latency;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Audio::Handle && value.is<uintptr_t>()) {
|
||||
settings.handle = (HWND)value.get<uintptr_t>();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
if(ds) clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Frequency && value.is<uint>()) {
|
||||
settings.frequency = value.get<uint>();
|
||||
if(ds) init();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Latency && value.is<uint>()) {
|
||||
//latency settings below 40ms causes DirectSound to hang
|
||||
settings.latency = max(40u, value.get<uint>());
|
||||
if(ds) init();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sample(uint16_t left, uint16_t right) -> void {
|
||||
device.buffer[device.bufferoffset++] = left + (right << 16);
|
||||
if(device.bufferoffset < device.latency) return;
|
||||
device.bufferoffset = 0;
|
||||
|
||||
DWORD pos, size;
|
||||
void* output;
|
||||
|
||||
if(settings.synchronize) {
|
||||
//wait until playback buffer has an empty ring to write new audio data to
|
||||
while(device.distance >= device.rings - 1) {
|
||||
dsb_b->GetCurrentPosition(&pos, 0);
|
||||
uint activering = pos / (device.latency * 4);
|
||||
if(activering == device.readring) continue;
|
||||
|
||||
//subtract number of played rings from ring distance counter
|
||||
device.distance -= (device.rings + activering - device.readring) % device.rings;
|
||||
device.readring = activering;
|
||||
|
||||
if(device.distance < 2) {
|
||||
//buffer underflow; set max distance to recover quickly
|
||||
device.distance = device.rings - 1;
|
||||
device.writering = (device.rings + device.readring - 1) % device.rings;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
device.writering = (device.writering + 1) % device.rings;
|
||||
device.distance = (device.distance + 1) % device.rings;
|
||||
|
||||
if(dsb_b->Lock(device.writering * device.latency * 4, device.latency * 4, &output, &size, 0, 0, 0) == DS_OK) {
|
||||
memcpy(output, device.buffer, device.latency * 4);
|
||||
dsb_b->Unlock(output, size, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
device.readring = 0;
|
||||
device.writering = device.rings - 1;
|
||||
device.distance = device.rings - 1;
|
||||
|
||||
device.bufferoffset = 0;
|
||||
if(device.buffer) memset(device.buffer, 0, device.latency * device.rings * 4);
|
||||
|
||||
if(!dsb_b) return;
|
||||
dsb_b->Stop();
|
||||
dsb_b->SetCurrentPosition(0);
|
||||
|
||||
DWORD size;
|
||||
void* output;
|
||||
dsb_b->Lock(0, device.latency * device.rings * 4, &output, &size, 0, 0, 0);
|
||||
memset(output, 0, size);
|
||||
dsb_b->Unlock(output, size, 0, 0);
|
||||
|
||||
dsb_b->Play(0, 0, DSBPLAY_LOOPING);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
settings.handle = GetDesktopWindow();
|
||||
|
||||
device.rings = 8;
|
||||
device.latency = settings.frequency * settings.latency / device.rings / 1000.0 + 0.5;
|
||||
device.buffer = new uint32_t[device.latency * device.rings];
|
||||
device.bufferoffset = 0;
|
||||
|
||||
if(DirectSoundCreate(0, &ds, 0) != DS_OK) return term(), false;
|
||||
ds->SetCooperativeLevel((HWND)settings.handle, DSSCL_PRIORITY);
|
||||
|
||||
memory::fill(&dsbd, sizeof(dsbd));
|
||||
dsbd.dwSize = sizeof(dsbd);
|
||||
dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||||
dsbd.dwBufferBytes = 0;
|
||||
dsbd.lpwfxFormat = 0;
|
||||
ds->CreateSoundBuffer(&dsbd, &dsb_p, 0);
|
||||
|
||||
memory::fill(&wfx, sizeof(wfx));
|
||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = 2;
|
||||
wfx.nSamplesPerSec = settings.frequency;
|
||||
wfx.wBitsPerSample = 16;
|
||||
wfx.nBlockAlign = wfx.wBitsPerSample / 8 * wfx.nChannels;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
dsb_p->SetFormat(&wfx);
|
||||
|
||||
memory::fill(&dsbd, sizeof(dsbd));
|
||||
dsbd.dwSize = sizeof(dsbd);
|
||||
dsbd.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLFREQUENCY | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE;
|
||||
dsbd.dwBufferBytes = device.latency * device.rings * sizeof(uint32_t);
|
||||
dsbd.guid3DAlgorithm = GUID_NULL;
|
||||
dsbd.lpwfxFormat = &wfx;
|
||||
ds->CreateSoundBuffer(&dsbd, &dsb_b, 0);
|
||||
dsb_b->SetFrequency(settings.frequency);
|
||||
dsb_b->SetCurrentPosition(0);
|
||||
|
||||
clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(device.buffer) {
|
||||
delete[] device.buffer;
|
||||
device.buffer = nullptr;
|
||||
}
|
||||
|
||||
if(dsb_b) { dsb_b->Stop(); dsb_b->Release(); dsb_b = nullptr; }
|
||||
if(dsb_p) { dsb_p->Stop(); dsb_p->Release(); dsb_p = nullptr; }
|
||||
if(ds) { ds->Release(); ds = nullptr; }
|
||||
}
|
||||
};
|
194
audio/openal.cpp
Normal file
194
audio/openal.cpp
Normal file
|
@ -0,0 +1,194 @@
|
|||
#if defined(PLATFORM_MACOSX)
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
|
||||
struct AudioOpenAL : Audio {
|
||||
~AudioOpenAL() { term(); }
|
||||
|
||||
struct {
|
||||
ALCdevice* handle = nullptr;
|
||||
ALCcontext* context = nullptr;
|
||||
ALuint source = 0;
|
||||
ALenum format = AL_FORMAT_STEREO16;
|
||||
unsigned latency = 0;
|
||||
unsigned queueLength = 0;
|
||||
} device;
|
||||
|
||||
struct {
|
||||
uint32_t* data = nullptr;
|
||||
unsigned length = 0;
|
||||
unsigned size = 0;
|
||||
} buffer;
|
||||
|
||||
struct {
|
||||
bool synchronize = true;
|
||||
unsigned frequency = 22050;
|
||||
unsigned latency = 40;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Audio::Synchronize) return true;
|
||||
if(name == Audio::Frequency) return true;
|
||||
if(name == Audio::Latency) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Audio::Synchronize) return settings.synchronize;
|
||||
if(name == Audio::Frequency) return settings.frequency;
|
||||
if(name == Audio::Latency) return settings.latency;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Frequency && value.is<unsigned>()) {
|
||||
settings.frequency = value.get<unsigned>();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Latency && value.is<unsigned>()) {
|
||||
if(settings.latency != value.get<unsigned>()) {
|
||||
settings.latency = value.get<unsigned>();
|
||||
updateLatency();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sample(uint16_t left, uint16_t right) -> void {
|
||||
buffer.data[buffer.length++] = left << 0 | right << 16;
|
||||
if(buffer.length < buffer.size) return;
|
||||
|
||||
ALuint albuffer = 0;
|
||||
int processed = 0;
|
||||
while(true) {
|
||||
alGetSourcei(device.source, AL_BUFFERS_PROCESSED, &processed);
|
||||
while(processed--) {
|
||||
alSourceUnqueueBuffers(device.source, 1, &albuffer);
|
||||
alDeleteBuffers(1, &albuffer);
|
||||
device.queueLength--;
|
||||
}
|
||||
//wait for buffer playback to catch up to sample generation if not synchronizing
|
||||
if(settings.synchronize == false || device.queueLength < 3) break;
|
||||
}
|
||||
|
||||
if(device.queueLength < 3) {
|
||||
alGenBuffers(1, &albuffer);
|
||||
alBufferData(albuffer, device.format, buffer.data, buffer.size * 4, settings.frequency);
|
||||
alSourceQueueBuffers(device.source, 1, &albuffer);
|
||||
device.queueLength++;
|
||||
}
|
||||
|
||||
ALint playing;
|
||||
alGetSourcei(device.source, AL_SOURCE_STATE, &playing);
|
||||
if(playing != AL_PLAYING) alSourcePlay(device.source);
|
||||
buffer.length = 0;
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
updateLatency();
|
||||
device.queueLength = 0;
|
||||
|
||||
bool success = false;
|
||||
if(device.handle = alcOpenDevice(nullptr)) {
|
||||
if(device.context = alcCreateContext(device.handle, nullptr)) {
|
||||
alcMakeContextCurrent(device.context);
|
||||
alGenSources(1, &device.source);
|
||||
|
||||
//alSourcef (device.source, AL_PITCH, 1.0);
|
||||
//alSourcef (device.source, AL_GAIN, 1.0);
|
||||
//alSource3f(device.source, AL_POSITION, 0.0, 0.0, 0.0);
|
||||
//alSource3f(device.source, AL_VELOCITY, 0.0, 0.0, 0.0);
|
||||
//alSource3f(device.source, AL_DIRECTION, 0.0, 0.0, 0.0);
|
||||
//alSourcef (device.source, AL_ROLLOFF_FACTOR, 0.0);
|
||||
//alSourcei (device.source, AL_SOURCE_RELATIVE, AL_TRUE);
|
||||
|
||||
alListener3f(AL_POSITION, 0.0, 0.0, 0.0);
|
||||
alListener3f(AL_VELOCITY, 0.0, 0.0, 0.0);
|
||||
ALfloat listener_orientation[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
||||
alListenerfv(AL_ORIENTATION, listener_orientation);
|
||||
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(success == false) {
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(alIsSource(device.source) == AL_TRUE) {
|
||||
int playing = 0;
|
||||
alGetSourcei(device.source, AL_SOURCE_STATE, &playing);
|
||||
if(playing == AL_PLAYING) {
|
||||
alSourceStop(device.source);
|
||||
int queued = 0;
|
||||
alGetSourcei(device.source, AL_BUFFERS_QUEUED, &queued);
|
||||
while(queued--) {
|
||||
ALuint albuffer = 0;
|
||||
alSourceUnqueueBuffers(device.source, 1, &albuffer);
|
||||
alDeleteBuffers(1, &albuffer);
|
||||
device.queueLength--;
|
||||
}
|
||||
}
|
||||
|
||||
alDeleteSources(1, &device.source);
|
||||
device.source = 0;
|
||||
}
|
||||
|
||||
if(device.context) {
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(device.context);
|
||||
device.context = 0;
|
||||
}
|
||||
|
||||
if(device.handle) {
|
||||
alcCloseDevice(device.handle);
|
||||
device.handle = 0;
|
||||
}
|
||||
|
||||
if(buffer.data) {
|
||||
delete[] buffer.data;
|
||||
buffer.data = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
auto queryDevices() -> lstring {
|
||||
lstring result;
|
||||
|
||||
const char* buffer = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
|
||||
if(!buffer) return result;
|
||||
|
||||
while(buffer[0] || buffer[1]) {
|
||||
result.append(buffer);
|
||||
while(buffer[0]) buffer++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto updateLatency() -> void {
|
||||
if(buffer.data) delete[] buffer.data;
|
||||
buffer.size = settings.frequency * settings.latency / 1000.0 + 0.5;
|
||||
buffer.data = new uint32_t[buffer.size]();
|
||||
}
|
||||
};
|
115
audio/oss.cpp
Normal file
115
audio/oss.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/soundcard.h>
|
||||
|
||||
//OSS4 soundcard.h includes below SNDCTL defines, but OSS3 does not
|
||||
//However, OSS4 soundcard.h does not reside in <sys/>
|
||||
//Therefore, attempt to manually define SNDCTL values if using OSS3 header
|
||||
//Note that if the defines below fail to work on any specific platform, one can point soundcard.h
|
||||
//above to the correct location for OSS4 (usually /usr/lib/oss/include/sys/soundcard.h)
|
||||
//Failing that, one can disable OSS4 ioctl calls inside init() and remove the below defines
|
||||
|
||||
#ifndef SNDCTL_DSP_COOKEDMODE
|
||||
#define SNDCTL_DSP_COOKEDMODE _IOW('P', 30, signed)
|
||||
#endif
|
||||
|
||||
#ifndef SNDCTL_DSP_POLICY
|
||||
#define SNDCTL_DSP_POLICY _IOW('P', 45, signed)
|
||||
#endif
|
||||
|
||||
struct AudioOSS : Audio {
|
||||
~AudioOSS() { term(); }
|
||||
|
||||
struct {
|
||||
signed fd = -1;
|
||||
signed format = AFMT_S16_LE;
|
||||
signed channels = 2;
|
||||
} device;
|
||||
|
||||
struct {
|
||||
string device = "/dev/dsp";
|
||||
bool synchronize = true;
|
||||
unsigned frequency = 22050;
|
||||
} 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;
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
return {};
|
||||
}
|
||||
|
||||
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<unsigned>()) {
|
||||
settings.frequency = value.get<unsigned>();
|
||||
if(device.fd >= 0) init();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sample(uint16_t left, uint16_t right) -> void {
|
||||
uint32_t sample = left << 0 | 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;
|
||||
|
||||
#if 1 //SOUND_VERSION >= 0x040000
|
||||
//attempt to enable OSS4-specific features regardless of version
|
||||
//OSS3 ioctl calls will silently fail, but sound will still work
|
||||
signed cooked = 1, policy = 4; //policy should be 0 - 10, lower = less latency, more CPU usage
|
||||
ioctl(device.fd, SNDCTL_DSP_COOKEDMODE, &cooked);
|
||||
ioctl(device.fd, SNDCTL_DSP_POLICY, &policy);
|
||||
#endif
|
||||
signed freq = settings.frequency;
|
||||
ioctl(device.fd, SNDCTL_DSP_CHANNELS, &device.channels);
|
||||
ioctl(device.fd, SNDCTL_DSP_SETFMT, &device.format);
|
||||
ioctl(device.fd, SNDCTL_DSP_SPEED, &freq);
|
||||
|
||||
updateSynchronization();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(device.fd >= 0) {
|
||||
close(device.fd);
|
||||
device.fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
159
audio/pulseaudio.cpp
Normal file
159
audio/pulseaudio.cpp
Normal file
|
@ -0,0 +1,159 @@
|
|||
#include <pulse/pulseaudio.h>
|
||||
|
||||
struct AudioPulseAudio : Audio {
|
||||
~AudioPulseAudio() { term(); }
|
||||
|
||||
struct {
|
||||
pa_mainloop* mainloop = nullptr;
|
||||
pa_context* context = nullptr;
|
||||
pa_stream* stream = nullptr;
|
||||
pa_sample_spec spec;
|
||||
pa_buffer_attr buffer_attr;
|
||||
bool first;
|
||||
} device;
|
||||
|
||||
struct {
|
||||
uint32_t* data = nullptr;
|
||||
size_t size;
|
||||
unsigned offset;
|
||||
} buffer;
|
||||
|
||||
struct {
|
||||
bool synchronize = false;
|
||||
unsigned frequency = 22050;
|
||||
unsigned latency = 60;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Audio::Synchronize) return true;
|
||||
if(name == Audio::Frequency) return true;
|
||||
if(name == Audio::Latency) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Audio::Synchronize) return settings.synchronize;
|
||||
if(name == Audio::Frequency) return settings.frequency;
|
||||
if(name == Audio::Latency) return settings.latency;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Frequency && value.is<unsigned>()) {
|
||||
settings.frequency = value.get<unsigned>();
|
||||
if(device.stream) {
|
||||
pa_operation_unref(pa_stream_update_sample_rate(device.stream, settings.frequency, NULL, NULL));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Latency && value.is<unsigned>()) {
|
||||
settings.latency = value.get<unsigned>();
|
||||
if(device.stream) {
|
||||
device.buffer_attr.tlength = pa_usec_to_bytes(settings.latency * PA_USEC_PER_MSEC, &device.spec);
|
||||
pa_stream_set_buffer_attr(device.stream, &device.buffer_attr, NULL, NULL);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sample(uint16_t left, uint16_t right) -> void {
|
||||
pa_stream_begin_write(device.stream, (void**)&buffer.data, &buffer.size);
|
||||
buffer.data[buffer.offset++] = left + (right << 16);
|
||||
if((buffer.offset + 1) * pa_frame_size(&device.spec) <= buffer.size) return;
|
||||
|
||||
while(true) {
|
||||
if(device.first) {
|
||||
device.first = false;
|
||||
pa_mainloop_iterate(device.mainloop, 0, NULL);
|
||||
} else {
|
||||
pa_mainloop_iterate(device.mainloop, 1, NULL);
|
||||
}
|
||||
unsigned length = pa_stream_writable_size(device.stream);
|
||||
if(length >= buffer.offset * pa_frame_size(&device.spec)) break;
|
||||
if(settings.synchronize == false) {
|
||||
buffer.offset = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pa_stream_write(device.stream, (const void*)buffer.data, buffer.offset * pa_frame_size(&device.spec), NULL, 0LL, PA_SEEK_RELATIVE);
|
||||
buffer.data = 0;
|
||||
buffer.offset = 0;
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
device.mainloop = pa_mainloop_new();
|
||||
|
||||
device.context = pa_context_new(pa_mainloop_get_api(device.mainloop), "ruby::pulseaudio");
|
||||
pa_context_connect(device.context, NULL, PA_CONTEXT_NOFLAGS, NULL);
|
||||
|
||||
pa_context_state_t cstate;
|
||||
do {
|
||||
pa_mainloop_iterate(device.mainloop, 1, NULL);
|
||||
cstate = pa_context_get_state(device.context);
|
||||
if(!PA_CONTEXT_IS_GOOD(cstate)) return false;
|
||||
} while(cstate != PA_CONTEXT_READY);
|
||||
|
||||
device.spec.format = PA_SAMPLE_S16LE;
|
||||
device.spec.channels = 2;
|
||||
device.spec.rate = settings.frequency;
|
||||
device.stream = pa_stream_new(device.context, "audio", &device.spec, NULL);
|
||||
|
||||
device.buffer_attr.maxlength = -1;
|
||||
device.buffer_attr.tlength = pa_usec_to_bytes(settings.latency * PA_USEC_PER_MSEC, &device.spec);
|
||||
device.buffer_attr.prebuf = -1;
|
||||
device.buffer_attr.minreq = -1;
|
||||
device.buffer_attr.fragsize = -1;
|
||||
|
||||
pa_stream_flags_t flags = (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY | PA_STREAM_VARIABLE_RATE);
|
||||
pa_stream_connect_playback(device.stream, NULL, &device.buffer_attr, flags, NULL, NULL);
|
||||
|
||||
pa_stream_state_t sstate;
|
||||
do {
|
||||
pa_mainloop_iterate(device.mainloop, 1, NULL);
|
||||
sstate = pa_stream_get_state(device.stream);
|
||||
if(!PA_STREAM_IS_GOOD(sstate)) return false;
|
||||
} while(sstate != PA_STREAM_READY);
|
||||
|
||||
buffer.size = 960;
|
||||
buffer.offset = 0;
|
||||
device.first = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(buffer.data) {
|
||||
pa_stream_cancel_write(device.stream);
|
||||
buffer.data = nullptr;
|
||||
}
|
||||
|
||||
if(device.stream) {
|
||||
pa_stream_disconnect(device.stream);
|
||||
pa_stream_unref(device.stream);
|
||||
device.stream = nullptr;
|
||||
}
|
||||
|
||||
if(device.context) {
|
||||
pa_context_disconnect(device.context);
|
||||
pa_context_unref(device.context);
|
||||
device.context = nullptr;
|
||||
}
|
||||
|
||||
if(device.mainloop) {
|
||||
pa_mainloop_free(device.mainloop);
|
||||
device.mainloop = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
95
audio/pulseaudiosimple.cpp
Normal file
95
audio/pulseaudiosimple.cpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
|
||||
struct AudioPulseAudioSimple : Audio {
|
||||
~AudioPulseAudioSimple() { term(); }
|
||||
|
||||
struct {
|
||||
pa_simple* handle = nullptr;
|
||||
pa_sample_spec spec;
|
||||
} device;
|
||||
|
||||
struct {
|
||||
uint32_t* data = nullptr;
|
||||
unsigned offset = 0;
|
||||
} buffer;
|
||||
|
||||
struct {
|
||||
unsigned frequency = 22050;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Audio::Frequency) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Audio::Frequency) return settings.frequency;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Audio::Frequency && value.is<unsigned>()) {
|
||||
settings.frequency = value.get<unsigned>();
|
||||
if(device.handle) init();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sample(uint16_t left, uint16_t right) -> void {
|
||||
if(!device.handle) return;
|
||||
|
||||
buffer.data[buffer.offset++] = left + (right << 16);
|
||||
if(buffer.offset >= 64) {
|
||||
int error;
|
||||
pa_simple_write(device.handle, (const void*)buffer.data, buffer.offset * sizeof(uint32_t), &error);
|
||||
buffer.offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
device.spec.format = PA_SAMPLE_S16LE;
|
||||
device.spec.channels = 2;
|
||||
device.spec.rate = settings.frequency;
|
||||
|
||||
int error = 0;
|
||||
device.handle = pa_simple_new(
|
||||
0, //default server
|
||||
"ruby::pulseaudiosimple", //application name
|
||||
PA_STREAM_PLAYBACK, //direction
|
||||
0, //default device
|
||||
"audio", //stream description
|
||||
&device.spec, //sample format
|
||||
0, //default channel map
|
||||
0, //default buffering attributes
|
||||
&error //error code
|
||||
);
|
||||
if(!device.handle) {
|
||||
fprintf(stderr, "ruby::pulseaudiosimple failed to initialize - %s\n", pa_strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer.data = new uint32_t[64];
|
||||
buffer.offset = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(device.handle) {
|
||||
int error;
|
||||
pa_simple_flush(device.handle, &error);
|
||||
pa_simple_free(device.handle);
|
||||
device.handle = nullptr;
|
||||
}
|
||||
|
||||
if(buffer.data) {
|
||||
delete[] buffer.data;
|
||||
buffer.data = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
169
audio/wasapi.cpp
Normal file
169
audio/wasapi.cpp
Normal file
|
@ -0,0 +1,169 @@
|
|||
#include <avrt.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <audioclient.h>
|
||||
#include <audiopolicy.h>
|
||||
#include <devicetopology.h>
|
||||
#include <endpointvolume.h>
|
||||
|
||||
#include <nall/dsp.hpp>
|
||||
|
||||
struct AudioWASAPI : Audio {
|
||||
~AudioWASAPI() { term(); }
|
||||
|
||||
struct {
|
||||
bool exclusive = false;
|
||||
bool synchronize = false;
|
||||
uint frequency = 44100;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Audio::Exclusive) return true;
|
||||
if(name == Audio::Synchronize) return true;
|
||||
if(name == Audio::Frequency) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Audio::Exclusive) return settings.exclusive;
|
||||
if(name == Audio::Synchronize) return settings.synchronize;
|
||||
if(name == Audio::Frequency) return settings.frequency;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Audio::Exclusive && value.get<bool>()) {
|
||||
settings.exclusive = value.get<bool>();
|
||||
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>();
|
||||
dsp.setFrequency(settings.frequency);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sample(uint16 left, uint16 right) -> void {
|
||||
int samples[] = {(int16)left, (int16)right};
|
||||
dsp.sample(samples);
|
||||
while(dsp.pending()) {
|
||||
dsp.read(samples);
|
||||
write(samples);
|
||||
}
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
audioClient->Stop();
|
||||
renderClient->GetBuffer(bufferFrameCount, &bufferData);
|
||||
|
||||
renderClient->ReleaseBuffer(bufferFrameCount, 0);
|
||||
audioClient->Start();
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&enumerator) != S_OK) return false;
|
||||
if(enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device) != S_OK) return false;
|
||||
if(device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&audioClient) != S_OK) return false;
|
||||
|
||||
if(settings.exclusive) {
|
||||
if(device->OpenPropertyStore(STGM_READ, &propertyStore) != S_OK) return false;
|
||||
if(propertyStore->GetValue(PKEY_AudioEngine_DeviceFormat, &propVariant) != S_OK) return false;
|
||||
waveFormat = (WAVEFORMATEX*)propVariant.blob.pBlobData;
|
||||
if(audioClient->GetDevicePeriod(nullptr, &devicePeriod) != S_OK) return false;
|
||||
if(audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, 0, devicePeriod, devicePeriod, waveFormat, nullptr) != S_OK) return false;
|
||||
taskHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskIndex);
|
||||
} else {
|
||||
if(audioClient->GetMixFormat(&waveFormat) != S_OK) return false;
|
||||
if(audioClient->GetDevicePeriod(&devicePeriod, nullptr)) return false;
|
||||
if(audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, devicePeriod, 0, waveFormat, nullptr) != S_OK) return false;
|
||||
}
|
||||
|
||||
if(audioClient->GetService(IID_IAudioRenderClient, (void**)&renderClient) != S_OK) return false;
|
||||
if(audioClient->GetBufferSize(&bufferFrameCount) != S_OK) return false;
|
||||
|
||||
switch(((WAVEFORMATEXTENSIBLE*)waveFormat)->SubFormat.Data1) {
|
||||
case 1: ieee = false; break; //fixed point
|
||||
case 3: ieee = true; break; //floating point
|
||||
default: return false; //unknown format; abort
|
||||
}
|
||||
|
||||
dsp.setChannels(2);
|
||||
dsp.setPrecision(16);
|
||||
dsp.setFrequency(settings.frequency);
|
||||
|
||||
dsp.setResampler(DSP::ResampleEngine::Linear);
|
||||
dsp.setResamplerFrequency(waveFormat->nSamplesPerSec);
|
||||
dsp.setChannels(waveFormat->nChannels);
|
||||
dsp.setPrecision(waveFormat->wBitsPerSample);
|
||||
|
||||
print("[WASAPI]\n");
|
||||
print("Channels: ", waveFormat->nChannels, "\n");
|
||||
print("Precision: ", waveFormat->wBitsPerSample, "\n");
|
||||
print("Frequency: ", waveFormat->nSamplesPerSec, "\n");
|
||||
print("IEEE-754: ", ieee, "\n");
|
||||
print("Exclusive: ", settings.exclusive, "\n\n");
|
||||
|
||||
audioClient->Start();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(audioClient) {
|
||||
audioClient->Stop();
|
||||
}
|
||||
|
||||
if(taskHandle) {
|
||||
AvRevertMmThreadCharacteristics(taskHandle);
|
||||
taskHandle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
auto write(int samples[]) -> void {
|
||||
while(true) {
|
||||
uint32 padding = 0;
|
||||
audioClient->GetCurrentPadding(&padding);
|
||||
if(bufferFrameCount - padding < 1) {
|
||||
if(!settings.synchronize) return;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
renderClient->GetBuffer(1, &bufferData);
|
||||
|
||||
if(ieee) {
|
||||
auto buffer = (float*)bufferData;
|
||||
buffer[0] = (int16)samples[0] / 32768.0;
|
||||
buffer[1] = (int16)samples[1] / 32768.0;
|
||||
} else {
|
||||
auto buffer = (int16*)bufferData;
|
||||
buffer[0] = (int16)samples[0];
|
||||
buffer[1] = (int16)samples[1];
|
||||
}
|
||||
|
||||
renderClient->ReleaseBuffer(1, 0);
|
||||
}
|
||||
|
||||
DSP dsp;
|
||||
IMMDeviceEnumerator* enumerator = nullptr;
|
||||
IMMDevice* device = nullptr;
|
||||
IPropertyStore* propertyStore = nullptr;
|
||||
IAudioClient* audioClient = nullptr;
|
||||
IAudioRenderClient* renderClient = nullptr;
|
||||
WAVEFORMATEX* waveFormat = nullptr;
|
||||
PROPVARIANT propVariant;
|
||||
HANDLE taskHandle = nullptr;
|
||||
DWORD taskIndex = 0;
|
||||
REFERENCE_TIME devicePeriod = 0;
|
||||
uint32 bufferFrameCount = 0;
|
||||
uint8* bufferData = nullptr;
|
||||
bool ieee = false;
|
||||
};
|
183
audio/xaudio2.cpp
Normal file
183
audio/xaudio2.cpp
Normal file
|
@ -0,0 +1,183 @@
|
|||
#include "xaudio2.hpp"
|
||||
#include <windows.h>
|
||||
|
||||
struct AudioXAudio2 : Audio, public IXAudio2VoiceCallback {
|
||||
~AudioXAudio2() { term(); }
|
||||
|
||||
IXAudio2* pXAudio2 = nullptr;
|
||||
IXAudio2MasteringVoice* pMasterVoice = nullptr;
|
||||
IXAudio2SourceVoice* pSourceVoice = nullptr;
|
||||
|
||||
//inherited from IXAudio2VoiceCallback
|
||||
STDMETHODIMP_(void) OnBufferStart(void* pBufferContext){}
|
||||
STDMETHODIMP_(void) OnLoopEnd(void* pBufferContext){}
|
||||
STDMETHODIMP_(void) OnStreamEnd() {}
|
||||
STDMETHODIMP_(void) OnVoiceError(void* pBufferContext, HRESULT Error) {}
|
||||
STDMETHODIMP_(void) OnVoiceProcessingPassEnd() {}
|
||||
STDMETHODIMP_(void) OnVoiceProcessingPassStart(UINT32 BytesRequired) {}
|
||||
|
||||
struct {
|
||||
unsigned buffers = 0;
|
||||
unsigned latency = 0;
|
||||
|
||||
uint32_t* buffer = nullptr;
|
||||
unsigned bufferoffset = 0;
|
||||
|
||||
volatile long submitbuffers = 0;
|
||||
unsigned writebuffer = 0;
|
||||
} device;
|
||||
|
||||
struct {
|
||||
bool synchronize = false;
|
||||
unsigned frequency = 22050;
|
||||
unsigned latency = 120;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Audio::Synchronize) return true;
|
||||
if(name == Audio::Frequency) return true;
|
||||
if(name == Audio::Latency) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Audio::Synchronize) return settings.synchronize;
|
||||
if(name == Audio::Frequency) return settings.frequency;
|
||||
if(name == Audio::Latency) return settings.latency;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
if(pXAudio2) clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Frequency && value.is<unsigned>()) {
|
||||
settings.frequency = value.get<unsigned>();
|
||||
if(pXAudio2) init();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Latency && value.is<unsigned>()) {
|
||||
settings.latency = value.get<unsigned>();
|
||||
if(pXAudio2) init();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pushbuffer(unsigned bytes, uint32_t* pAudioData) -> void {
|
||||
XAUDIO2_BUFFER xa2buffer = {0};
|
||||
xa2buffer.AudioBytes = bytes;
|
||||
xa2buffer.pAudioData = reinterpret_cast<BYTE*>(pAudioData);
|
||||
xa2buffer.pContext = 0;
|
||||
InterlockedIncrement(&device.submitbuffers);
|
||||
pSourceVoice->SubmitSourceBuffer(&xa2buffer);
|
||||
}
|
||||
|
||||
auto sample(uint16_t left, uint16_t right) -> void {
|
||||
device.buffer[device.writebuffer * device.latency + device.bufferoffset++] = left + (right << 16);
|
||||
if(device.bufferoffset < device.latency) return;
|
||||
device.bufferoffset = 0;
|
||||
|
||||
if(device.submitbuffers == device.buffers - 1) {
|
||||
if(settings.synchronize == true) {
|
||||
//wait until there is at least one other free buffer for the next sample
|
||||
while(device.submitbuffers == device.buffers - 1) {
|
||||
//Sleep(0);
|
||||
}
|
||||
} else { //we need one free buffer for the next sample, so ignore the current contents
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pushbuffer(device.latency * 4,device.buffer + device.writebuffer * device.latency);
|
||||
|
||||
device.writebuffer = (device.writebuffer + 1) % device.buffers;
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
if(!pSourceVoice) return;
|
||||
pSourceVoice->Stop(0);
|
||||
pSourceVoice->FlushSourceBuffers(); //calls OnBufferEnd for all currently submitted buffers
|
||||
|
||||
device.writebuffer = 0;
|
||||
|
||||
device.bufferoffset = 0;
|
||||
if(device.buffer) memset(device.buffer, 0, device.latency * device.buffers * 4);
|
||||
|
||||
pSourceVoice->Start(0);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
device.buffers = 8;
|
||||
device.latency = settings.frequency * settings.latency / device.buffers / 1000.0 + 0.5;
|
||||
device.buffer = new uint32_t[device.latency * device.buffers];
|
||||
device.bufferoffset = 0;
|
||||
device.submitbuffers = 0;
|
||||
|
||||
HRESULT hr;
|
||||
if(FAILED(hr = XAudio2Create(&pXAudio2, 0 , XAUDIO2_DEFAULT_PROCESSOR))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned deviceCount = 0;
|
||||
pXAudio2->GetDeviceCount(&deviceCount);
|
||||
if(deviceCount == 0) { term(); return false; }
|
||||
|
||||
unsigned deviceID = 0;
|
||||
for(unsigned deviceIndex = 0; deviceIndex < deviceCount; deviceIndex++) {
|
||||
XAUDIO2_DEVICE_DETAILS deviceDetails;
|
||||
memset(&deviceDetails, 0, sizeof(XAUDIO2_DEVICE_DETAILS));
|
||||
pXAudio2->GetDeviceDetails(deviceIndex, &deviceDetails);
|
||||
if(deviceDetails.Role & DefaultGameDevice) deviceID = deviceIndex;
|
||||
}
|
||||
|
||||
if(FAILED(hr = pXAudio2->CreateMasteringVoice(&pMasterVoice, 2, settings.frequency, 0, deviceID, NULL))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WAVEFORMATEX wfx;
|
||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = 2;
|
||||
wfx.nSamplesPerSec = settings.frequency;
|
||||
wfx.nBlockAlign = 4;
|
||||
wfx.wBitsPerSample = 16;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
wfx.cbSize = 0;
|
||||
|
||||
if(FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, (WAVEFORMATEX*)&wfx, XAUDIO2_VOICE_NOSRC, XAUDIO2_DEFAULT_FREQ_RATIO, this, NULL, NULL))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(pSourceVoice) {
|
||||
pSourceVoice->Stop(0);
|
||||
pSourceVoice->DestroyVoice();
|
||||
pSourceVoice = nullptr;
|
||||
}
|
||||
if(pMasterVoice) {
|
||||
pMasterVoice->DestroyVoice();
|
||||
pMasterVoice = nullptr;
|
||||
}
|
||||
if(pXAudio2) {
|
||||
pXAudio2->Release();
|
||||
pXAudio2 = nullptr;
|
||||
}
|
||||
if(device.buffer) {
|
||||
delete[] device.buffer;
|
||||
device.buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
STDMETHODIMP_(void) OnBufferEnd(void* pBufferContext) {
|
||||
InterlockedDecrement(&device.submitbuffers);
|
||||
}
|
||||
};
|
340
audio/xaudio2.hpp
Normal file
340
audio/xaudio2.hpp
Normal file
|
@ -0,0 +1,340 @@
|
|||
/*
|
||||
xaudio2.hpp (2010-08-14)
|
||||
author: OV2
|
||||
|
||||
ruby-specific header to provide mingw-friendly xaudio2 interfaces
|
||||
*/
|
||||
|
||||
#ifndef XAUDIO2_RUBY_H
|
||||
#define XAUDIO2_RUBY_H
|
||||
|
||||
//64-bit GCC fix
|
||||
#define GUID_EXT EXTERN_C
|
||||
#define GUID_SECT
|
||||
|
||||
#include <BaseTyps.h>
|
||||
|
||||
#define DEFINE_GUID_X(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) GUID_EXT const GUID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
|
||||
#define DEFINE_CLSID_X(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
DEFINE_GUID_X(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8)
|
||||
#define DEFINE_IID_X(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
DEFINE_GUID_X(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8)
|
||||
#define X2DEFAULT(x) =x
|
||||
|
||||
DEFINE_CLSID_X(XAudio2, e21a7345, eb21, 468e, be, 50, 80, 4d, b9, 7c, f7, 08);
|
||||
DEFINE_CLSID_X(XAudio2_Debug, f7a76c21, 53d4, 46bb, ac, 53, 8b, 45, 9c, ae, 46, bd);
|
||||
DEFINE_IID_X(IXAudio2, 8bcf1f58, 9fe7, 4583, 8a, c6, e2, ad, c4, 65, c8, bb);
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2Voice);
|
||||
|
||||
#define XAUDIO2_COMMIT_NOW 0
|
||||
#define XAUDIO2_DEFAULT_CHANNELS 0
|
||||
#define XAUDIO2_DEFAULT_SAMPLERATE 0
|
||||
#define XAUDIO2_DEFAULT_FREQ_RATIO 4.0f
|
||||
#define XAUDIO2_DEBUG_ENGINE 0x0001
|
||||
#define XAUDIO2_VOICE_NOSRC 0x0004
|
||||
|
||||
typedef struct
|
||||
{
|
||||
WAVEFORMATEX Format;
|
||||
union
|
||||
{
|
||||
WORD wValidBitsPerSample;
|
||||
WORD wSamplesPerBlock;
|
||||
WORD wReserved;
|
||||
} Samples;
|
||||
DWORD dwChannelMask;
|
||||
GUID SubFormat;
|
||||
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE, *LPPWAVEFORMATEXTENSIBLE;
|
||||
typedef const WAVEFORMATEXTENSIBLE* LPCWAVEFORMATEXTENSIBLE;
|
||||
|
||||
typedef enum XAUDIO2_DEVICE_ROLE
|
||||
{
|
||||
NotDefaultDevice = 0x0,
|
||||
DefaultConsoleDevice = 0x1,
|
||||
DefaultMultimediaDevice = 0x2,
|
||||
DefaultCommunicationsDevice = 0x4,
|
||||
DefaultGameDevice = 0x8,
|
||||
GlobalDefaultDevice = 0xf,
|
||||
InvalidDeviceRole = ~GlobalDefaultDevice
|
||||
} XAUDIO2_DEVICE_ROLE;
|
||||
|
||||
typedef struct XAUDIO2_DEVICE_DETAILS
|
||||
{
|
||||
WCHAR DeviceID[256];
|
||||
WCHAR DisplayName[256];
|
||||
XAUDIO2_DEVICE_ROLE Role;
|
||||
WAVEFORMATEXTENSIBLE OutputFormat;
|
||||
} XAUDIO2_DEVICE_DETAILS;
|
||||
|
||||
typedef struct XAUDIO2_VOICE_DETAILS
|
||||
{
|
||||
UINT32 CreationFlags;
|
||||
UINT32 InputChannels;
|
||||
UINT32 InputSampleRate;
|
||||
} XAUDIO2_VOICE_DETAILS;
|
||||
|
||||
typedef enum XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER
|
||||
{
|
||||
Processor1 = 0x00000001,
|
||||
Processor2 = 0x00000002,
|
||||
Processor3 = 0x00000004,
|
||||
Processor4 = 0x00000008,
|
||||
Processor5 = 0x00000010,
|
||||
Processor6 = 0x00000020,
|
||||
Processor7 = 0x00000040,
|
||||
Processor8 = 0x00000080,
|
||||
Processor9 = 0x00000100,
|
||||
Processor10 = 0x00000200,
|
||||
Processor11 = 0x00000400,
|
||||
Processor12 = 0x00000800,
|
||||
Processor13 = 0x00001000,
|
||||
Processor14 = 0x00002000,
|
||||
Processor15 = 0x00004000,
|
||||
Processor16 = 0x00008000,
|
||||
Processor17 = 0x00010000,
|
||||
Processor18 = 0x00020000,
|
||||
Processor19 = 0x00040000,
|
||||
Processor20 = 0x00080000,
|
||||
Processor21 = 0x00100000,
|
||||
Processor22 = 0x00200000,
|
||||
Processor23 = 0x00400000,
|
||||
Processor24 = 0x00800000,
|
||||
Processor25 = 0x01000000,
|
||||
Processor26 = 0x02000000,
|
||||
Processor27 = 0x04000000,
|
||||
Processor28 = 0x08000000,
|
||||
Processor29 = 0x10000000,
|
||||
Processor30 = 0x20000000,
|
||||
Processor31 = 0x40000000,
|
||||
Processor32 = 0x80000000,
|
||||
XAUDIO2_ANY_PROCESSOR = 0xffffffff,
|
||||
XAUDIO2_DEFAULT_PROCESSOR = XAUDIO2_ANY_PROCESSOR
|
||||
} XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER, XAUDIO2_PROCESSOR;
|
||||
|
||||
typedef struct XAUDIO2_VOICE_SENDS
|
||||
{
|
||||
UINT32 OutputCount;
|
||||
IXAudio2Voice** pOutputVoices;
|
||||
} XAUDIO2_VOICE_SENDS;
|
||||
|
||||
typedef struct XAUDIO2_EFFECT_DESCRIPTOR
|
||||
{
|
||||
IUnknown* pEffect;
|
||||
BOOL InitialState;
|
||||
UINT32 OutputChannels;
|
||||
} XAUDIO2_EFFECT_DESCRIPTOR;
|
||||
|
||||
typedef struct XAUDIO2_EFFECT_CHAIN
|
||||
{
|
||||
UINT32 EffectCount;
|
||||
const XAUDIO2_EFFECT_DESCRIPTOR* pEffectDescriptors;
|
||||
} XAUDIO2_EFFECT_CHAIN;
|
||||
|
||||
typedef enum XAUDIO2_FILTER_TYPE
|
||||
{
|
||||
LowPassFilter,
|
||||
BandPassFilter,
|
||||
HighPassFilter
|
||||
} XAUDIO2_FILTER_TYPE;
|
||||
|
||||
typedef struct XAUDIO2_FILTER_PARAMETERS
|
||||
{
|
||||
XAUDIO2_FILTER_TYPE Type;
|
||||
float Frequency;
|
||||
float OneOverQ;
|
||||
|
||||
} XAUDIO2_FILTER_PARAMETERS;
|
||||
|
||||
typedef struct XAUDIO2_BUFFER
|
||||
{
|
||||
UINT32 Flags;
|
||||
UINT32 AudioBytes;
|
||||
const BYTE* pAudioData;
|
||||
UINT32 PlayBegin;
|
||||
UINT32 PlayLength;
|
||||
UINT32 LoopBegin;
|
||||
UINT32 LoopLength;
|
||||
UINT32 LoopCount;
|
||||
void* pContext;
|
||||
} XAUDIO2_BUFFER;
|
||||
|
||||
typedef struct XAUDIO2_BUFFER_WMA
|
||||
{
|
||||
const UINT32* pDecodedPacketCumulativeBytes;
|
||||
UINT32 PacketCount;
|
||||
} XAUDIO2_BUFFER_WMA;
|
||||
|
||||
typedef struct XAUDIO2_VOICE_STATE
|
||||
{
|
||||
void* pCurrentBufferContext;
|
||||
UINT32 BuffersQueued;
|
||||
UINT64 SamplesPlayed;
|
||||
} XAUDIO2_VOICE_STATE;
|
||||
|
||||
typedef struct XAUDIO2_PERFORMANCE_DATA
|
||||
{
|
||||
UINT64 AudioCyclesSinceLastQuery;
|
||||
UINT64 TotalCyclesSinceLastQuery;
|
||||
UINT32 MinimumCyclesPerQuantum;
|
||||
UINT32 MaximumCyclesPerQuantum;
|
||||
UINT32 MemoryUsageInBytes;
|
||||
UINT32 CurrentLatencyInSamples;
|
||||
UINT32 GlitchesSinceEngineStarted;
|
||||
UINT32 ActiveSourceVoiceCount;
|
||||
UINT32 TotalSourceVoiceCount;
|
||||
UINT32 ActiveSubmixVoiceCount;
|
||||
UINT32 TotalSubmixVoiceCount;
|
||||
UINT32 ActiveXmaSourceVoices;
|
||||
UINT32 ActiveXmaStreams;
|
||||
} XAUDIO2_PERFORMANCE_DATA;
|
||||
|
||||
typedef struct XAUDIO2_DEBUG_CONFIGURATION
|
||||
{
|
||||
UINT32 TraceMask;
|
||||
UINT32 BreakMask;
|
||||
BOOL LogThreadID;
|
||||
BOOL LogFileline;
|
||||
BOOL LogFunctionName;
|
||||
BOOL LogTiming;
|
||||
} XAUDIO2_DEBUG_CONFIGURATION;
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2EngineCallback)
|
||||
{
|
||||
STDMETHOD_(void, OnProcessingPassStart) (THIS) PURE;
|
||||
STDMETHOD_(void, OnProcessingPassEnd) (THIS) PURE;
|
||||
STDMETHOD_(void, OnCriticalError) (THIS_ HRESULT Error) PURE;
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2VoiceCallback)
|
||||
{
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart) (THIS_ UINT32 BytesRequired) PURE;
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd) (THIS) PURE;
|
||||
STDMETHOD_(void, OnStreamEnd) (THIS) PURE;
|
||||
STDMETHOD_(void, OnBufferStart) (THIS_ void* pBufferContext) PURE;
|
||||
STDMETHOD_(void, OnBufferEnd) (THIS_ void* pBufferContext) PURE;
|
||||
STDMETHOD_(void, OnLoopEnd) (THIS_ void* pBufferContext) PURE;
|
||||
STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) PURE;
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2Voice)
|
||||
{
|
||||
#define Declare_IXAudio2Voice_Methods() \
|
||||
STDMETHOD_(void, GetVoiceDetails) (THIS_ XAUDIO2_VOICE_DETAILS* pVoiceDetails) PURE; \
|
||||
STDMETHOD(SetOutputVoices) (THIS_ const XAUDIO2_VOICE_SENDS* pSendList) PURE; \
|
||||
STDMETHOD(SetEffectChain) (THIS_ const XAUDIO2_EFFECT_CHAIN* pEffectChain) PURE; \
|
||||
STDMETHOD(EnableEffect) (THIS_ UINT32 EffectIndex, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD(DisableEffect) (THIS_ UINT32 EffectIndex, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetEffectState) (THIS_ UINT32 EffectIndex, BOOL* pEnabled) PURE; \
|
||||
STDMETHOD(SetEffectParameters) (THIS_ UINT32 EffectIndex, \
|
||||
const void* pParameters, \
|
||||
UINT32 ParametersByteSize, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD(GetEffectParameters) (THIS_ UINT32 EffectIndex, void* pParameters, \
|
||||
UINT32 ParametersByteSize) PURE; \
|
||||
STDMETHOD(SetFilterParameters) (THIS_ const XAUDIO2_FILTER_PARAMETERS* pParameters, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetFilterParameters) (THIS_ XAUDIO2_FILTER_PARAMETERS* pParameters) PURE; \
|
||||
STDMETHOD(SetVolume) (THIS_ float Volume, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetVolume) (THIS_ float* pVolume) PURE; \
|
||||
STDMETHOD(SetChannelVolumes) (THIS_ UINT32 Channels, const float* pVolumes, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetChannelVolumes) (THIS_ UINT32 Channels, float* pVolumes) PURE; \
|
||||
STDMETHOD(SetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \
|
||||
UINT32 SourceChannels, UINT32 DestinationChannels, \
|
||||
const float* pLevelMatrix, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \
|
||||
UINT32 SourceChannels, UINT32 DestinationChannels, \
|
||||
float* pLevelMatrix) PURE; \
|
||||
STDMETHOD_(void, DestroyVoice) (THIS) PURE
|
||||
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
};
|
||||
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2MasteringVoice, IXAudio2Voice)
|
||||
{
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2SubmixVoice, IXAudio2Voice)
|
||||
{
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2SourceVoice, IXAudio2Voice)
|
||||
{
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
STDMETHOD(Start) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD(Stop) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD(SubmitSourceBuffer) (THIS_ const XAUDIO2_BUFFER* pBuffer, const XAUDIO2_BUFFER_WMA* pBufferWMA X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(FlushSourceBuffers) (THIS) PURE;
|
||||
STDMETHOD(Discontinuity) (THIS) PURE;
|
||||
STDMETHOD(ExitLoop) (THIS_ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD_(void, GetState) (THIS_ XAUDIO2_VOICE_STATE* pVoiceState) PURE;
|
||||
STDMETHOD(SetFrequencyRatio) (THIS_ float Ratio,
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD_(void, GetFrequencyRatio) (THIS_ float* pRatio) PURE;
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2, IUnknown)
|
||||
{
|
||||
STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvInterface) PURE;
|
||||
STDMETHOD_(ULONG, AddRef) (THIS) PURE;
|
||||
STDMETHOD_(ULONG, Release) (THIS) PURE;
|
||||
STDMETHOD(GetDeviceCount) (THIS_ UINT32* pCount) PURE;
|
||||
STDMETHOD(GetDeviceDetails) (THIS_ UINT32 Index, XAUDIO2_DEVICE_DETAILS* pDeviceDetails) PURE;
|
||||
STDMETHOD(Initialize) (THIS_ UINT32 Flags X2DEFAULT(0),
|
||||
XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) PURE;
|
||||
STDMETHOD(RegisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE;
|
||||
STDMETHOD_(void, UnregisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE;
|
||||
STDMETHOD(CreateSourceVoice) (THIS_ IXAudio2SourceVoice** ppSourceVoice,
|
||||
const WAVEFORMATEX* pSourceFormat,
|
||||
UINT32 Flags X2DEFAULT(0),
|
||||
float MaxFrequencyRatio X2DEFAULT(XAUDIO2_DEFAULT_FREQ_RATIO),
|
||||
IXAudio2VoiceCallback* pCallback X2DEFAULT(NULL),
|
||||
const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL),
|
||||
const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(CreateSubmixVoice) (THIS_ IXAudio2SubmixVoice** ppSubmixVoice,
|
||||
UINT32 InputChannels, UINT32 InputSampleRate,
|
||||
UINT32 Flags X2DEFAULT(0), UINT32 ProcessingStage X2DEFAULT(0),
|
||||
const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL),
|
||||
const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(CreateMasteringVoice) (THIS_ IXAudio2MasteringVoice** ppMasteringVoice,
|
||||
UINT32 InputChannels X2DEFAULT(XAUDIO2_DEFAULT_CHANNELS),
|
||||
UINT32 InputSampleRate X2DEFAULT(XAUDIO2_DEFAULT_SAMPLERATE),
|
||||
UINT32 Flags X2DEFAULT(0), UINT32 DeviceIndex X2DEFAULT(0),
|
||||
const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(StartEngine) (THIS) PURE;
|
||||
STDMETHOD_(void, StopEngine) (THIS) PURE;
|
||||
STDMETHOD(CommitChanges) (THIS_ UINT32 OperationSet) PURE;
|
||||
STDMETHOD_(void, GetPerformanceData) (THIS_ XAUDIO2_PERFORMANCE_DATA* pPerfData) PURE;
|
||||
STDMETHOD_(void, SetDebugConfiguration) (THIS_ const XAUDIO2_DEBUG_CONFIGURATION* pDebugConfiguration,
|
||||
void* pReserved X2DEFAULT(NULL)) PURE;
|
||||
};
|
||||
|
||||
__inline HRESULT XAudio2Create(IXAudio2** ppXAudio2, UINT32 Flags X2DEFAULT(0),
|
||||
XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR))
|
||||
{
|
||||
IXAudio2* pXAudio2;
|
||||
HRESULT hr = CoCreateInstance((Flags & XAUDIO2_DEBUG_ENGINE) ? CLSID_XAudio2_Debug : CLSID_XAudio2,
|
||||
NULL, CLSCTX_INPROC_SERVER, IID_IXAudio2, (void**)&pXAudio2);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = pXAudio2->Initialize(Flags, XAudio2Processor);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
*ppXAudio2 = pXAudio2;
|
||||
}
|
||||
else
|
||||
{
|
||||
pXAudio2->Release();
|
||||
}
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
#endif
|
43
input/carbon.cpp
Normal file
43
input/carbon.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#include "keyboard/carbon.cpp"
|
||||
|
||||
struct InputCarbon : Input {
|
||||
InputKeyboardCarbon carbonKeyboard;
|
||||
InputCarbon() : carbonKeyboard(*this) {}
|
||||
~InputCarbon() { term(); }
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Input::KeyboardSupport) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto acquire() -> bool { return false; }
|
||||
auto release() -> bool { return false; }
|
||||
auto acquired() -> bool { return false; }
|
||||
|
||||
auto poll() -> vector<shared_pointer<HID::Device>> {
|
||||
vector<shared_pointer<HID::Device>> devices;
|
||||
carbonKeyboard.poll(devices);
|
||||
return devices;
|
||||
}
|
||||
|
||||
auto rumble(uint64 id, bool enable) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
if(!carbonKeyboard.init()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
carbonKeyboard.term();
|
||||
}
|
||||
};
|
217
input/joypad/directinput.cpp
Normal file
217
input/joypad/directinput.cpp
Normal file
|
@ -0,0 +1,217 @@
|
|||
#ifndef RUBY_INPUT_JOYPAD_DIRECTINPUT
|
||||
#define RUBY_INPUT_JOYPAD_DIRECTINPUT
|
||||
|
||||
auto CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) -> BOOL;
|
||||
auto CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL;
|
||||
auto CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL;
|
||||
|
||||
struct InputJoypadDirectInput {
|
||||
Input& input;
|
||||
InputJoypadDirectInput(Input& input) : input(input) {}
|
||||
|
||||
struct Joypad {
|
||||
shared_pointer<HID::Joypad> hid{new HID::Joypad};
|
||||
|
||||
LPDIRECTINPUTDEVICE8 device = nullptr;
|
||||
LPDIRECTINPUTEFFECT effect = nullptr;
|
||||
|
||||
uint32_t pathID = 0;
|
||||
uint16_t vendorID = 0;
|
||||
uint16_t productID = 0;
|
||||
bool isXInputDevice = false;
|
||||
};
|
||||
vector<Joypad> joypads;
|
||||
|
||||
uintptr_t handle = 0;
|
||||
LPDIRECTINPUT8 context = nullptr;
|
||||
LPDIRECTINPUTDEVICE8 device = nullptr;
|
||||
bool xinputAvailable = false;
|
||||
unsigned effects = 0;
|
||||
|
||||
auto assign(shared_pointer<HID::Joypad> hid, unsigned groupID, unsigned inputID, int16_t value) -> void {
|
||||
auto& group = hid->group(groupID);
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
}
|
||||
|
||||
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
|
||||
for(auto& jp : joypads) {
|
||||
if(FAILED(jp.device->Poll())) jp.device->Acquire();
|
||||
|
||||
DIJOYSTATE2 state;
|
||||
if(FAILED(jp.device->GetDeviceState(sizeof(DIJOYSTATE2), &state))) continue;
|
||||
|
||||
for(unsigned n = 0; n < 4; n++) {
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, 0, state.lX);
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, 1, state.lY);
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, 2, state.lZ);
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, 3, state.lRx);
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, 4, state.lRy);
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, 5, state.lRz);
|
||||
|
||||
unsigned pov = state.rgdwPOV[n];
|
||||
int16_t xaxis = 0;
|
||||
int16_t yaxis = 0;
|
||||
|
||||
if(pov < 36000) {
|
||||
if(pov >= 31500 || pov <= 4500) yaxis = -32768;
|
||||
if(pov >= 4500 && pov <= 13500) xaxis = +32767;
|
||||
if(pov >= 13500 && pov <= 22500) yaxis = +32767;
|
||||
if(pov >= 22500 && pov <= 31500) xaxis = -32768;
|
||||
}
|
||||
|
||||
assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 0, xaxis);
|
||||
assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 1, yaxis);
|
||||
}
|
||||
|
||||
for(unsigned n = 0; n < 128; n++) {
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, n, (bool)state.rgbButtons[n]);
|
||||
}
|
||||
|
||||
devices.append(jp.hid);
|
||||
}
|
||||
}
|
||||
|
||||
auto rumble(uint64_t id, bool enable) -> bool {
|
||||
for(auto& jp : joypads) {
|
||||
if(jp.hid->id() != id) continue;
|
||||
if(jp.effect == nullptr) continue;
|
||||
|
||||
if(enable) jp.effect->Start(1, 0);
|
||||
else jp.effect->Stop();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto init(uintptr_t handle, LPDIRECTINPUT8 context, bool xinputAvailable) -> bool {
|
||||
this->handle = handle;
|
||||
this->context = context;
|
||||
this->xinputAvailable = xinputAvailable;
|
||||
context->EnumDevices(DI8DEVCLASS_GAMECTRL, DirectInput_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
for(auto& jp : joypads) {
|
||||
jp.device->Unacquire();
|
||||
if(jp.effect) jp.effect->Release();
|
||||
jp.device->Release();
|
||||
}
|
||||
joypads.reset();
|
||||
context = nullptr;
|
||||
}
|
||||
|
||||
auto initJoypad(const DIDEVICEINSTANCE* instance) -> bool {
|
||||
Joypad jp;
|
||||
jp.vendorID = instance->guidProduct.Data1 >> 0;
|
||||
jp.productID = instance->guidProduct.Data1 >> 16;
|
||||
jp.isXInputDevice = false;
|
||||
if(auto device = rawinput.find(jp.vendorID, jp.productID)) {
|
||||
jp.isXInputDevice = device().isXInputDevice;
|
||||
}
|
||||
|
||||
//Microsoft has intentionally imposed artificial restrictions on XInput devices when used with DirectInput
|
||||
//a) the two triggers are merged into a single axis, making uniquely distinguishing them impossible
|
||||
//b) rumble support is not exposed
|
||||
//thus, it's always preferred to let the XInput driver handle these joypads
|
||||
//but if the driver is not available (XInput 1.3 does not ship with stock Windows XP), fall back on DirectInput
|
||||
if(jp.isXInputDevice && xinputAvailable) return DIENUM_CONTINUE;
|
||||
|
||||
if(FAILED(context->CreateDevice(instance->guidInstance, &device, 0))) return DIENUM_CONTINUE;
|
||||
jp.device = device;
|
||||
device->SetDataFormat(&c_dfDIJoystick2);
|
||||
device->SetCooperativeLevel((HWND)handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
|
||||
|
||||
effects = 0;
|
||||
device->EnumObjects(DirectInput_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS);
|
||||
device->EnumObjects(DirectInput_EnumJoypadEffectsCallback, (void*)this, DIDFT_FFACTUATOR);
|
||||
jp.hid->setRumble(effects > 0);
|
||||
|
||||
DIPROPGUIDANDPATH property;
|
||||
memset(&property, 0, sizeof(DIPROPGUIDANDPATH));
|
||||
property.diph.dwSize = sizeof(DIPROPGUIDANDPATH);
|
||||
property.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
||||
property.diph.dwObj = 0;
|
||||
property.diph.dwHow = DIPH_DEVICE;
|
||||
device->GetProperty(DIPROP_GUIDANDPATH, &property.diph);
|
||||
string devicePath = (const char*)utf8_t(property.wszPath);
|
||||
jp.pathID = Hash::CRC32(devicePath.data(), devicePath.size()).value();
|
||||
jp.hid->setID((uint64_t)jp.pathID << 32 | jp.vendorID << 16 | jp.productID << 0);
|
||||
|
||||
if(jp.hid->rumble()) {
|
||||
//disable auto-centering spring for rumble support
|
||||
DIPROPDWORD property;
|
||||
memset(&property, 0, sizeof(DIPROPDWORD));
|
||||
property.diph.dwSize = sizeof(DIPROPDWORD);
|
||||
property.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
||||
property.diph.dwObj = 0;
|
||||
property.diph.dwHow = DIPH_DEVICE;
|
||||
property.dwData = false;
|
||||
device->SetProperty(DIPROP_AUTOCENTER, &property.diph);
|
||||
|
||||
DWORD dwAxes[2] = {(DWORD)DIJOFS_X, (DWORD)DIJOFS_Y};
|
||||
LONG lDirection[2] = {0, 0};
|
||||
DICONSTANTFORCE force;
|
||||
force.lMagnitude = DI_FFNOMINALMAX; //full force
|
||||
DIEFFECT effect;
|
||||
memset(&effect, 0, sizeof(DIEFFECT));
|
||||
effect.dwSize = sizeof(DIEFFECT);
|
||||
effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
|
||||
effect.dwDuration = INFINITE;
|
||||
effect.dwSamplePeriod = 0;
|
||||
effect.dwGain = DI_FFNOMINALMAX;
|
||||
effect.dwTriggerButton = DIEB_NOTRIGGER;
|
||||
effect.dwTriggerRepeatInterval = 0;
|
||||
effect.cAxes = 2;
|
||||
effect.rgdwAxes = dwAxes;
|
||||
effect.rglDirection = lDirection;
|
||||
effect.lpEnvelope = 0;
|
||||
effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
|
||||
effect.lpvTypeSpecificParams = &force;
|
||||
effect.dwStartDelay = 0;
|
||||
device->CreateEffect(GUID_ConstantForce, &effect, &jp.effect, NULL);
|
||||
}
|
||||
|
||||
for(unsigned n = 0; n < 6; n++) jp.hid->axes().append(n);
|
||||
for(unsigned n = 0; n < 8; n++) jp.hid->hats().append(n);
|
||||
for(unsigned n = 0; n < 128; n++) jp.hid->buttons().append(n);
|
||||
joypads.append(jp);
|
||||
|
||||
return DIENUM_CONTINUE;
|
||||
}
|
||||
|
||||
auto initAxis(const DIDEVICEOBJECTINSTANCE* instance) -> bool {
|
||||
DIPROPRANGE range;
|
||||
memset(&range, 0, sizeof(DIPROPRANGE));
|
||||
range.diph.dwSize = sizeof(DIPROPRANGE);
|
||||
range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
||||
range.diph.dwHow = DIPH_BYID;
|
||||
range.diph.dwObj = instance->dwType;
|
||||
range.lMin = -32768;
|
||||
range.lMax = +32767;
|
||||
device->SetProperty(DIPROP_RANGE, &range.diph);
|
||||
return DIENUM_CONTINUE;
|
||||
}
|
||||
|
||||
auto initEffect(const DIDEVICEOBJECTINSTANCE* instance) -> bool {
|
||||
effects++;
|
||||
return DIENUM_CONTINUE;
|
||||
}
|
||||
};
|
||||
|
||||
auto CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) -> BOOL {
|
||||
return ((InputJoypadDirectInput*)p)->initJoypad(instance);
|
||||
}
|
||||
|
||||
auto CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL {
|
||||
return ((InputJoypadDirectInput*)p)->initAxis(instance);
|
||||
}
|
||||
|
||||
auto CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL {
|
||||
return ((InputJoypadDirectInput*)p)->initEffect(instance);
|
||||
}
|
||||
|
||||
#endif
|
79
input/joypad/sdl.cpp
Normal file
79
input/joypad/sdl.cpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
#ifndef RUBY_INPUT_JOYPAD_SDL
|
||||
#define RUBY_INPUT_JOYPAD_SDL
|
||||
|
||||
struct InputJoypadSDL {
|
||||
Input& input;
|
||||
InputJoypadSDL(Input& input) : input(input) {}
|
||||
|
||||
struct Joypad {
|
||||
shared_pointer<HID::Joypad> hid{new HID::Joypad};
|
||||
|
||||
unsigned id = 0;
|
||||
SDL_Joystick* handle = nullptr;
|
||||
};
|
||||
vector<Joypad> joypads;
|
||||
|
||||
auto assign(shared_pointer<HID::Joypad> hid, unsigned groupID, unsigned inputID, int16_t value) -> void {
|
||||
auto& group = hid->group(groupID);
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
}
|
||||
|
||||
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
|
||||
SDL_JoystickUpdate();
|
||||
|
||||
for(auto& jp : joypads) {
|
||||
for(auto n : range(jp.hid->axes())) {
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, n, (int16_t)SDL_JoystickGetAxis(jp.handle, n));
|
||||
}
|
||||
|
||||
for(signed n = 0; n < (signed)jp.hid->hats().size() - 1; n += 2) {
|
||||
uint8_t state = SDL_JoystickGetHat(jp.handle, n >> 1);
|
||||
assign(jp.hid, HID::Joypad::GroupID::Hat, n + 0, state & SDL_HAT_LEFT ? -32768 : state & SDL_HAT_RIGHT ? +32767 : 0);
|
||||
assign(jp.hid, HID::Joypad::GroupID::Hat, n + 1, state & SDL_HAT_UP ? -32768 : state & SDL_HAT_DOWN ? +32767 : 0);
|
||||
}
|
||||
|
||||
for(auto n : range(jp.hid->buttons())) {
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, n, (bool)SDL_JoystickGetButton(jp.handle, n));
|
||||
}
|
||||
|
||||
devices.append(jp.hid);
|
||||
}
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
SDL_InitSubSystem(SDL_INIT_JOYSTICK);
|
||||
SDL_JoystickEventState(SDL_IGNORE);
|
||||
|
||||
for(auto id : range(SDL_NumJoysticks())) {
|
||||
Joypad jp;
|
||||
jp.id = id;
|
||||
jp.handle = SDL_JoystickOpen(id);
|
||||
|
||||
unsigned axes = SDL_JoystickNumAxes(jp.handle);
|
||||
unsigned hats = SDL_JoystickNumHats(jp.handle) * 2;
|
||||
unsigned buttons = 32; //there is no SDL_JoystickNumButtons()
|
||||
|
||||
jp.hid->setID(3 + jp.id);
|
||||
for(auto n : range(axes)) jp.hid->axes().append(n);
|
||||
for(auto n : range(hats)) jp.hid->hats().append(n);
|
||||
for(auto n : range(buttons)) jp.hid->buttons().append(n);
|
||||
jp.hid->setRumble(false);
|
||||
|
||||
joypads.append(jp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
for(auto& jp : joypads) {
|
||||
SDL_JoystickClose(jp.handle);
|
||||
}
|
||||
joypads.reset();
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
279
input/joypad/udev.cpp
Normal file
279
input/joypad/udev.cpp
Normal file
|
@ -0,0 +1,279 @@
|
|||
#ifndef RUBY_INPUT_JOYPAD_UDEV
|
||||
#define RUBY_INPUT_JOYPAD_UDEV
|
||||
|
||||
struct InputJoypadUdev {
|
||||
Input& input;
|
||||
InputJoypadUdev(Input& input) : input(input) {}
|
||||
|
||||
udev* context = nullptr;
|
||||
udev_monitor* monitor = nullptr;
|
||||
udev_enumerate* enumerator = nullptr;
|
||||
udev_list_entry* devices = nullptr;
|
||||
udev_list_entry* item = nullptr;
|
||||
|
||||
struct JoypadInput {
|
||||
signed code = 0;
|
||||
unsigned id = 0;
|
||||
int16_t value = 0;
|
||||
input_absinfo info;
|
||||
|
||||
JoypadInput() {}
|
||||
JoypadInput(signed code) : code(code) {}
|
||||
JoypadInput(signed code, unsigned id) : code(code), id(id) {}
|
||||
bool operator< (const JoypadInput& source) const { return code < source.code; }
|
||||
bool operator==(const JoypadInput& source) const { return code == source.code; }
|
||||
};
|
||||
|
||||
struct Joypad {
|
||||
shared_pointer<HID::Joypad> hid{new HID::Joypad};
|
||||
|
||||
int fd = -1;
|
||||
dev_t device = 0;
|
||||
string deviceName;
|
||||
string deviceNode;
|
||||
|
||||
uint8_t evbit[(EV_MAX + 7) / 8] = {0};
|
||||
uint8_t keybit[(KEY_MAX + 7) / 8] = {0};
|
||||
uint8_t absbit[(ABS_MAX + 7) / 8] = {0};
|
||||
uint8_t ffbit[(FF_MAX + 7) / 8] = {0};
|
||||
unsigned effects = 0;
|
||||
|
||||
string name;
|
||||
string manufacturer;
|
||||
string product;
|
||||
string serial;
|
||||
string vendorID;
|
||||
string productID;
|
||||
|
||||
set<JoypadInput> axes;
|
||||
set<JoypadInput> hats;
|
||||
set<JoypadInput> buttons;
|
||||
bool rumble = false;
|
||||
unsigned effectID = 0;
|
||||
};
|
||||
vector<Joypad> joypads;
|
||||
|
||||
auto assign(shared_pointer<HID::Joypad> hid, unsigned groupID, unsigned inputID, int16_t value) -> void {
|
||||
auto& group = hid->group(groupID);
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
}
|
||||
|
||||
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
|
||||
while(hotplugDevicesAvailable()) hotplugDevice();
|
||||
|
||||
for(auto& jp : joypads) {
|
||||
input_event events[32];
|
||||
signed length = 0;
|
||||
while((length = read(jp.fd, events, sizeof(events))) > 0) {
|
||||
length /= sizeof(input_event);
|
||||
for(unsigned i = 0; i < length; i++) {
|
||||
signed code = events[i].code;
|
||||
signed type = events[i].type;
|
||||
signed value = events[i].value;
|
||||
|
||||
if(type == EV_ABS) {
|
||||
if(auto input = jp.axes.find({code})) {
|
||||
signed range = input().info.maximum - input().info.minimum;
|
||||
value = (value - input().info.minimum) * 65535ll / range - 32767;
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, input().id, sclamp<16>(value));
|
||||
} else if(auto input = jp.hats.find({code})) {
|
||||
signed range = input().info.maximum - input().info.minimum;
|
||||
value = (value - input().info.minimum) * 65535ll / range - 32767;
|
||||
assign(jp.hid, HID::Joypad::GroupID::Hat, input().id, sclamp<16>(value));
|
||||
}
|
||||
} else if(type == EV_KEY) {
|
||||
if(code >= BTN_MISC) {
|
||||
if(auto input = jp.buttons.find({code})) {
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, input().id, (bool)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
devices.append(jp.hid);
|
||||
}
|
||||
}
|
||||
|
||||
auto rumble(uint64_t id, bool enable) -> bool {
|
||||
for(auto& jp : joypads) {
|
||||
if(jp.hid->id() != id) continue;
|
||||
if(!jp.hid->rumble()) continue;
|
||||
|
||||
input_event play;
|
||||
memset(&play, 0, sizeof(input_event));
|
||||
play.type = EV_FF;
|
||||
play.code = jp.effectID;
|
||||
play.value = enable;
|
||||
auto unused = write(jp.fd, &play, sizeof(input_event));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
context = udev_new();
|
||||
if(context == nullptr) return false;
|
||||
|
||||
monitor = udev_monitor_new_from_netlink(context, "udev");
|
||||
if(monitor) {
|
||||
udev_monitor_filter_add_match_subsystem_devtype(monitor, "input", nullptr);
|
||||
udev_monitor_enable_receiving(monitor);
|
||||
}
|
||||
|
||||
enumerator = udev_enumerate_new(context);
|
||||
if(enumerator) {
|
||||
udev_enumerate_add_match_property(enumerator, "ID_INPUT_JOYSTICK", "1");
|
||||
udev_enumerate_scan_devices(enumerator);
|
||||
devices = udev_enumerate_get_list_entry(enumerator);
|
||||
for(udev_list_entry* item = devices; item != nullptr; item = udev_list_entry_get_next(item)) {
|
||||
string name = udev_list_entry_get_name(item);
|
||||
udev_device* device = udev_device_new_from_syspath(context, name);
|
||||
string deviceNode = udev_device_get_devnode(device);
|
||||
if(deviceNode) createJoypad(device, deviceNode);
|
||||
udev_device_unref(device);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(enumerator) { udev_enumerate_unref(enumerator); enumerator = nullptr; }
|
||||
}
|
||||
|
||||
private:
|
||||
auto hotplugDevicesAvailable() -> bool {
|
||||
pollfd fd = {0};
|
||||
fd.fd = udev_monitor_get_fd(monitor);
|
||||
fd.events = POLLIN;
|
||||
return (::poll(&fd, 1, 0) == 1) && (fd.revents & POLLIN);
|
||||
}
|
||||
|
||||
auto hotplugDevice() -> void {
|
||||
udev_device* device = udev_monitor_receive_device(monitor);
|
||||
if(device == nullptr) return;
|
||||
|
||||
string value = udev_device_get_property_value(device, "ID_INPUT_JOYSTICK");
|
||||
string action = udev_device_get_action(device);
|
||||
string deviceNode = udev_device_get_devnode(device);
|
||||
if(value == "1") {
|
||||
if(action == "add") {
|
||||
createJoypad(device, deviceNode);
|
||||
}
|
||||
if(action == "remove") {
|
||||
removeJoypad(device, deviceNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto createJoypad(udev_device* device, const string& deviceNode) -> void {
|
||||
Joypad jp;
|
||||
jp.deviceNode = deviceNode;
|
||||
|
||||
struct stat st;
|
||||
if(stat(deviceNode, &st) < 0) return;
|
||||
jp.device = st.st_rdev;
|
||||
|
||||
jp.fd = open(deviceNode, O_RDWR | O_NONBLOCK);
|
||||
if(jp.fd < 0) return;
|
||||
|
||||
uint8_t evbit[(EV_MAX + 7) / 8] = {0};
|
||||
uint8_t keybit[(KEY_MAX + 7) / 8] = {0};
|
||||
uint8_t absbit[(ABS_MAX + 7) / 8] = {0};
|
||||
|
||||
ioctl(jp.fd, EVIOCGBIT(0, sizeof(jp.evbit)), jp.evbit);
|
||||
ioctl(jp.fd, EVIOCGBIT(EV_KEY, sizeof(jp.keybit)), jp.keybit);
|
||||
ioctl(jp.fd, EVIOCGBIT(EV_ABS, sizeof(jp.absbit)), jp.absbit);
|
||||
ioctl(jp.fd, EVIOCGBIT(EV_FF, sizeof(jp.ffbit)), jp.ffbit);
|
||||
ioctl(jp.fd, EVIOCGEFFECTS, &jp.effects);
|
||||
|
||||
#define testBit(buffer, bit) (buffer[(bit) >> 3] & 1 << ((bit) & 7))
|
||||
|
||||
if(testBit(jp.evbit, EV_KEY)) {
|
||||
if(udev_device* parent = udev_device_get_parent_with_subsystem_devtype(device, "input", nullptr)) {
|
||||
jp.name = udev_device_get_sysattr_value(parent, "name");
|
||||
jp.vendorID = udev_device_get_sysattr_value(parent, "id/vendor");
|
||||
jp.productID = udev_device_get_sysattr_value(parent, "id/product");
|
||||
if(udev_device* root = udev_device_get_parent_with_subsystem_devtype(parent, "usb", "usb_device")) {
|
||||
if(jp.vendorID == udev_device_get_sysattr_value(root, "idVendor")
|
||||
&& jp.productID == udev_device_get_sysattr_value(root, "idProduct")
|
||||
) {
|
||||
jp.deviceName = udev_device_get_devpath(root);
|
||||
jp.manufacturer = udev_device_get_sysattr_value(root, "manufacturer");
|
||||
jp.product = udev_device_get_sysattr_value(root, "product");
|
||||
jp.serial = udev_device_get_sysattr_value(root, "serial");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned axes = 0;
|
||||
unsigned hats = 0;
|
||||
unsigned buttons = 0;
|
||||
for(signed i = 0; i < ABS_MISC; i++) {
|
||||
if(testBit(jp.absbit, i)) {
|
||||
if(i >= ABS_HAT0X && i <= ABS_HAT3Y) {
|
||||
if(auto hat = jp.hats.insert({i, hats++})) {
|
||||
ioctl(jp.fd, EVIOCGABS(i), &hat().info);
|
||||
}
|
||||
} else {
|
||||
if(auto axis = jp.axes.insert({i, axes++})) {
|
||||
ioctl(jp.fd, EVIOCGABS(i), &axis().info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for(signed i = BTN_JOYSTICK; i < KEY_MAX; i++) {
|
||||
if(testBit(jp.keybit, i)) {
|
||||
jp.buttons.insert({i, buttons++});
|
||||
}
|
||||
}
|
||||
for(signed i = BTN_MISC; i < BTN_JOYSTICK; i++) {
|
||||
if(testBit(jp.keybit, i)) {
|
||||
jp.buttons.insert({i, buttons++});
|
||||
}
|
||||
}
|
||||
jp.rumble = jp.effects >= 2 && testBit(jp.ffbit, FF_RUMBLE);
|
||||
if(jp.rumble) {
|
||||
ff_effect effect;
|
||||
memset(&effect, 0, sizeof(ff_effect));
|
||||
effect.type = FF_RUMBLE;
|
||||
effect.id = -1;
|
||||
effect.u.rumble.strong_magnitude = 65535;
|
||||
effect.u.rumble.weak_magnitude = 65535;
|
||||
ioctl(jp.fd, EVIOCSFF, &effect);
|
||||
jp.effectID = effect.id;
|
||||
}
|
||||
|
||||
createJoypadHID(jp);
|
||||
joypads.append(jp);
|
||||
}
|
||||
|
||||
#undef testBit
|
||||
}
|
||||
|
||||
auto createJoypadHID(Joypad& jp) -> void {
|
||||
uint64_t pathID = Hash::CRC32(jp.deviceName.data(), jp.deviceName.size()).value();
|
||||
jp.hid->setID(pathID << 32 | hex(jp.vendorID) << 16 | hex(jp.productID) << 0);
|
||||
|
||||
for(unsigned n = 0; n < jp.axes.size(); n++) jp.hid->axes().append(n);
|
||||
for(unsigned n = 0; n < jp.hats.size(); n++) jp.hid->hats().append(n);
|
||||
for(unsigned n = 0; n < jp.buttons.size(); n++) jp.hid->buttons().append(n);
|
||||
jp.hid->setRumble(jp.rumble);
|
||||
}
|
||||
|
||||
auto removeJoypad(udev_device* device, const string& deviceNode) -> void {
|
||||
for(unsigned n = 0; n < joypads.size(); n++) {
|
||||
if(joypads[n].deviceNode == deviceNode) {
|
||||
close(joypads[n].fd);
|
||||
joypads.remove(n);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
162
input/joypad/xinput.cpp
Normal file
162
input/joypad/xinput.cpp
Normal file
|
@ -0,0 +1,162 @@
|
|||
#ifndef RUBY_INPUT_JOYPAD_XINPUT
|
||||
#define RUBY_INPUT_JOYPAD_XINPUT
|
||||
|
||||
//documented functionality
|
||||
#define oXInputGetState "XInputGetState"
|
||||
#define oXInputSetState "XInputSetState"
|
||||
typedef DWORD WINAPI (*pXInputGetState)(DWORD dwUserIndex, XINPUT_STATE* pState);
|
||||
typedef DWORD WINAPI (*pXInputSetState)(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration);
|
||||
|
||||
//undocumented functionality
|
||||
#define oXInputGetStateEx (LPCSTR)100
|
||||
#define oXInputWaitForGuideButton (LPCSTR)101
|
||||
#define oXInputCancelGuideButtonWait (LPCSTR)102
|
||||
#define oXInputPowerOffController (LPCSTR)103
|
||||
typedef DWORD WINAPI (*pXInputGetStateEx)(DWORD dwUserIndex, XINPUT_STATE* pState);
|
||||
typedef DWORD WINAPI (*pXInputWaitForGuideButton)(DWORD dwUserIndex, DWORD dwFlag, void* pUnknown);
|
||||
typedef DWORD WINAPI (*pXInputCancelGuideButtonWait)(DWORD dwUserIndex);
|
||||
typedef DWORD WINAPI (*pXInputPowerOffController)(DWORD dwUserIndex);
|
||||
|
||||
#define XINPUT_GAMEPAD_GUIDE 0x0400
|
||||
|
||||
struct InputJoypadXInput {
|
||||
Input& input;
|
||||
InputJoypadXInput(Input& input) : input(input) {}
|
||||
|
||||
HMODULE libxinput = nullptr;
|
||||
pXInputGetStateEx XInputGetStateEx = nullptr;
|
||||
pXInputSetState XInputSetState = nullptr;
|
||||
|
||||
struct Joypad {
|
||||
shared_pointer<HID::Joypad> hid{new HID::Joypad};
|
||||
unsigned id = 0;
|
||||
};
|
||||
vector<Joypad> joypads;
|
||||
|
||||
auto assign(shared_pointer<HID::Joypad> hid, unsigned groupID, unsigned inputID, int16_t value) -> void {
|
||||
auto& group = hid->group(groupID);
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
}
|
||||
|
||||
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
|
||||
for(auto& jp : joypads) {
|
||||
XINPUT_STATE state;
|
||||
if(XInputGetStateEx(jp.id, &state) != ERROR_SUCCESS) continue;
|
||||
|
||||
//flip vertical axes so that -32768 = up, +32767 = down
|
||||
uint16_t axisLY = 32768 + state.Gamepad.sThumbLY;
|
||||
uint16_t axisRY = 32768 + state.Gamepad.sThumbRY;
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, 0, (int16_t)state.Gamepad.sThumbLX);
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, 1, (int16_t)(~axisLY - 32768));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, 2, (int16_t)state.Gamepad.sThumbRX);
|
||||
assign(jp.hid, HID::Joypad::GroupID::Axis, 3, (int16_t)(~axisRY - 32768));
|
||||
|
||||
int16_t hatX = 0;
|
||||
int16_t hatY = 0;
|
||||
if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ) hatY = -32768;
|
||||
if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) hatY = +32767;
|
||||
if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) hatX = -32768;
|
||||
if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) hatX = +32767;
|
||||
|
||||
assign(jp.hid, HID::Joypad::GroupID::Hat, 0, hatX);
|
||||
assign(jp.hid, HID::Joypad::GroupID::Hat, 1, hatY);
|
||||
|
||||
//scale trigger ranges for up to down from (0 to 255) to (-32768 to +32767)
|
||||
uint16_t triggerL = state.Gamepad.bLeftTrigger;
|
||||
uint16_t triggerR = state.Gamepad.bRightTrigger;
|
||||
triggerL = triggerL << 8 | triggerL << 0;
|
||||
triggerR = triggerR << 8 | triggerR << 0;
|
||||
|
||||
assign(jp.hid, HID::Joypad::GroupID::Trigger, 0, (int16_t)(triggerL - 32768));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Trigger, 1, (int16_t)(triggerR - 32768));
|
||||
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, 0, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_A));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, 1, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_B));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, 2, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_X));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, 3, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, 4, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, 5, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_START));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, 6, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, 7, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, 8, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, 9, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB));
|
||||
assign(jp.hid, HID::Joypad::GroupID::Button, 10, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE));
|
||||
|
||||
devices.append(jp.hid);
|
||||
}
|
||||
}
|
||||
|
||||
auto rumble(uint64_t id, bool enable) -> bool {
|
||||
for(auto& jp : joypads) {
|
||||
if(jp.hid->id() != id) continue;
|
||||
|
||||
XINPUT_VIBRATION vibration;
|
||||
memset(&vibration, 0, sizeof(XINPUT_VIBRATION));
|
||||
vibration.wLeftMotorSpeed = enable ? 65535 : 0; //low-frequency motor (0 = off, 65535 = max)
|
||||
vibration.wRightMotorSpeed = enable ? 65535 : 0; //high-frequency motor (0 = off, 65535 = max)
|
||||
XInputSetState(jp.id, &vibration);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
if(!libxinput) libxinput = LoadLibraryA("xinput1_3.dll");
|
||||
if(!libxinput) return false;
|
||||
|
||||
//XInputGetStateEx is an undocumented function; but is required to get the state of the guide button
|
||||
//if for some reason it is not available, fall back on XInputGetState, which takes the same parameters
|
||||
XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetStateEx);
|
||||
XInputSetState = (pXInputSetState)GetProcAddress(libxinput, oXInputSetState);
|
||||
if(!XInputGetStateEx) XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetState);
|
||||
if(!XInputGetStateEx || !XInputSetState) return term(), 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
|
||||
for(unsigned id = 0; id < 4; id++) {
|
||||
Joypad jp;
|
||||
jp.id = id;
|
||||
jp.hid->setID((uint64_t)(1 + id) << 32 | 0x045e << 16 | 0x028e << 0); //Xbox 360 Player# + VendorID + ProductID
|
||||
jp.hid->setRumble(true);
|
||||
|
||||
jp.hid->axes().append("LeftThumbX");
|
||||
jp.hid->axes().append("LeftThumbY");
|
||||
jp.hid->axes().append("RightThumbX");
|
||||
jp.hid->axes().append("RightThumbY");
|
||||
|
||||
jp.hid->hats().append("HatX");
|
||||
jp.hid->hats().append("HatY");
|
||||
|
||||
jp.hid->triggers().append("LeftTrigger");
|
||||
jp.hid->triggers().append("RightTrigger");
|
||||
|
||||
jp.hid->buttons().append("A");
|
||||
jp.hid->buttons().append("B");
|
||||
jp.hid->buttons().append("X");
|
||||
jp.hid->buttons().append("Y");
|
||||
jp.hid->buttons().append("Back");
|
||||
jp.hid->buttons().append("Start");
|
||||
jp.hid->buttons().append("LeftShoulder");
|
||||
jp.hid->buttons().append("RightShoulder");
|
||||
jp.hid->buttons().append("LeftThumb");
|
||||
jp.hid->buttons().append("RightThumb");
|
||||
jp.hid->buttons().append("Guide");
|
||||
|
||||
joypads.append(jp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(!libxinput) return;
|
||||
|
||||
FreeLibrary(libxinput);
|
||||
libxinput = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
158
input/keyboard/carbon.cpp
Normal file
158
input/keyboard/carbon.cpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
struct InputKeyboardCarbon {
|
||||
Input& input;
|
||||
InputKeyboardCarbon(Input& input) : input(input) {}
|
||||
|
||||
shared_pointer<HID::Keyboard> hid{new HID::Keyboard};
|
||||
|
||||
struct Key {
|
||||
uint8 id;
|
||||
string name;
|
||||
};
|
||||
vector<Key> keys;
|
||||
|
||||
auto assign(uint inputID, bool value) -> void {
|
||||
auto& group = hid->buttons();
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(hid, HID::Keyboard::GroupID::Button, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
}
|
||||
|
||||
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
|
||||
KeyMap keymap;
|
||||
GetKeys(keymap);
|
||||
auto buffer = (const uint8*)keymap;
|
||||
|
||||
uint inputID = 0;
|
||||
for(auto& key : keys) {
|
||||
bool value = buffer[key.id >> 3] & (1 << (key.id & 7));
|
||||
assign(inputID++, value);
|
||||
}
|
||||
|
||||
devices.append(hid);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
keys.append({0x35, "Escape"});
|
||||
keys.append({0x7a, "F1"});
|
||||
keys.append({0x78, "F2"});
|
||||
keys.append({0x63, "F3"});
|
||||
keys.append({0x76, "F4"});
|
||||
keys.append({0x60, "F5"});
|
||||
keys.append({0x61, "F6"});
|
||||
keys.append({0x62, "F7"});
|
||||
keys.append({0x64, "F8"});
|
||||
keys.append({0x65, "F9"});
|
||||
keys.append({0x6d, "F10"});
|
||||
keys.append({0x67, "F11"});
|
||||
//keys.append({0x??, "F12"});
|
||||
|
||||
keys.append({0x69, "PrintScreen"});
|
||||
//keys.append({0x??, "ScrollLock"});
|
||||
keys.append({0x71, "Pause"});
|
||||
|
||||
keys.append({0x32, "Tilde"});
|
||||
keys.append({0x12, "Num1"});
|
||||
keys.append({0x13, "Num2"});
|
||||
keys.append({0x14, "Num3"});
|
||||
keys.append({0x15, "Num4"});
|
||||
keys.append({0x17, "Num5"});
|
||||
keys.append({0x16, "Num6"});
|
||||
keys.append({0x1a, "Num7"});
|
||||
keys.append({0x1c, "Num8"});
|
||||
keys.append({0x19, "Num9"});
|
||||
keys.append({0x1d, "Num0"});
|
||||
|
||||
keys.append({0x1b, "Dash"});
|
||||
keys.append({0x18, "Equal"});
|
||||
keys.append({0x33, "Backspace"});
|
||||
|
||||
keys.append({0x72, "Insert"});
|
||||
keys.append({0x75, "Delete"});
|
||||
keys.append({0x73, "Home"});
|
||||
keys.append({0x77, "End"});
|
||||
keys.append({0x74, "PageUp"});
|
||||
keys.append({0x79, "PageDown"});
|
||||
|
||||
keys.append({0x00, "A"});
|
||||
keys.append({0x0b, "B"});
|
||||
keys.append({0x08, "C"});
|
||||
keys.append({0x02, "D"});
|
||||
keys.append({0x0e, "E"});
|
||||
keys.append({0x03, "F"});
|
||||
keys.append({0x05, "G"});
|
||||
keys.append({0x04, "H"});
|
||||
keys.append({0x22, "I"});
|
||||
keys.append({0x26, "J"});
|
||||
keys.append({0x28, "K"});
|
||||
keys.append({0x25, "L"});
|
||||
keys.append({0x2e, "M"});
|
||||
keys.append({0x2d, "N"});
|
||||
keys.append({0x1f, "O"});
|
||||
keys.append({0x23, "P"});
|
||||
keys.append({0x0c, "Q"});
|
||||
keys.append({0x0f, "R"});
|
||||
keys.append({0x01, "S"});
|
||||
keys.append({0x11, "T"});
|
||||
keys.append({0x20, "U"});
|
||||
keys.append({0x09, "V"});
|
||||
keys.append({0x0d, "W"});
|
||||
keys.append({0x07, "X"});
|
||||
keys.append({0x10, "Y"});
|
||||
keys.append({0x06, "Z"});
|
||||
|
||||
keys.append({0x21, "LeftBracket"});
|
||||
keys.append({0x1e, "RightBracket"});
|
||||
keys.append({0x2a, "Backslash"});
|
||||
keys.append({0x29, "Semicolon"});
|
||||
keys.append({0x27, "Apostrophe"});
|
||||
keys.append({0x2b, "Comma"});
|
||||
keys.append({0x2f, "Period"});
|
||||
keys.append({0x2c, "Slash"});
|
||||
|
||||
keys.append({0x53, "Keypad1"});
|
||||
keys.append({0x54, "Keypad2"});
|
||||
keys.append({0x55, "Keypad3"});
|
||||
keys.append({0x56, "Keypad4"});
|
||||
keys.append({0x57, "Keypad5"});
|
||||
keys.append({0x58, "Keypad6"});
|
||||
keys.append({0x59, "Keypad7"});
|
||||
keys.append({0x5b, "Keypad8"});
|
||||
keys.append({0x5c, "Keypad9"});
|
||||
keys.append({0x52, "Keypad0"});
|
||||
|
||||
//keys.append({0x??, "Point"});
|
||||
keys.append({0x45, "Add"});
|
||||
keys.append({0x4e, "Subtract"});
|
||||
keys.append({0x43, "Multiply"});
|
||||
keys.append({0x4b, "Divide"});
|
||||
keys.append({0x4c, "Enter"});
|
||||
|
||||
keys.append({0x47, "NumLock"});
|
||||
//keys.append({0x39, "CapsLock"});
|
||||
|
||||
keys.append({0x7e, "Up"});
|
||||
keys.append({0x7d, "Down"});
|
||||
keys.append({0x7b, "Left"});
|
||||
keys.append({0x7c, "Right"});
|
||||
|
||||
keys.append({0x30, "Tab"});
|
||||
keys.append({0x24, "Return"});
|
||||
keys.append({0x31, "Spacebar"});
|
||||
//keys.append({0x??, "Menu"});
|
||||
|
||||
keys.append({0x38, "Shift"});
|
||||
keys.append({0x3b, "Control"});
|
||||
keys.append({0x3a, "Alt"});
|
||||
keys.append({0x37, "Super"});
|
||||
|
||||
hid->setID(1);
|
||||
for(auto& key : keys) {
|
||||
hid->buttons().append(key.name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
}
|
||||
};
|
153
input/keyboard/quartz.cpp
Normal file
153
input/keyboard/quartz.cpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
struct InputKeyboardQuartz {
|
||||
Input& input;
|
||||
InputKeyboardQuartz(Input& input) : input(input) {}
|
||||
|
||||
shared_pointer<HID::Keyboard> hid{new HID::Keyboard};
|
||||
|
||||
struct Key {
|
||||
string name;
|
||||
uint id;
|
||||
};
|
||||
vector<Key> keys;
|
||||
|
||||
auto assign(uint inputID, bool value) -> void {
|
||||
auto& group = hid->buttons();
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(hid, HID::Keyboard::GroupID::Button, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
}
|
||||
|
||||
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
|
||||
uint inputID = 0;
|
||||
for(auto& key : keys) {
|
||||
bool value = CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, key.id);
|
||||
assign(inputID++, value);
|
||||
}
|
||||
devices.append(hid);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
keys.append({"Escape", kVK_Escape});
|
||||
keys.append({"F1", kVK_F1});
|
||||
keys.append({"F2", kVK_F2});
|
||||
keys.append({"F3", kVK_F3});
|
||||
keys.append({"F4", kVK_F4});
|
||||
keys.append({"F5", kVK_F5});
|
||||
keys.append({"F6", kVK_F6});
|
||||
keys.append({"F7", kVK_F7});
|
||||
keys.append({"F8", kVK_F8});
|
||||
keys.append({"F9", kVK_F9});
|
||||
keys.append({"F10", kVK_F10});
|
||||
keys.append({"F11", kVK_F11});
|
||||
keys.append({"F12", kVK_F12});
|
||||
keys.append({"F13", kVK_F13});
|
||||
keys.append({"F14", kVK_F14});
|
||||
keys.append({"F15", kVK_F15});
|
||||
keys.append({"F16", kVK_F16});
|
||||
keys.append({"F17", kVK_F17});
|
||||
keys.append({"F18", kVK_F18});
|
||||
keys.append({"F19", kVK_F19});
|
||||
keys.append({"F20", kVK_F20});
|
||||
|
||||
keys.append({"Tilde", kVK_ANSI_Grave});
|
||||
keys.append({"Num1", kVK_ANSI_1});
|
||||
keys.append({"Num2", kVK_ANSI_2});
|
||||
keys.append({"Num3", kVK_ANSI_3});
|
||||
keys.append({"Num4", kVK_ANSI_4});
|
||||
keys.append({"Num5", kVK_ANSI_5});
|
||||
keys.append({"Num6", kVK_ANSI_6});
|
||||
keys.append({"Num7", kVK_ANSI_7});
|
||||
keys.append({"Num8", kVK_ANSI_8});
|
||||
keys.append({"Num9", kVK_ANSI_9});
|
||||
keys.append({"Num0", kVK_ANSI_0});
|
||||
|
||||
keys.append({"Dash", kVK_ANSI_Minus});
|
||||
keys.append({"Equal", kVK_ANSI_Equal});
|
||||
keys.append({"Delete", kVK_Delete});
|
||||
|
||||
keys.append({"Erase", kVK_ForwardDelete});
|
||||
keys.append({"Home", kVK_Home});
|
||||
keys.append({"End", kVK_End});
|
||||
keys.append({"PageUp", kVK_PageUp});
|
||||
keys.append({"PageDown", kVK_PageDown});
|
||||
|
||||
keys.append({"A", kVK_ANSI_A});
|
||||
keys.append({"B", kVK_ANSI_B});
|
||||
keys.append({"C", kVK_ANSI_C});
|
||||
keys.append({"D", kVK_ANSI_D});
|
||||
keys.append({"E", kVK_ANSI_E});
|
||||
keys.append({"F", kVK_ANSI_F});
|
||||
keys.append({"G", kVK_ANSI_G});
|
||||
keys.append({"H", kVK_ANSI_H});
|
||||
keys.append({"I", kVK_ANSI_I});
|
||||
keys.append({"J", kVK_ANSI_J});
|
||||
keys.append({"K", kVK_ANSI_K});
|
||||
keys.append({"L", kVK_ANSI_L});
|
||||
keys.append({"M", kVK_ANSI_M});
|
||||
keys.append({"N", kVK_ANSI_N});
|
||||
keys.append({"O", kVK_ANSI_O});
|
||||
keys.append({"P", kVK_ANSI_P});
|
||||
keys.append({"Q", kVK_ANSI_Q});
|
||||
keys.append({"R", kVK_ANSI_R});
|
||||
keys.append({"S", kVK_ANSI_S});
|
||||
keys.append({"T", kVK_ANSI_T});
|
||||
keys.append({"U", kVK_ANSI_U});
|
||||
keys.append({"V", kVK_ANSI_V});
|
||||
keys.append({"W", kVK_ANSI_W});
|
||||
keys.append({"X", kVK_ANSI_X});
|
||||
keys.append({"Y", kVK_ANSI_Y});
|
||||
keys.append({"Z", kVK_ANSI_Z});
|
||||
|
||||
keys.append({"LeftBracket", kVK_ANSI_LeftBracket});
|
||||
keys.append({"RightBracket", kVK_ANSI_RightBracket});
|
||||
keys.append({"Backslash", kVK_ANSI_Backslash});
|
||||
keys.append({"Semicolon", kVK_ANSI_Semicolon});
|
||||
keys.append({"Apostrophe", kVK_ANSI_Quote});
|
||||
keys.append({"Comma", kVK_ANSI_Comma});
|
||||
keys.append({"Period", kVK_ANSI_Period});
|
||||
keys.append({"Slash", kVK_ANSI_Slash});
|
||||
|
||||
keys.append({"Keypad1", kVK_ANSI_Keypad1});
|
||||
keys.append({"Keypad2", kVK_ANSI_Keypad2});
|
||||
keys.append({"Keypad3", kVK_ANSI_Keypad3});
|
||||
keys.append({"Keypad4", kVK_ANSI_Keypad4});
|
||||
keys.append({"Keypad5", kVK_ANSI_Keypad5});
|
||||
keys.append({"Keypad6", kVK_ANSI_Keypad6});
|
||||
keys.append({"Keypad7", kVK_ANSI_Keypad7});
|
||||
keys.append({"Keypad8", kVK_ANSI_Keypad8});
|
||||
keys.append({"Keypad9", kVK_ANSI_Keypad9});
|
||||
keys.append({"Keypad0", kVK_ANSI_Keypad0});
|
||||
|
||||
keys.append({"Clear", kVK_ANSI_KeypadClear});
|
||||
keys.append({"Equals", kVK_ANSI_KeypadEquals});
|
||||
keys.append({"Divide", kVK_ANSI_KeypadDivide});
|
||||
keys.append({"Multiply", kVK_ANSI_KeypadMultiply});
|
||||
keys.append({"Subtract", kVK_ANSI_KeypadMinus});
|
||||
keys.append({"Add", kVK_ANSI_KeypadPlus});
|
||||
keys.append({"Enter", kVK_ANSI_KeypadEnter});
|
||||
keys.append({"Decimal", kVK_ANSI_KeypadDecimal});
|
||||
|
||||
keys.append({"Up", kVK_UpArrow});
|
||||
keys.append({"Down", kVK_DownArrow});
|
||||
keys.append({"Left", kVK_LeftArrow});
|
||||
keys.append({"Right", kVK_RightArrow});
|
||||
|
||||
keys.append({"Tab", kVK_Tab});
|
||||
keys.append({"Return", kVK_Return});
|
||||
keys.append({"Spacebar", kVK_Space});
|
||||
keys.append({"Shift", kVK_Shift});
|
||||
keys.append({"Control", kVK_Control});
|
||||
keys.append({"Option", kVK_Option});
|
||||
keys.append({"Command", kVK_Command});
|
||||
|
||||
hid->setID(1);
|
||||
for(auto& key : keys) {
|
||||
hid->buttons().append(key.name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
}
|
||||
};
|
177
input/keyboard/rawinput.cpp
Normal file
177
input/keyboard/rawinput.cpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
#ifndef RUBY_INPUT_KEYBOARD_RAWINPUT
|
||||
#define RUBY_INPUT_KEYBOARD_RAWINPUT
|
||||
|
||||
struct InputKeyboardRawInput {
|
||||
Input& input;
|
||||
InputKeyboardRawInput(Input& input) : input(input) {}
|
||||
|
||||
struct Key {
|
||||
uint16_t code;
|
||||
uint16_t flag;
|
||||
string name;
|
||||
bool value;
|
||||
};
|
||||
vector<Key> keys;
|
||||
|
||||
struct Keyboard {
|
||||
shared_pointer<HID::Keyboard> hid{new HID::Keyboard};
|
||||
} kb;
|
||||
|
||||
auto update(RAWINPUT* input) -> void {
|
||||
unsigned code = input->data.keyboard.MakeCode;
|
||||
unsigned flag = input->data.keyboard.Flags;
|
||||
|
||||
for(auto& key : keys) {
|
||||
if(key.code != code) continue;
|
||||
key.value = (key.flag == flag);
|
||||
}
|
||||
}
|
||||
|
||||
auto assign(unsigned inputID, bool value) -> void {
|
||||
auto& group = kb.hid->buttons();
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(kb.hid, HID::Keyboard::GroupID::Button, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
}
|
||||
|
||||
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
|
||||
for(unsigned n = 0; n < keys.size(); n++) assign(n, keys[n].value);
|
||||
devices.append(kb.hid);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
rawinput.updateKeyboard = {&InputKeyboardRawInput::update, this};
|
||||
|
||||
//Pause sends 0x001d,4 + 0x0045,0; NumLock sends only 0x0045,0
|
||||
//pressing Pause will falsely trigger NumLock
|
||||
//further, pause sends its key release even while button is held down
|
||||
//because of this, we cannot map either reliably
|
||||
|
||||
keys.append({0x0001, 0, "Escape"});
|
||||
keys.append({0x003b, 0, "F1"});
|
||||
keys.append({0x003c, 0, "F2"});
|
||||
keys.append({0x003d, 0, "F3"});
|
||||
keys.append({0x003e, 0, "F4"});
|
||||
keys.append({0x003f, 0, "F5"});
|
||||
keys.append({0x0040, 0, "F6"});
|
||||
keys.append({0x0041, 0, "F7"});
|
||||
keys.append({0x0042, 0, "F8"});
|
||||
keys.append({0x0043, 0, "F9"});
|
||||
keys.append({0x0044, 0, "F10"});
|
||||
keys.append({0x0057, 0, "F11"});
|
||||
keys.append({0x0058, 0, "F12"});
|
||||
|
||||
keys.append({0x0037, 2, "PrintScreen"});
|
||||
keys.append({0x0046, 0, "ScrollLock"});
|
||||
//keys.append({0x001d, 4, "Pause"});
|
||||
keys.append({0x0029, 0, "Tilde"});
|
||||
|
||||
keys.append({0x0002, 0, "Num1"});
|
||||
keys.append({0x0003, 0, "Num2"});
|
||||
keys.append({0x0004, 0, "Num3"});
|
||||
keys.append({0x0005, 0, "Num4"});
|
||||
keys.append({0x0006, 0, "Num5"});
|
||||
keys.append({0x0007, 0, "Num6"});
|
||||
keys.append({0x0008, 0, "Num7"});
|
||||
keys.append({0x0009, 0, "Num8"});
|
||||
keys.append({0x000a, 0, "Num9"});
|
||||
keys.append({0x000b, 0, "Num0"});
|
||||
|
||||
keys.append({0x000c, 0, "Dash"});
|
||||
keys.append({0x000d, 0, "Equal"});
|
||||
keys.append({0x000e, 0, "Backspace"});
|
||||
|
||||
keys.append({0x0052, 2, "Insert"});
|
||||
keys.append({0x0053, 2, "Delete"});
|
||||
keys.append({0x0047, 2, "Home"});
|
||||
keys.append({0x004f, 2, "End"});
|
||||
keys.append({0x0049, 2, "PageUp"});
|
||||
keys.append({0x0051, 2, "PageDown"});
|
||||
|
||||
keys.append({0x001e, 0, "A"});
|
||||
keys.append({0x0030, 0, "B"});
|
||||
keys.append({0x002e, 0, "C"});
|
||||
keys.append({0x0020, 0, "D"});
|
||||
keys.append({0x0012, 0, "E"});
|
||||
keys.append({0x0021, 0, "F"});
|
||||
keys.append({0x0022, 0, "G"});
|
||||
keys.append({0x0023, 0, "H"});
|
||||
keys.append({0x0017, 0, "I"});
|
||||
keys.append({0x0024, 0, "J"});
|
||||
keys.append({0x0025, 0, "K"});
|
||||
keys.append({0x0026, 0, "L"});
|
||||
keys.append({0x0032, 0, "M"});
|
||||
keys.append({0x0031, 0, "N"});
|
||||
keys.append({0x0018, 0, "O"});
|
||||
keys.append({0x0019, 0, "P"});
|
||||
keys.append({0x0010, 0, "Q"});
|
||||
keys.append({0x0013, 0, "R"});
|
||||
keys.append({0x001f, 0, "S"});
|
||||
keys.append({0x0014, 0, "T"});
|
||||
keys.append({0x0016, 0, "U"});
|
||||
keys.append({0x002f, 0, "V"});
|
||||
keys.append({0x0011, 0, "W"});
|
||||
keys.append({0x002d, 0, "X"});
|
||||
keys.append({0x0015, 0, "Y"});
|
||||
keys.append({0x002c, 0, "Z"});
|
||||
|
||||
keys.append({0x001a, 0, "LeftBracket"});
|
||||
keys.append({0x001b, 0, "RightBracket"});
|
||||
keys.append({0x002b, 0, "Backslash"});
|
||||
keys.append({0x0027, 0, "Semicolon"});
|
||||
keys.append({0x0028, 0, "Apostrophe"});
|
||||
keys.append({0x0033, 0, "Comma"});
|
||||
keys.append({0x0034, 0, "Period"});
|
||||
keys.append({0x0035, 0, "Slash"});
|
||||
|
||||
keys.append({0x004f, 0, "Keypad1"});
|
||||
keys.append({0x0050, 0, "Keypad2"});
|
||||
keys.append({0x0051, 0, "Keypad3"});
|
||||
keys.append({0x004b, 0, "Keypad4"});
|
||||
keys.append({0x004c, 0, "Keypad5"});
|
||||
keys.append({0x004d, 0, "Keypad6"});
|
||||
keys.append({0x0047, 0, "Keypad7"});
|
||||
keys.append({0x0048, 0, "Keypad8"});
|
||||
keys.append({0x0049, 0, "Keypad9"});
|
||||
keys.append({0x0052, 0, "Keypad0"});
|
||||
|
||||
keys.append({0x0053, 0, "Point"});
|
||||
keys.append({0x001c, 2, "Enter"});
|
||||
keys.append({0x004e, 0, "Add"});
|
||||
keys.append({0x004a, 0, "Subtract"});
|
||||
keys.append({0x0037, 0, "Multiply"});
|
||||
keys.append({0x0035, 2, "Divide"});
|
||||
|
||||
//keys.append({0x0045, 0, "NumLock"});
|
||||
keys.append({0x003a, 0, "CapsLock"});
|
||||
|
||||
keys.append({0x0048, 2, "Up"});
|
||||
keys.append({0x0050, 2, "Down"});
|
||||
keys.append({0x004b, 2, "Left"});
|
||||
keys.append({0x004d, 2, "Right"});
|
||||
|
||||
keys.append({0x000f, 0, "Tab"});
|
||||
keys.append({0x001c, 0, "Return"});
|
||||
keys.append({0x0039, 0, "Spacebar"});
|
||||
|
||||
keys.append({0x002a, 0, "LeftShift"});
|
||||
keys.append({0x0036, 0, "RightShift"});
|
||||
keys.append({0x001d, 0, "LeftControl"});
|
||||
keys.append({0x001d, 2, "RightControl"});
|
||||
keys.append({0x0038, 0, "LeftAlt"});
|
||||
keys.append({0x0038, 2, "RightAlt"});
|
||||
keys.append({0x005b, 2, "LeftSuper"});
|
||||
keys.append({0x005c, 2, "RightSuper"});
|
||||
keys.append({0x005d, 2, "Menu"});
|
||||
|
||||
kb.hid->setID(1);
|
||||
for(auto& key : keys) kb.hid->buttons().append(key.name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void term() {
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
174
input/keyboard/xlib.cpp
Normal file
174
input/keyboard/xlib.cpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
#ifndef RUBY_INPUT_KEYBOARD_XLIB
|
||||
#define RUBY_INPUT_KEYBOARD_XLIB
|
||||
|
||||
struct InputKeyboardXlib {
|
||||
Input& input;
|
||||
InputKeyboardXlib(Input& input) : input(input) {}
|
||||
|
||||
shared_pointer<HID::Keyboard> hid{new HID::Keyboard};
|
||||
|
||||
Display* display = nullptr;
|
||||
|
||||
struct Key {
|
||||
string name;
|
||||
uint keysym;
|
||||
uint keycode;
|
||||
};
|
||||
vector<Key> keys;
|
||||
|
||||
auto assign(uint inputID, bool value) -> void {
|
||||
auto& group = hid->buttons();
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(hid, HID::Keyboard::GroupID::Button, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
}
|
||||
|
||||
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
|
||||
char state[32];
|
||||
XQueryKeymap(display, state);
|
||||
|
||||
uint inputID = 0;
|
||||
for(auto& key : keys) {
|
||||
bool value = state[key.keycode >> 3] & (1 << (key.keycode & 7));
|
||||
assign(inputID++, value);
|
||||
}
|
||||
|
||||
devices.append(hid);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
display = XOpenDisplay(0);
|
||||
|
||||
keys.append({"Escape", XK_Escape});
|
||||
|
||||
keys.append({"F1", XK_F1});
|
||||
keys.append({"F2", XK_F2});
|
||||
keys.append({"F3", XK_F3});
|
||||
keys.append({"F4", XK_F4});
|
||||
keys.append({"F5", XK_F5});
|
||||
keys.append({"F6", XK_F6});
|
||||
keys.append({"F7", XK_F7});
|
||||
keys.append({"F8", XK_F8});
|
||||
keys.append({"F9", XK_F9});
|
||||
keys.append({"F10", XK_F10});
|
||||
keys.append({"F11", XK_F11});
|
||||
keys.append({"F12", XK_F12});
|
||||
|
||||
keys.append({"ScrollLock", XK_Scroll_Lock});
|
||||
keys.append({"Pause", XK_Pause});
|
||||
|
||||
keys.append({"Tilde", XK_asciitilde});
|
||||
|
||||
keys.append({"Num0", XK_0});
|
||||
keys.append({"Num1", XK_1});
|
||||
keys.append({"Num2", XK_2});
|
||||
keys.append({"Num3", XK_3});
|
||||
keys.append({"Num4", XK_4});
|
||||
keys.append({"Num5", XK_5});
|
||||
keys.append({"Num6", XK_6});
|
||||
keys.append({"Num7", XK_7});
|
||||
keys.append({"Num8", XK_8});
|
||||
keys.append({"Num9", XK_9});
|
||||
|
||||
keys.append({"Dash", XK_minus});
|
||||
keys.append({"Equal", XK_equal});
|
||||
keys.append({"Backspace", XK_BackSpace});
|
||||
|
||||
keys.append({"Insert", XK_Insert});
|
||||
keys.append({"Delete", XK_Delete});
|
||||
keys.append({"Home", XK_Home});
|
||||
keys.append({"End", XK_End});
|
||||
keys.append({"PageUp", XK_Prior});
|
||||
keys.append({"PageDown", XK_Next});
|
||||
|
||||
keys.append({"A", XK_A});
|
||||
keys.append({"B", XK_B});
|
||||
keys.append({"C", XK_C});
|
||||
keys.append({"D", XK_D});
|
||||
keys.append({"E", XK_E});
|
||||
keys.append({"F", XK_F});
|
||||
keys.append({"G", XK_G});
|
||||
keys.append({"H", XK_H});
|
||||
keys.append({"I", XK_I});
|
||||
keys.append({"J", XK_J});
|
||||
keys.append({"K", XK_K});
|
||||
keys.append({"L", XK_L});
|
||||
keys.append({"M", XK_M});
|
||||
keys.append({"N", XK_N});
|
||||
keys.append({"O", XK_O});
|
||||
keys.append({"P", XK_P});
|
||||
keys.append({"Q", XK_Q});
|
||||
keys.append({"R", XK_R});
|
||||
keys.append({"S", XK_S});
|
||||
keys.append({"T", XK_T});
|
||||
keys.append({"U", XK_U});
|
||||
keys.append({"V", XK_V});
|
||||
keys.append({"W", XK_W});
|
||||
keys.append({"X", XK_X});
|
||||
keys.append({"Y", XK_Y});
|
||||
keys.append({"Z", XK_Z});
|
||||
|
||||
keys.append({"LeftBracket", XK_bracketleft});
|
||||
keys.append({"RightBracket", XK_bracketright});
|
||||
keys.append({"Backslash", XK_backslash});
|
||||
keys.append({"Semicolon", XK_semicolon});
|
||||
keys.append({"Apostrophe", XK_apostrophe});
|
||||
keys.append({"Comma", XK_comma});
|
||||
keys.append({"Period", XK_period});
|
||||
keys.append({"Slash", XK_slash});
|
||||
|
||||
keys.append({"Keypad0", XK_KP_0});
|
||||
keys.append({"Keypad1", XK_KP_1});
|
||||
keys.append({"Keypad2", XK_KP_2});
|
||||
keys.append({"Keypad3", XK_KP_3});
|
||||
keys.append({"Keypad4", XK_KP_4});
|
||||
keys.append({"Keypad5", XK_KP_5});
|
||||
keys.append({"Keypad6", XK_KP_6});
|
||||
keys.append({"Keypad7", XK_KP_7});
|
||||
keys.append({"Keypad8", XK_KP_8});
|
||||
keys.append({"Keypad9", XK_KP_9});
|
||||
|
||||
keys.append({"Add", XK_KP_Add});
|
||||
keys.append({"Subtract", XK_KP_Subtract});
|
||||
keys.append({"Multiply", XK_KP_Multiply});
|
||||
keys.append({"Divide", XK_KP_Divide});
|
||||
keys.append({"Enter", XK_KP_Enter});
|
||||
|
||||
keys.append({"Up", XK_Up});
|
||||
keys.append({"Down", XK_Down});
|
||||
keys.append({"Left", XK_Left});
|
||||
keys.append({"Right", XK_Right});
|
||||
|
||||
keys.append({"Tab", XK_Tab});
|
||||
keys.append({"Return", XK_Return});
|
||||
keys.append({"Spacebar", XK_space});
|
||||
|
||||
keys.append({"LeftControl", XK_Control_L});
|
||||
keys.append({"RightControl", XK_Control_R});
|
||||
keys.append({"LeftAlt", XK_Alt_L});
|
||||
keys.append({"RightAlt", XK_Alt_R});
|
||||
keys.append({"LeftShift", XK_Shift_L});
|
||||
keys.append({"RightShift", XK_Shift_R});
|
||||
keys.append({"LeftSuper", XK_Super_L});
|
||||
keys.append({"RightSuper", XK_Super_R});
|
||||
keys.append({"Menu", XK_Menu});
|
||||
|
||||
hid->setID(1);
|
||||
|
||||
for(auto& key : keys) {
|
||||
hid->buttons().append(key.name);
|
||||
key.keycode = XKeysymToKeycode(display, key.keysym);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(display) {
|
||||
XCloseDisplay(display);
|
||||
display = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
122
input/mouse/rawinput.cpp
Normal file
122
input/mouse/rawinput.cpp
Normal file
|
@ -0,0 +1,122 @@
|
|||
#ifndef RUBY_INPUT_MOUSE_RAWINPUT
|
||||
#define RUBY_INPUT_MOUSE_RAWINPUT
|
||||
|
||||
struct InputMouseRawInput {
|
||||
Input& input;
|
||||
InputMouseRawInput(Input& input) : input(input) {}
|
||||
|
||||
uintptr_t handle = 0;
|
||||
bool mouseAcquired = false;
|
||||
|
||||
struct Mouse {
|
||||
shared_pointer<HID::Mouse> hid{new HID::Mouse};
|
||||
|
||||
signed relativeX = 0;
|
||||
signed relativeY = 0;
|
||||
signed relativeZ = 0;
|
||||
bool buttons[5] = {0};
|
||||
} ms;
|
||||
|
||||
auto acquire() -> bool {
|
||||
if(!mouseAcquired) {
|
||||
mouseAcquired = true;
|
||||
ShowCursor(false);
|
||||
}
|
||||
return acquired();
|
||||
}
|
||||
|
||||
auto release() -> bool {
|
||||
if(mouseAcquired) {
|
||||
mouseAcquired = false;
|
||||
ReleaseCapture();
|
||||
ClipCursor(nullptr);
|
||||
ShowCursor(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto acquired() -> bool {
|
||||
if(mouseAcquired) {
|
||||
SetFocus((HWND)handle);
|
||||
SetCapture((HWND)handle);
|
||||
RECT rc;
|
||||
GetWindowRect((HWND)handle, &rc);
|
||||
ClipCursor(&rc);
|
||||
}
|
||||
return GetCapture() == (HWND)handle;
|
||||
}
|
||||
|
||||
auto update(RAWINPUT* input) -> void {
|
||||
if((input->data.mouse.usFlags & 1) == MOUSE_MOVE_RELATIVE) {
|
||||
ms.relativeX += input->data.mouse.lLastX;
|
||||
ms.relativeY += input->data.mouse.lLastY;
|
||||
}
|
||||
|
||||
if(input->data.mouse.usButtonFlags & RI_MOUSE_WHEEL) {
|
||||
ms.relativeZ += (int16_t)input->data.mouse.usButtonData;
|
||||
}
|
||||
|
||||
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) ms.buttons[0] = 1;
|
||||
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP ) ms.buttons[0] = 0;
|
||||
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) ms.buttons[1] = 1;
|
||||
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP ) ms.buttons[1] = 0;
|
||||
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) ms.buttons[2] = 1;
|
||||
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP ) ms.buttons[2] = 0;
|
||||
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) ms.buttons[3] = 1;
|
||||
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP ) ms.buttons[3] = 0;
|
||||
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) ms.buttons[4] = 1;
|
||||
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP ) ms.buttons[4] = 0;
|
||||
}
|
||||
|
||||
auto assign(unsigned groupID, unsigned inputID, int16_t value) -> void {
|
||||
auto& group = ms.hid->group(groupID);
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(ms.hid, groupID, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
}
|
||||
|
||||
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
|
||||
assign(HID::Mouse::GroupID::Axis, 0, ms.relativeX);
|
||||
assign(HID::Mouse::GroupID::Axis, 1, ms.relativeY);
|
||||
assign(HID::Mouse::GroupID::Axis, 2, ms.relativeZ);
|
||||
|
||||
//keys are intentionally reordered below:
|
||||
//in ruby, button order is {left, middle, right, up, down}
|
||||
assign(HID::Mouse::GroupID::Button, 0, ms.buttons[0]);
|
||||
assign(HID::Mouse::GroupID::Button, 2, ms.buttons[1]);
|
||||
assign(HID::Mouse::GroupID::Button, 1, ms.buttons[2]);
|
||||
assign(HID::Mouse::GroupID::Button, 4, ms.buttons[3]);
|
||||
assign(HID::Mouse::GroupID::Button, 3, ms.buttons[4]);
|
||||
|
||||
ms.relativeX = 0;
|
||||
ms.relativeY = 0;
|
||||
ms.relativeZ = 0;
|
||||
|
||||
devices.append(ms.hid);
|
||||
}
|
||||
|
||||
auto init(uintptr_t handle) -> bool {
|
||||
this->handle = handle;
|
||||
|
||||
ms.hid->setID(2);
|
||||
|
||||
ms.hid->axes().append("X");
|
||||
ms.hid->axes().append("Y");
|
||||
ms.hid->axes().append("Z");
|
||||
|
||||
ms.hid->buttons().append("Left");
|
||||
ms.hid->buttons().append("Middle");
|
||||
ms.hid->buttons().append("Right");
|
||||
ms.hid->buttons().append("Up");
|
||||
ms.hid->buttons().append("Down");
|
||||
|
||||
rawinput.updateMouse = {&InputMouseRawInput::update, this};
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
release();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
153
input/mouse/xlib.cpp
Normal file
153
input/mouse/xlib.cpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
#ifndef RUBY_INPUT_MOUSE_XLIB
|
||||
#define RUBY_INPUT_MOUSE_XLIB
|
||||
|
||||
struct InputMouseXlib {
|
||||
Input& input;
|
||||
InputMouseXlib(Input& input) : input(input) {}
|
||||
|
||||
shared_pointer<HID::Mouse> hid{new HID::Mouse};
|
||||
|
||||
uintptr_t handle = 0;
|
||||
|
||||
Display* display = nullptr;
|
||||
Window rootWindow;
|
||||
Cursor invisibleCursor;
|
||||
unsigned screenWidth = 0;
|
||||
unsigned screenHeight = 0;
|
||||
|
||||
struct Mouse {
|
||||
bool acquired = false;
|
||||
signed numerator = 0;
|
||||
signed denominator = 0;
|
||||
signed threshold = 0;
|
||||
unsigned relativeX = 0;
|
||||
unsigned relativeY = 0;
|
||||
} ms;
|
||||
|
||||
auto acquire() -> bool {
|
||||
if(acquired()) return true;
|
||||
|
||||
if(XGrabPointer(display, handle, True, 0, GrabModeAsync, GrabModeAsync, rootWindow, invisibleCursor, CurrentTime) == GrabSuccess) {
|
||||
//backup existing cursor acceleration settings
|
||||
XGetPointerControl(display, &ms.numerator, &ms.denominator, &ms.threshold);
|
||||
|
||||
//disable cursor acceleration
|
||||
XChangePointerControl(display, True, False, 1, 1, 0);
|
||||
|
||||
//center cursor (so that first relative poll returns 0, 0 if mouse has not moved)
|
||||
XWarpPointer(display, None, rootWindow, 0, 0, 0, 0, screenWidth / 2, screenHeight / 2);
|
||||
|
||||
return ms.acquired = true;
|
||||
} else {
|
||||
return ms.acquired = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto release() -> bool {
|
||||
if(acquired()) {
|
||||
//restore cursor acceleration and release cursor
|
||||
XChangePointerControl(display, True, True, ms.numerator, ms.denominator, ms.threshold);
|
||||
XUngrabPointer(display, CurrentTime);
|
||||
ms.acquired = false;
|
||||
}
|
||||
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;
|
||||
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
}
|
||||
|
||||
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
|
||||
Window rootReturn;
|
||||
Window childReturn;
|
||||
signed rootXReturn = 0;
|
||||
signed rootYReturn = 0;
|
||||
signed windowXReturn = 0;
|
||||
signed windowYReturn = 0;
|
||||
unsigned maskReturn = 0;
|
||||
XQueryPointer(display, handle, &rootReturn, &childReturn, &rootXReturn, &rootYReturn, &windowXReturn, &windowYReturn, &maskReturn);
|
||||
|
||||
if(acquired()) {
|
||||
XWindowAttributes attributes;
|
||||
XGetWindowAttributes(display, handle, &attributes);
|
||||
|
||||
//absolute -> relative conversion
|
||||
assign(HID::Mouse::GroupID::Axis, 0, (int16_t)(rootXReturn - screenWidth / 2));
|
||||
assign(HID::Mouse::GroupID::Axis, 1, (int16_t)(rootYReturn - screenHeight / 2));
|
||||
|
||||
if(hid->axes().input(0).value() != 0 || hid->axes().input(1).value() != 0) {
|
||||
//if mouse moved, re-center mouse for next poll
|
||||
XWarpPointer(display, None, rootWindow, 0, 0, 0, 0, screenWidth / 2, screenHeight / 2);
|
||||
}
|
||||
} else {
|
||||
assign(HID::Mouse::GroupID::Axis, 0, (int16_t)(rootXReturn - ms.relativeX));
|
||||
assign(HID::Mouse::GroupID::Axis, 1, (int16_t)(rootYReturn - ms.relativeY));
|
||||
|
||||
ms.relativeX = rootXReturn;
|
||||
ms.relativeY = rootYReturn;
|
||||
}
|
||||
|
||||
assign(HID::Mouse::GroupID::Button, 0, (bool)(maskReturn & Button1Mask));
|
||||
assign(HID::Mouse::GroupID::Button, 1, (bool)(maskReturn & Button2Mask));
|
||||
assign(HID::Mouse::GroupID::Button, 2, (bool)(maskReturn & Button3Mask));
|
||||
assign(HID::Mouse::GroupID::Button, 3, (bool)(maskReturn & Button4Mask));
|
||||
assign(HID::Mouse::GroupID::Button, 4, (bool)(maskReturn & Button5Mask));
|
||||
|
||||
devices.append(hid);
|
||||
}
|
||||
|
||||
auto init(uintptr_t handle) -> bool {
|
||||
this->handle = handle;
|
||||
display = XOpenDisplay(0);
|
||||
rootWindow = DefaultRootWindow(display);
|
||||
|
||||
XWindowAttributes attributes;
|
||||
XGetWindowAttributes(display, rootWindow, &attributes);
|
||||
screenWidth = attributes.width;
|
||||
screenHeight = attributes.height;
|
||||
|
||||
//Xlib: because XShowCursor(display, false) would just be too easy
|
||||
//create invisible cursor for use when mouse is acquired
|
||||
Pixmap pixmap;
|
||||
XColor black, unused;
|
||||
static char invisibleData[8] = {0};
|
||||
Colormap colormap = DefaultColormap(display, DefaultScreen(display));
|
||||
XAllocNamedColor(display, colormap, "black", &black, &unused);
|
||||
pixmap = XCreateBitmapFromData(display, handle, invisibleData, 8, 8);
|
||||
invisibleCursor = XCreatePixmapCursor(display, pixmap, pixmap, &black, &black, 0, 0);
|
||||
XFreePixmap(display, pixmap);
|
||||
XFreeColors(display, colormap, &black.pixel, 1, 0);
|
||||
|
||||
ms.acquired = false;
|
||||
ms.relativeX = 0;
|
||||
ms.relativeY = 0;
|
||||
|
||||
hid->setID(2);
|
||||
|
||||
hid->axes().append("X");
|
||||
hid->axes().append("Y");
|
||||
|
||||
hid->buttons().append("Left");
|
||||
hid->buttons().append("Middle");
|
||||
hid->buttons().append("Right");
|
||||
hid->buttons().append("Up");
|
||||
hid->buttons().append("Down");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
release();
|
||||
XFreeCursor(display, invisibleCursor);
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
43
input/quartz.cpp
Normal file
43
input/quartz.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#include "keyboard/quartz.cpp"
|
||||
|
||||
struct InputQuartz : Input {
|
||||
InputKeyboardQuartz quartzKeyboard;
|
||||
InputQuartz() : quartzKeyboard(*this) {}
|
||||
~InputQuartz() { term(); }
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Input::KeyboardSupport) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto acquire() -> bool { return false; }
|
||||
auto release() -> bool { return false; }
|
||||
auto acquired() -> bool { return false; }
|
||||
|
||||
auto poll() -> vector<shared_pointer<HID::Device>> {
|
||||
vector<shared_pointer<HID::Device>> devices;
|
||||
quartzKeyboard.poll(devices);
|
||||
return devices;
|
||||
}
|
||||
|
||||
auto rumble(uint64 id, bool enable) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
if(!quartzKeyboard.init()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
quartzKeyboard.term();
|
||||
}
|
||||
};
|
78
input/sdl.cpp
Normal file
78
input/sdl.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
#include <SDL/SDL.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
|
||||
#include "keyboard/xlib.cpp"
|
||||
#include "mouse/xlib.cpp"
|
||||
#include "joypad/sdl.cpp"
|
||||
|
||||
struct InputSDL : Input {
|
||||
InputKeyboardXlib xlibKeyboard;
|
||||
InputMouseXlib xlibMouse;
|
||||
InputJoypadSDL sdl;
|
||||
InputSDL() : xlibKeyboard(*this), xlibMouse(*this), sdl(*this) {}
|
||||
~InputSDL() { term(); }
|
||||
|
||||
struct Settings {
|
||||
uintptr_t handle = 0;
|
||||
} settings;
|
||||
|
||||
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 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 acquired() -> bool {
|
||||
return xlibMouse.acquired();
|
||||
}
|
||||
|
||||
auto poll() -> vector<shared_pointer<HID::Device>> {
|
||||
vector<shared_pointer<HID::Device>> devices;
|
||||
xlibKeyboard.poll(devices);
|
||||
xlibMouse.poll(devices);
|
||||
sdl.poll(devices);
|
||||
return devices;
|
||||
}
|
||||
|
||||
auto rumble(uint64_t id, bool enable) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
if(!xlibKeyboard.init()) return false;
|
||||
if(!xlibMouse.init(settings.handle)) return false;
|
||||
if(!sdl.init()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
xlibKeyboard.term();
|
||||
xlibMouse.term();
|
||||
sdl.term();
|
||||
}
|
||||
};
|
158
input/shared/rawinput.cpp
Normal file
158
input/shared/rawinput.cpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
#ifndef RUBY_INPUT_SHARED_RAWINPUT
|
||||
#define RUBY_INPUT_SHARED_RAWINPUT
|
||||
|
||||
auto CALLBACK RawInputWindowProc(HWND, UINT, WPARAM, LPARAM) -> LRESULT;
|
||||
|
||||
struct RawInput {
|
||||
HANDLE mutex = nullptr;
|
||||
HWND hwnd = nullptr;
|
||||
bool ready = false;
|
||||
bool initialized = false;
|
||||
function<void (RAWINPUT*)> updateKeyboard;
|
||||
function<void (RAWINPUT*)> updateMouse;
|
||||
|
||||
struct Device {
|
||||
HANDLE handle = nullptr;
|
||||
string path;
|
||||
enum class Type : unsigned { Keyboard, Mouse, Joypad } type;
|
||||
uint16_t vendorID = 0;
|
||||
uint16_t productID = 0;
|
||||
bool isXInputDevice = false;
|
||||
};
|
||||
vector<Device> devices;
|
||||
|
||||
auto find(uint16_t vendorID, uint16_t productID) -> maybe<Device&> {
|
||||
for(auto& device : devices) {
|
||||
if(device.vendorID == vendorID && device.productID == productID) return device;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
auto scanDevices() -> void {
|
||||
devices.reset();
|
||||
|
||||
unsigned deviceCount = 0;
|
||||
GetRawInputDeviceList(NULL, &deviceCount, sizeof(RAWINPUTDEVICELIST));
|
||||
RAWINPUTDEVICELIST* list = new RAWINPUTDEVICELIST[deviceCount];
|
||||
GetRawInputDeviceList(list, &deviceCount, sizeof(RAWINPUTDEVICELIST));
|
||||
|
||||
for(unsigned n = 0; n < deviceCount; n++) {
|
||||
wchar_t path[4096];
|
||||
unsigned size = sizeof(path) - 1;
|
||||
GetRawInputDeviceInfo(list[n].hDevice, RIDI_DEVICENAME, &path, &size);
|
||||
|
||||
RID_DEVICE_INFO info;
|
||||
info.cbSize = size = sizeof(RID_DEVICE_INFO);
|
||||
GetRawInputDeviceInfo(list[n].hDevice, RIDI_DEVICEINFO, &info, &size);
|
||||
|
||||
Device device;
|
||||
device.path = (const char*)utf8_t(path);
|
||||
device.handle = list[n].hDevice;
|
||||
|
||||
if(info.dwType == RIM_TYPEKEYBOARD) {
|
||||
device.type = Device::Type::Keyboard;
|
||||
device.vendorID = 0;
|
||||
device.productID = 1;
|
||||
}
|
||||
|
||||
if(info.dwType == RIM_TYPEMOUSE) {
|
||||
device.type = Device::Type::Mouse;
|
||||
device.vendorID = 0;
|
||||
device.productID = 2;
|
||||
}
|
||||
|
||||
if(info.dwType == RIM_TYPEHID) {
|
||||
//verify that this is a joypad device
|
||||
if(info.hid.usUsagePage != 1 || (info.hid.usUsage != 4 && info.hid.usUsage != 5)) continue;
|
||||
|
||||
device.type = Device::Type::Joypad;
|
||||
device.vendorID = info.hid.dwVendorId;
|
||||
device.productID = info.hid.dwProductId;
|
||||
if(device.path.find("IG_")) device.isXInputDevice = true; //"IG_" is only found inside XInput device paths
|
||||
}
|
||||
|
||||
devices.append(device);
|
||||
}
|
||||
|
||||
delete[] list;
|
||||
}
|
||||
|
||||
auto windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
|
||||
if(msg != WM_INPUT) return DefWindowProc(hwnd, msg, wparam, lparam);
|
||||
|
||||
unsigned size = 0;
|
||||
GetRawInputData((HRAWINPUT)lparam, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER));
|
||||
RAWINPUT* input = new RAWINPUT[size];
|
||||
GetRawInputData((HRAWINPUT)lparam, RID_INPUT, input, &size, sizeof(RAWINPUTHEADER));
|
||||
WaitForSingleObject(mutex, INFINITE);
|
||||
|
||||
if(input->header.dwType == RIM_TYPEKEYBOARD) {
|
||||
if(updateKeyboard) updateKeyboard(input);
|
||||
}
|
||||
|
||||
if(input->header.dwType == RIM_TYPEMOUSE) {
|
||||
if(updateMouse) updateMouse(input);
|
||||
}
|
||||
|
||||
ReleaseMutex(mutex);
|
||||
LRESULT result = DefRawInputProc(&input, size, sizeof(RAWINPUTHEADER));
|
||||
delete[] input;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
WNDCLASS wc;
|
||||
wc.cbClsExtra = 0;
|
||||
wc.cbWndExtra = 0;
|
||||
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
|
||||
wc.hCursor = LoadCursor(0, IDC_ARROW);
|
||||
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
|
||||
wc.hInstance = GetModuleHandle(0);
|
||||
wc.lpfnWndProc = RawInputWindowProc;
|
||||
wc.lpszClassName = L"RawInputClass";
|
||||
wc.lpszMenuName = 0;
|
||||
wc.style = CS_VREDRAW | CS_HREDRAW;
|
||||
RegisterClass(&wc);
|
||||
|
||||
hwnd = CreateWindow(L"RawInputClass", L"RawInputClass", WS_POPUP, 0, 0, 64, 64, 0, 0, GetModuleHandle(0), 0);
|
||||
|
||||
scanDevices();
|
||||
|
||||
RAWINPUTDEVICE device[2];
|
||||
//capture all keyboard input
|
||||
device[0].usUsagePage = 1;
|
||||
device[0].usUsage = 6;
|
||||
device[0].dwFlags = RIDEV_INPUTSINK;
|
||||
device[0].hwndTarget = hwnd;
|
||||
//capture all mouse input
|
||||
device[1].usUsagePage = 1;
|
||||
device[1].usUsage = 2;
|
||||
device[1].dwFlags = RIDEV_INPUTSINK;
|
||||
device[1].hwndTarget = hwnd;
|
||||
RegisterRawInputDevices(device, 2, sizeof(RAWINPUTDEVICE));
|
||||
|
||||
WaitForSingleObject(mutex, INFINITE);
|
||||
ready = true;
|
||||
ReleaseMutex(mutex);
|
||||
|
||||
while(true) {
|
||||
MSG msg;
|
||||
GetMessage(&msg, hwnd, 0, 0);
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static RawInput rawinput;
|
||||
|
||||
auto WINAPI RawInputThreadProc(void*) -> DWORD {
|
||||
rawinput.main();
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto CALLBACK RawInputWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
|
||||
return rawinput.windowProc(hwnd, msg, wparam, lparam);
|
||||
}
|
||||
|
||||
#endif
|
84
input/udev.cpp
Normal file
84
input/udev.cpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/poll.h>
|
||||
#include <fcntl.h>
|
||||
#include <libudev.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#include "keyboard/xlib.cpp"
|
||||
#include "mouse/xlib.cpp"
|
||||
#include "joypad/udev.cpp"
|
||||
|
||||
struct InputUdev : Input {
|
||||
InputKeyboardXlib xlibKeyboard;
|
||||
InputMouseXlib xlibMouse;
|
||||
InputJoypadUdev udev;
|
||||
InputUdev() : xlibKeyboard(*this), xlibMouse(*this), udev(*this) {}
|
||||
~InputUdev() { term(); }
|
||||
|
||||
struct Settings {
|
||||
uintptr_t handle = 0;
|
||||
} settings;
|
||||
|
||||
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 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 acquired() -> bool {
|
||||
return xlibMouse.acquired();
|
||||
}
|
||||
|
||||
auto poll() -> vector<shared_pointer<HID::Device>> {
|
||||
vector<shared_pointer<HID::Device>> devices;
|
||||
xlibKeyboard.poll(devices);
|
||||
xlibMouse.poll(devices);
|
||||
udev.poll(devices);
|
||||
return devices;
|
||||
}
|
||||
|
||||
auto rumble(uint64_t id, bool enable) -> bool {
|
||||
return udev.rumble(id, enable);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
if(xlibKeyboard.init() == false) return false;
|
||||
if(xlibMouse.init(settings.handle) == false) return false;
|
||||
if(udev.init() == false) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
xlibKeyboard.term();
|
||||
xlibMouse.term();
|
||||
udev.term();
|
||||
}
|
||||
};
|
105
input/windows.cpp
Normal file
105
input/windows.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
#include <xinput.h>
|
||||
#define DIRECTINPUT_VERSION 0x0800
|
||||
#include <dinput.h>
|
||||
|
||||
#include "shared/rawinput.cpp"
|
||||
#include "keyboard/rawinput.cpp"
|
||||
#include "mouse/rawinput.cpp"
|
||||
#include "joypad/xinput.cpp"
|
||||
#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(); }
|
||||
|
||||
LPDIRECTINPUT8 directinputContext = nullptr;
|
||||
|
||||
struct Settings {
|
||||
uintptr_t handle = 0;
|
||||
} settings;
|
||||
|
||||
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 acquired() -> bool {
|
||||
return rawinputMouse.acquired();
|
||||
}
|
||||
|
||||
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);
|
||||
return devices;
|
||||
}
|
||||
|
||||
auto rumble(uint64_t id, bool enable) -> bool {
|
||||
if(xinput.rumble(id, enable)) return true;
|
||||
if(directinput.rumble(id, enable)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
if(rawinput.initialized == false) {
|
||||
rawinput.initialized = true;
|
||||
rawinput.mutex = CreateMutex(NULL, FALSE, NULL);
|
||||
CreateThread(NULL, 0, RawInputThreadProc, 0, 0, NULL);
|
||||
|
||||
do {
|
||||
Sleep(1);
|
||||
WaitForSingleObject(rawinput.mutex, INFINITE);
|
||||
ReleaseMutex(rawinput.mutex);
|
||||
} while(rawinput.ready == false);
|
||||
}
|
||||
|
||||
DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&directinputContext, 0);
|
||||
if(directinputContext == nullptr) 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;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
rawinputKeyboard.term();
|
||||
rawinputMouse.term();
|
||||
xinput.term();
|
||||
directinput.term();
|
||||
|
||||
if(directinputContext) { directinputContext->Release(); directinputContext = nullptr; }
|
||||
}
|
||||
};
|
73
input/xlib.cpp
Normal file
73
input/xlib.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xatom.h>
|
||||
|
||||
#include "keyboard/xlib.cpp"
|
||||
#include "mouse/xlib.cpp"
|
||||
|
||||
struct InputXlib : Input {
|
||||
InputKeyboardXlib xlibKeyboard;
|
||||
InputMouseXlib xlibMouse;
|
||||
InputXlib() : xlibKeyboard(*this), xlibMouse(*this) {}
|
||||
~InputXlib() { term(); }
|
||||
|
||||
struct Settings {
|
||||
uintptr 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>()) {
|
||||
settings.handle = value.get<uintptr>();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto acquire() -> bool {
|
||||
return xlibMouse.acquire();
|
||||
}
|
||||
|
||||
auto release() -> bool {
|
||||
return xlibMouse.release();
|
||||
}
|
||||
|
||||
auto acquired() -> bool {
|
||||
return xlibMouse.acquired();
|
||||
}
|
||||
|
||||
auto poll() -> vector<shared_pointer<HID::Device>> {
|
||||
vector<shared_pointer<HID::Device>> devices;
|
||||
xlibKeyboard.poll(devices);
|
||||
xlibMouse.poll(devices);
|
||||
return devices;
|
||||
}
|
||||
|
||||
auto rumble(uint64_t id, bool enable) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
if(!xlibKeyboard.init()) return false;
|
||||
if(!xlibMouse.init(settings.handle)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
xlibKeyboard.term();
|
||||
xlibMouse.term();
|
||||
}
|
||||
};
|
541
ruby.cpp
Normal file
541
ruby.cpp
Normal file
|
@ -0,0 +1,541 @@
|
|||
#ifdef _WIN32
|
||||
#include <initguid.h>
|
||||
#include <cguid.h>
|
||||
#endif
|
||||
|
||||
#include <ruby/ruby.hpp>
|
||||
using namespace nall;
|
||||
using namespace ruby;
|
||||
|
||||
/* Shared */
|
||||
|
||||
#undef deprecated
|
||||
#undef mkdir
|
||||
#undef usleep
|
||||
|
||||
#if defined(DISPLAY_XORG)
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xatom.h>
|
||||
#elif defined(DISPLAY_QUARTZ)
|
||||
#define Boolean CocoaBoolean
|
||||
#define decimal CocoaDecimal
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <Carbon/Carbon.h>
|
||||
#undef Boolean
|
||||
#undef decimal
|
||||
#elif defined(DISPLAY_WINDOWS)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
/* Video */
|
||||
|
||||
#if defined(VIDEO_CGL)
|
||||
#include <ruby/video/cgl.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_DIRECT3D)
|
||||
#include <ruby/video/direct3d.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_DIRECTDRAW)
|
||||
#include <ruby/video/directdraw.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_GDI)
|
||||
#include <ruby/video/gdi.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_GLX)
|
||||
#include <ruby/video/glx.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_GLX2)
|
||||
#include <ruby/video/glx2.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_SDL)
|
||||
#include <ruby/video/sdl.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_WGL)
|
||||
#include <ruby/video/wgl.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_XSHM)
|
||||
#include <ruby/video/xshm.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_XV)
|
||||
#include <ruby/video/xv.cpp>
|
||||
#endif
|
||||
|
||||
namespace ruby {
|
||||
|
||||
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 unsigned Video::FilterNearest = 0;
|
||||
const unsigned Video::FilterLinear = 1;
|
||||
|
||||
auto Video::create(const string& driver) -> Video* {
|
||||
if(!driver) return create(optimalDriver());
|
||||
|
||||
#if defined(VIDEO_CGL)
|
||||
if(driver == "OpenGL") return new VideoCGL;
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_DIRECT3D)
|
||||
if(driver == "Direct3D") return new VideoD3D;
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_DIRECTDRAW)
|
||||
if(driver == "DirectDraw") return new VideoDD;
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_GDI)
|
||||
if(driver == "GDI") return new VideoGDI;
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_GLX)
|
||||
if(driver == "OpenGL") return new VideoGLX;
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_GLX2)
|
||||
if(driver == "OpenGL2") return new VideoGLX2;
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_SDL)
|
||||
if(driver == "SDL") return new VideoSDL;
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_WGL)
|
||||
if(driver == "OpenGL") return new VideoWGL;
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_XSHM)
|
||||
if(driver == "XShm") return new VideoXShm;
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_XV)
|
||||
if(driver == "X-Video") return new VideoXv;
|
||||
#endif
|
||||
|
||||
return new Video;
|
||||
}
|
||||
|
||||
auto Video::optimalDriver() -> string {
|
||||
#if defined(VIDEO_WGL)
|
||||
return "OpenGL";
|
||||
#elif defined(VIDEO_DIRECT3D)
|
||||
return "Direct3D";
|
||||
#elif defined(VIDEO_DIRECTDRAW)
|
||||
return "DirectDraw";
|
||||
#elif defined(VIDEO_GDI)
|
||||
return "GDI";
|
||||
#elif defined(VIDEO_CGL)
|
||||
return "OpenGL";
|
||||
#elif defined(VIDEO_GLX)
|
||||
return "OpenGL";
|
||||
#elif defined(VIDEO_GLX2)
|
||||
return "OpenGL2";
|
||||
#elif defined(VIDEO_XV)
|
||||
return "X-Video";
|
||||
#elif defined(VIDEO_XSHM)
|
||||
return "XShm";
|
||||
#elif defined(VIDEO_SDL)
|
||||
return "SDL";
|
||||
#else
|
||||
return "None";
|
||||
#endif
|
||||
}
|
||||
|
||||
auto Video::safestDriver() -> string {
|
||||
#if defined(VIDEO_DIRECT3D)
|
||||
return "Direct3D";
|
||||
#elif defined(VIDEO_WGL)
|
||||
return "OpenGL";
|
||||
#elif defined(VIDEO_DIRECTDRAW)
|
||||
return "DirectDraw";
|
||||
#elif defined(VIDEO_GDI)
|
||||
return "GDI";
|
||||
#elif defined(VIDEO_CGL)
|
||||
return "OpenGL";
|
||||
#elif defined(VIDEO_XSHM)
|
||||
return "XShm";
|
||||
#elif defined(VIDEO_SDL)
|
||||
return "SDL";
|
||||
#elif defined(VIDEO_XV)
|
||||
return "X-Video";
|
||||
#elif defined(VIDEO_GLX2)
|
||||
return "OpenGL2";
|
||||
#elif defined(VIDEO_GLX)
|
||||
return "OpenGL";
|
||||
#else
|
||||
return "None";
|
||||
#endif
|
||||
}
|
||||
|
||||
auto Video::availableDrivers() -> lstring {
|
||||
return {
|
||||
|
||||
#if defined(VIDEO_WGL)
|
||||
"OpenGL",
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_DIRECT3D)
|
||||
"Direct3D",
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_DIRECTDRAW)
|
||||
"DirectDraw",
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_GDI)
|
||||
"GDI",
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_CGL)
|
||||
"OpenGL",
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_GLX)
|
||||
"OpenGL",
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_GLX2)
|
||||
"OpenGL2",
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_XV)
|
||||
"X-Video",
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_XSHM)
|
||||
"XShm",
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_SDL)
|
||||
"SDL",
|
||||
#endif
|
||||
|
||||
"None"};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Audio */
|
||||
|
||||
#if defined(AUDIO_ALSA)
|
||||
#include <ruby/audio/alsa.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_AO)
|
||||
#include <ruby/audio/ao.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_DIRECTSOUND)
|
||||
#include <ruby/audio/directsound.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_OPENAL)
|
||||
#include <ruby/audio/openal.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_OSS)
|
||||
#include <ruby/audio/oss.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_PULSEAUDIO)
|
||||
#include <ruby/audio/pulseaudio.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_PULSEAUDIOSIMPLE)
|
||||
#include <ruby/audio/pulseaudiosimple.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_WASAPI)
|
||||
#include <ruby/audio/wasapi.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_XAUDIO2)
|
||||
#include <ruby/audio/xaudio2.cpp>
|
||||
#endif
|
||||
|
||||
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());
|
||||
|
||||
#if defined(AUDIO_ALSA)
|
||||
if(driver == "ALSA") return new AudioALSA;
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_AO)
|
||||
if(driver == "libao") return new AudioAO;
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_DIRECTSOUND)
|
||||
if(driver == "DirectSound") return new AudioDS;
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_OPENAL)
|
||||
if(driver == "OpenAL") return new AudioOpenAL;
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_OSS)
|
||||
if(driver == "OSS") return new AudioOSS;
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_PULSEAUDIO)
|
||||
if(driver == "PulseAudio") return new AudioPulseAudio;
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_PULSEAUDIOSIMPLE)
|
||||
if(driver == "PulseAudioSimple") return new AudioPulseAudioSimple;
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_WASAPI)
|
||||
if(driver == "WASAPI") return new AudioWASAPI;
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_XAUDIO2)
|
||||
if(driver == "XAudio2") return new AudioXAudio2;
|
||||
#endif
|
||||
|
||||
return new Audio;
|
||||
}
|
||||
|
||||
auto Audio::optimalDriver() -> string {
|
||||
#if defined(AUDIO_WASAPI)
|
||||
return "WASAPI";
|
||||
#elif defined(AUDIO_XAUDIO2)
|
||||
return "XAudio2";
|
||||
#elif defined(AUDIO_DIRECTSOUND)
|
||||
return "DirectSound";
|
||||
#elif defined(AUDIO_ALSA)
|
||||
return "ALSA";
|
||||
#elif defined(AUDIO_OSS)
|
||||
return "OSS";
|
||||
#elif defined(AUDIO_OPENAL)
|
||||
return "OpenAL";
|
||||
#elif defined(AUDIO_PULSEAUDIO)
|
||||
return "PulseAudio";
|
||||
#elif defined(AUDIO_PULSEAUDIOSIMPLE)
|
||||
return "PulseAudioSimple";
|
||||
#elif defined(AUDIO_AO)
|
||||
return "libao";
|
||||
#else
|
||||
return "None";
|
||||
#endif
|
||||
}
|
||||
|
||||
auto Audio::safestDriver() -> string {
|
||||
#if defined(AUDIO_DIRECTSOUND)
|
||||
return "DirectSound";
|
||||
#elif defined(AUDIO_WASAPI)
|
||||
return "WASAPI";
|
||||
#elif defined(AUDIO_XAUDIO2)
|
||||
return "XAudio2";
|
||||
#elif defined(AUDIO_ALSA)
|
||||
return "ALSA";
|
||||
#elif defined(AUDIO_OSS)
|
||||
return "OSS";
|
||||
#elif defined(AUDIO_OPENAL)
|
||||
return "OpenAL";
|
||||
#elif defined(AUDIO_PULSEAUDIO)
|
||||
return "PulseAudio";
|
||||
#elif defined(AUDIO_PULSEAUDIOSIMPLE)
|
||||
return "PulseAudioSimple";
|
||||
#elif defined(AUDIO_AO)
|
||||
return "libao";
|
||||
#else
|
||||
return "None";
|
||||
#endif
|
||||
}
|
||||
|
||||
auto Audio::availableDrivers() -> lstring {
|
||||
return {
|
||||
|
||||
#if defined(AUDIO_WASAPI)
|
||||
"WASAPI",
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_XAUDIO2)
|
||||
"XAudio2",
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_DIRECTSOUND)
|
||||
"DirectSound",
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_ALSA)
|
||||
"ALSA",
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_OSS)
|
||||
"OSS",
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_OPENAL)
|
||||
"OpenAL",
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_PULSEAUDIO)
|
||||
"PulseAudio",
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_PULSEAUDIOSIMPLE)
|
||||
"PulseAudioSimple",
|
||||
#endif
|
||||
|
||||
#if defined(AUDIO_AO)
|
||||
"libao",
|
||||
#endif
|
||||
|
||||
"None"};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
#if defined(INPUT_CARBON)
|
||||
#include <ruby/input/carbon.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_QUARTZ)
|
||||
#include <ruby/input/quartz.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_SDL)
|
||||
#include <ruby/input/sdl.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_UDEV)
|
||||
#include <ruby/input/udev.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_WINDOWS)
|
||||
#include <ruby/input/windows.cpp>
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_XLIB)
|
||||
#include <ruby/input/xlib.cpp>
|
||||
#endif
|
||||
|
||||
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());
|
||||
|
||||
#if defined(INPUT_WINDOWS)
|
||||
if(driver == "Windows") return new InputWindows;
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_QUARTZ)
|
||||
if(driver == "Quartz") return new InputQuartz;
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_CARBON)
|
||||
if(driver == "Carbon") return new InputCarbon;
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_UDEV)
|
||||
if(driver == "udev") return new InputUdev;
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_SDL)
|
||||
if(driver == "SDL") return new InputSDL;
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_XLIB)
|
||||
if(driver == "Xlib") return new InputXlib;
|
||||
#endif
|
||||
|
||||
return new Input;
|
||||
}
|
||||
|
||||
auto Input::optimalDriver() -> string {
|
||||
#if defined(INPUT_WINDOWS)
|
||||
return "Windows";
|
||||
#elif defined(INPUT_QUARTZ)
|
||||
return "Quartz";
|
||||
#elif defined(INPUT_CARBON)
|
||||
return "Carbon";
|
||||
#elif defined(INPUT_UDEV)
|
||||
return "udev";
|
||||
#elif defined(INPUT_SDL)
|
||||
return "SDL";
|
||||
#elif defined(INPUT_XLIB)
|
||||
return "Xlib";
|
||||
#else
|
||||
return "None";
|
||||
#endif
|
||||
}
|
||||
|
||||
auto Input::safestDriver() -> string {
|
||||
#if defined(INPUT_WINDOWS)
|
||||
return "Windows";
|
||||
#elif defined(INPUT_QUARTZ)
|
||||
return "Quartz";
|
||||
#elif defined(INPUT_CARBON)
|
||||
return "Carbon";
|
||||
#elif defined(INPUT_UDEV)
|
||||
return "udev";
|
||||
#elif defined(INPUT_SDL)
|
||||
return "SDL";
|
||||
#elif defined(INPUT_XLIB)
|
||||
return "Xlib";
|
||||
#else
|
||||
return "none";
|
||||
#endif
|
||||
}
|
||||
|
||||
auto Input::availableDrivers() -> lstring {
|
||||
return {
|
||||
|
||||
#if defined(INPUT_WINDOWS)
|
||||
"Windows",
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_QUARTZ)
|
||||
"Quartz",
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_CARBON)
|
||||
"Carbon",
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_UDEV)
|
||||
"udev",
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_SDL)
|
||||
"SDL",
|
||||
#endif
|
||||
|
||||
#if defined(INPUT_XLIB)
|
||||
"Xlib",
|
||||
#endif
|
||||
|
||||
"None"};
|
||||
}
|
||||
|
||||
}
|
108
ruby.hpp
Normal file
108
ruby.hpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
#pragma once
|
||||
|
||||
/* ruby
|
||||
* author: byuu
|
||||
* license: ISC
|
||||
* version: 0.14 (2015-11-19)
|
||||
*
|
||||
* ruby is a cross-platform hardware abstraction layer
|
||||
* it provides a common interface to video, audio and input devices
|
||||
*/
|
||||
|
||||
#include <nall/nall.hpp>
|
||||
|
||||
namespace ruby {
|
||||
|
||||
struct Video {
|
||||
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 unsigned FilterNearest;
|
||||
static const unsigned FilterLinear;
|
||||
|
||||
static auto create(const nall::string& driver = "") -> Video*;
|
||||
static auto optimalDriver() -> nall::string;
|
||||
static auto safestDriver() -> nall::string;
|
||||
static auto availableDrivers() -> nall::lstring;
|
||||
|
||||
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 lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned 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 {}
|
||||
};
|
||||
|
||||
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::lstring;
|
||||
|
||||
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 sample(uint16_t left, uint16_t right) -> void {}
|
||||
virtual auto clear() -> void {}
|
||||
|
||||
virtual auto init() -> bool { return true; }
|
||||
virtual auto term() -> 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::lstring;
|
||||
|
||||
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 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>, unsigned, unsigned, int16_t, int16_t)>& callback) { _onChange = callback; }
|
||||
auto doChange(nall::shared_pointer<nall::HID::Device> device, unsigned group, unsigned input, int16_t oldValue, int16_t newValue) -> void {
|
||||
if(_onChange) _onChange(device, group, input, oldValue, newValue);
|
||||
}
|
||||
|
||||
private:
|
||||
nall::function<void (nall::shared_pointer<nall::HID::Device> device, unsigned group, unsigned input, int16_t oldValue, int16_t newValue)> _onChange;
|
||||
};
|
||||
|
||||
}
|
171
video/cgl.cpp
Normal file
171
video/cgl.cpp
Normal file
|
@ -0,0 +1,171 @@
|
|||
#define GL_ALPHA_TEST 0x0bc0
|
||||
#include "opengl/opengl.hpp"
|
||||
|
||||
struct VideoCGL;
|
||||
|
||||
@interface RubyVideoCGL : NSOpenGLView {
|
||||
@public
|
||||
VideoCGL* video;
|
||||
}
|
||||
-(id) initWith:(VideoCGL*)video pixelFormat:(NSOpenGLPixelFormat*)pixelFormat;
|
||||
-(void) reshape;
|
||||
@end
|
||||
|
||||
struct VideoCGL : Video, OpenGL {
|
||||
~VideoCGL() { term(); }
|
||||
|
||||
RubyVideoCGL* view = nullptr;
|
||||
|
||||
struct {
|
||||
NSView* handle = nullptr;
|
||||
bool synchronize = false;
|
||||
uint filter = Video::FilterNearest;
|
||||
string shader;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Video::Handle) return true;
|
||||
if(name == Video::Synchronize) return true;
|
||||
if(name == Video::Filter) return true;
|
||||
if(name == Video::Shader) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Video::Handle) return (uintptr)settings.handle;
|
||||
if(name == Video::Synchronize) return settings.synchronize;
|
||||
if(name == Video::Filter) return settings.filter;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Video::Handle && value.is<uintptr>()) {
|
||||
settings.handle = (NSView*)value.get<uintptr>();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Synchronize && value.is<bool>()) {
|
||||
if(settings.synchronize != value.get<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
|
||||
if(view) {
|
||||
@autoreleasepool {
|
||||
[[view openGLContext] makeCurrentContext];
|
||||
int synchronize = settings.synchronize;
|
||||
[[view openGLContext] setValues:&synchronize forParameter:NSOpenGLCPSwapInterval];
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Filter && value.is<uint>()) {
|
||||
settings.filter = value.get<uint>();
|
||||
if(!settings.shader) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Shader && value.is<string>()) {
|
||||
settings.shader = value.get<string>();
|
||||
@autoreleasepool {
|
||||
[[view openGLContext] makeCurrentContext];
|
||||
}
|
||||
OpenGL::shader(settings.shader);
|
||||
if(!settings.shader) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto lock(uint32*& data, uint& pitch, uint width, uint height) -> bool {
|
||||
OpenGL::size(width, height);
|
||||
return OpenGL::lock(data, pitch);
|
||||
}
|
||||
|
||||
auto unlock() -> void {
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
@autoreleasepool {
|
||||
[view lockFocus];
|
||||
OpenGL::clear();
|
||||
[[view openGLContext] flushBuffer];
|
||||
[view unlockFocus];
|
||||
}
|
||||
}
|
||||
|
||||
auto refresh() -> void {
|
||||
@autoreleasepool {
|
||||
if([view lockFocusIfCanDraw]) {
|
||||
auto area = [view frame];
|
||||
outputWidth = area.size.width;
|
||||
outputHeight = area.size.height;
|
||||
OpenGL::refresh();
|
||||
[[view openGLContext] flushBuffer];
|
||||
[view unlockFocus];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
@autoreleasepool {
|
||||
NSOpenGLPixelFormatAttribute attributes[] = {
|
||||
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
||||
NSOpenGLPFAColorSize, 24,
|
||||
NSOpenGLPFAAlphaSize, 8,
|
||||
NSOpenGLPFADoubleBuffer,
|
||||
0
|
||||
};
|
||||
|
||||
auto size = [settings.handle frame].size;
|
||||
auto format = [[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes] autorelease];
|
||||
auto context = [[[NSOpenGLContext alloc] initWithFormat:format shareContext:nil] autorelease];
|
||||
|
||||
view = [[RubyVideoCGL alloc] initWith:this pixelFormat:format];
|
||||
[view setOpenGLContext:context];
|
||||
[view setFrame:NSMakeRect(0, 0, size.width, size.height)];
|
||||
[view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
||||
[settings.handle addSubview:view];
|
||||
[context setView:view];
|
||||
|
||||
[view lockFocus];
|
||||
|
||||
OpenGL::init();
|
||||
//print((const char*)glGetString(GL_VERSION), "\n");
|
||||
|
||||
int synchronize = settings.synchronize;
|
||||
[[view openGLContext] setValues:&synchronize forParameter:NSOpenGLCPSwapInterval];
|
||||
|
||||
[view unlockFocus];
|
||||
}
|
||||
|
||||
clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
OpenGL::term();
|
||||
|
||||
@autoreleasepool {
|
||||
[view removeFromSuperview];
|
||||
[view release];
|
||||
view = nil;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@implementation RubyVideoCGL : NSOpenGLView
|
||||
|
||||
-(id) initWith:(VideoCGL*)videoPointer pixelFormat:(NSOpenGLPixelFormat*)pixelFormat {
|
||||
if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0) pixelFormat:pixelFormat]) {
|
||||
video = videoPointer;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) reshape {
|
||||
video->refresh();
|
||||
}
|
||||
|
||||
@end
|
443
video/direct3d.cpp
Normal file
443
video/direct3d.cpp
Normal file
|
@ -0,0 +1,443 @@
|
|||
#undef interface
|
||||
#define interface struct
|
||||
#include <d3d9.h>
|
||||
#include <d3dx9.h>
|
||||
#undef interface
|
||||
|
||||
#define D3DVERTEX (D3DFVF_XYZRHW | D3DFVF_TEX1)
|
||||
|
||||
typedef HRESULT (__stdcall* EffectProc)(LPDIRECT3DDEVICE9, LPCVOID, UINT, D3DXMACRO const*, LPD3DXINCLUDE, DWORD, LPD3DXEFFECTPOOL, LPD3DXEFFECT*, LPD3DXBUFFER*);
|
||||
typedef HRESULT (__stdcall* TextureProc)(LPDIRECT3DDEVICE9, LPCTSTR, LPDIRECT3DTEXTURE9*);
|
||||
|
||||
struct VideoD3D : Video {
|
||||
~VideoD3D() { term(); }
|
||||
|
||||
LPDIRECT3D9 lpd3d = nullptr;
|
||||
LPDIRECT3DDEVICE9 device = nullptr;
|
||||
LPDIRECT3DVERTEXBUFFER9 vertex_buffer = nullptr;
|
||||
LPDIRECT3DVERTEXBUFFER9* vertex_ptr = nullptr;
|
||||
D3DPRESENT_PARAMETERS presentation;
|
||||
D3DSURFACE_DESC d3dsd;
|
||||
D3DLOCKED_RECT d3dlr;
|
||||
D3DRASTER_STATUS d3drs;
|
||||
D3DCAPS9 d3dcaps;
|
||||
LPDIRECT3DTEXTURE9 texture = nullptr;
|
||||
LPDIRECT3DSURFACE9 surface = nullptr;
|
||||
LPD3DXEFFECT effect = nullptr;
|
||||
string shader_source_markup;
|
||||
|
||||
bool lost = true;
|
||||
unsigned iwidth;
|
||||
unsigned iheight;
|
||||
|
||||
struct d3dvertex {
|
||||
float x, y, z, rhw; //screen coords
|
||||
float u, v; //texture coords
|
||||
};
|
||||
|
||||
struct {
|
||||
uint32_t t_usage, v_usage;
|
||||
uint32_t t_pool, v_pool;
|
||||
uint32_t lock;
|
||||
uint32_t filter;
|
||||
} flags;
|
||||
|
||||
struct {
|
||||
bool dynamic; //device supports dynamic textures
|
||||
bool shader; //device supports pixel shaders
|
||||
} caps;
|
||||
|
||||
struct {
|
||||
HWND handle = nullptr;
|
||||
bool synchronize = false;
|
||||
unsigned filter = Video::FilterLinear;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
} settings;
|
||||
|
||||
struct {
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
} state;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Video::Handle) return true;
|
||||
if(name == Video::Synchronize) return true;
|
||||
if(name == Video::Filter) return true;
|
||||
if(name == Video::Shader) return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Video::Handle) return (uintptr_t)settings.handle;
|
||||
if(name == Video::Synchronize) return settings.synchronize;
|
||||
if(name == Video::Filter) return settings.filter;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Video::Handle && value.is<uintptr_t>()) {
|
||||
settings.handle = (HWND)value.get<uintptr_t>();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Synchronize && value.is<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Filter && value.is<unsigned>()) {
|
||||
settings.filter = value.get<unsigned>();
|
||||
if(lpd3d) update_filter();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Shader && value.is<string>()) {
|
||||
return false;
|
||||
//set_shader(value.get<string>());
|
||||
//return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto recover() -> bool {
|
||||
if(!device) return false;
|
||||
|
||||
if(lost) {
|
||||
release_resources();
|
||||
if(device->Reset(&presentation) != D3D_OK) return false;
|
||||
}
|
||||
|
||||
lost = 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_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_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
|
||||
|
||||
device->SetVertexShader(NULL);
|
||||
device->SetFVF(D3DVERTEX);
|
||||
|
||||
device->CreateVertexBuffer(sizeof(d3dvertex) * 4, flags.v_usage, D3DVERTEX, (D3DPOOL)flags.v_pool, &vertex_buffer, NULL);
|
||||
iwidth = 0;
|
||||
iheight = 0;
|
||||
resize(settings.width = 256, settings.height = 256);
|
||||
update_filter();
|
||||
clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto rounded_power_of_two(unsigned n) -> unsigned {
|
||||
n--;
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
n |= n >> 16;
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
auto resize(unsigned width, unsigned height) -> void {
|
||||
if(iwidth >= width && iheight >= height) return;
|
||||
|
||||
iwidth = rounded_power_of_two(max(width, iwidth ));
|
||||
iheight = rounded_power_of_two(max(height, iheight));
|
||||
|
||||
if(d3dcaps.MaxTextureWidth < iwidth || d3dcaps.MaxTextureWidth < iheight) {
|
||||
//TODO: attempt to handle this more gracefully
|
||||
return;
|
||||
}
|
||||
|
||||
if(texture) texture->Release();
|
||||
device->CreateTexture(iwidth, iheight, 1, flags.t_usage, D3DFMT_X8R8G8B8, (D3DPOOL)flags.t_pool, &texture, NULL);
|
||||
}
|
||||
|
||||
auto update_filter() -> void {
|
||||
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);
|
||||
}
|
||||
|
||||
// Vertex format:
|
||||
//
|
||||
// 0----------1
|
||||
// | /|
|
||||
// | / |
|
||||
// | / |
|
||||
// | / |
|
||||
// | / |
|
||||
// 2----------3
|
||||
//
|
||||
// (x,y) screen coords, in pixels
|
||||
// (u,v) texture coords, betweeen 0.0 (top, left) to 1.0 (bottom, right)
|
||||
auto set_vertex(
|
||||
uint32_t px, uint32_t py, uint32_t pw, uint32_t ph,
|
||||
uint32_t tw, uint32_t th,
|
||||
uint32_t x, uint32_t y, uint32_t w, uint32_t h
|
||||
) -> void {
|
||||
d3dvertex vertex[4];
|
||||
vertex[0].x = vertex[2].x = (double)(x - 0.5);
|
||||
vertex[1].x = vertex[3].x = (double)(x + w - 0.5);
|
||||
vertex[0].y = vertex[1].y = (double)(y - 0.5);
|
||||
vertex[2].y = vertex[3].y = (double)(y + h - 0.5);
|
||||
|
||||
//Z-buffer and RHW are unused for 2D blit, set to normal values
|
||||
vertex[0].z = vertex[1].z = vertex[2].z = vertex[3].z = 0.0;
|
||||
vertex[0].rhw = vertex[1].rhw = vertex[2].rhw = vertex[3].rhw = 1.0;
|
||||
|
||||
double rw = (double)w / (double)pw * (double)tw;
|
||||
double rh = (double)h / (double)ph * (double)th;
|
||||
vertex[0].u = vertex[2].u = (double)(px ) / rw;
|
||||
vertex[1].u = vertex[3].u = (double)(px + w) / rw;
|
||||
vertex[0].v = vertex[1].v = (double)(py ) / rh;
|
||||
vertex[2].v = vertex[3].v = (double)(py + h) / rh;
|
||||
|
||||
vertex_buffer->Lock(0, sizeof(d3dvertex) * 4, (void**)&vertex_ptr, 0);
|
||||
memcpy(vertex_ptr, vertex, sizeof(d3dvertex) * 4);
|
||||
vertex_buffer->Unlock();
|
||||
|
||||
device->SetStreamSource(0, vertex_buffer, 0, sizeof(d3dvertex));
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
if(lost && !recover()) return;
|
||||
|
||||
texture->GetLevelDesc(0, &d3dsd);
|
||||
texture->GetSurfaceLevel(0, &surface);
|
||||
|
||||
if(surface) {
|
||||
device->ColorFill(surface, 0, D3DCOLOR_XRGB(0x00, 0x00, 0x00));
|
||||
surface->Release();
|
||||
surface = nullptr;
|
||||
}
|
||||
|
||||
//clear primary display and all backbuffers
|
||||
for(unsigned i = 0; i < 3; i++) {
|
||||
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, unsigned& pitch, unsigned width, unsigned height) -> bool {
|
||||
if(lost && !recover()) return false;
|
||||
|
||||
if(width != settings.width || height != settings.height) {
|
||||
resize(settings.width = width, settings.height = height);
|
||||
}
|
||||
|
||||
texture->GetLevelDesc(0, &d3dsd);
|
||||
texture->GetSurfaceLevel(0, &surface);
|
||||
|
||||
surface->LockRect(&d3dlr, 0, flags.lock);
|
||||
pitch = d3dlr.Pitch;
|
||||
return data = (uint32_t*)d3dlr.pBits;
|
||||
}
|
||||
|
||||
auto unlock() -> void {
|
||||
surface->UnlockRect();
|
||||
surface->Release();
|
||||
surface = nullptr;
|
||||
}
|
||||
|
||||
auto refresh() -> void {
|
||||
if(lost && !recover()) return;
|
||||
|
||||
RECT rd, rs; //dest, source rectangles
|
||||
GetClientRect(settings.handle, &rd);
|
||||
SetRect(&rs, 0, 0, settings.width, settings.height);
|
||||
|
||||
//if output size changed, driver must be re-initialized.
|
||||
//failure to do so causes scaling issues on some video drivers.
|
||||
if(state.width != rd.right || state.height != rd.bottom) {
|
||||
init();
|
||||
set_shader(shader_source_markup);
|
||||
return;
|
||||
}
|
||||
|
||||
if(caps.shader && effect) {
|
||||
device->BeginScene();
|
||||
set_vertex(0, 0, settings.width, settings.height, iwidth, iheight, 0, 0, rd.right, rd.bottom);
|
||||
|
||||
D3DXVECTOR4 rubyTextureSize;
|
||||
rubyTextureSize.x = iwidth;
|
||||
rubyTextureSize.y = iheight;
|
||||
rubyTextureSize.z = 1.0 / iheight;
|
||||
rubyTextureSize.w = 1.0 / iwidth;
|
||||
effect->SetVector("rubyTextureSize", &rubyTextureSize);
|
||||
|
||||
D3DXVECTOR4 rubyInputSize;
|
||||
rubyInputSize.x = settings.width;
|
||||
rubyInputSize.y = settings.height;
|
||||
rubyInputSize.z = 1.0 / settings.height;
|
||||
rubyInputSize.w = 1.0 / settings.width;
|
||||
effect->SetVector("rubyInputSize", &rubyInputSize);
|
||||
|
||||
D3DXVECTOR4 rubyOutputSize;
|
||||
rubyOutputSize.x = rd.right;
|
||||
rubyOutputSize.y = rd.bottom;
|
||||
rubyOutputSize.z = 1.0 / rd.bottom;
|
||||
rubyOutputSize.w = 1.0 / rd.right;
|
||||
effect->SetVector("rubyOutputSize", &rubyOutputSize);
|
||||
|
||||
UINT passes;
|
||||
effect->Begin(&passes, 0);
|
||||
effect->SetTexture("rubyTexture", texture);
|
||||
device->SetTexture(0, texture);
|
||||
for(unsigned pass = 0; pass < passes; pass++) {
|
||||
effect->BeginPass(pass);
|
||||
device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
|
||||
effect->EndPass();
|
||||
}
|
||||
effect->End();
|
||||
device->EndScene();
|
||||
} else {
|
||||
device->BeginScene();
|
||||
set_vertex(0, 0, settings.width, settings.height, iwidth, iheight, 0, 0, rd.right, rd.bottom);
|
||||
device->SetTexture(0, texture);
|
||||
device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
|
||||
device->EndScene();
|
||||
}
|
||||
|
||||
if(settings.synchronize) {
|
||||
D3DRASTER_STATUS status;
|
||||
//wait for a previous vblank to finish, if necessary
|
||||
while(true) {
|
||||
device->GetRasterStatus(0, &status);
|
||||
if(status.InVBlank == false) break;
|
||||
}
|
||||
//wait for next vblank to begin
|
||||
while(true) {
|
||||
device->GetRasterStatus(0, &status);
|
||||
if(status.InVBlank == true) break;
|
||||
}
|
||||
}
|
||||
|
||||
if(device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) lost = true;
|
||||
}
|
||||
|
||||
auto set_shader(const char* source) -> void {
|
||||
if(!caps.shader) return;
|
||||
|
||||
if(effect) {
|
||||
effect->Release();
|
||||
effect = NULL;
|
||||
}
|
||||
|
||||
if(!source || !*source) {
|
||||
shader_source_markup = "";
|
||||
return;
|
||||
}
|
||||
shader_source_markup = source;
|
||||
|
||||
auto document = BML::unserialize(shader_source_markup);
|
||||
bool is_hlsl = document["shader"]["language"].text() == "HLSL";
|
||||
string shader_source = document["shader"]["source"].text();
|
||||
if(shader_source == "") return;
|
||||
|
||||
HMODULE d3dx;
|
||||
for(unsigned i = 0; i < 256; i++) {
|
||||
char t[256];
|
||||
sprintf(t, "d3dx9_%u.dll", i);
|
||||
d3dx = LoadLibraryW(utf16_t(t));
|
||||
if(d3dx) break;
|
||||
}
|
||||
if(!d3dx) d3dx = LoadLibraryW(L"d3dx9.dll");
|
||||
if(!d3dx) return;
|
||||
|
||||
EffectProc effectProc = (EffectProc)GetProcAddress(d3dx, "D3DXCreateEffect");
|
||||
TextureProc textureProc = (TextureProc)GetProcAddress(d3dx, "D3DXCreateTextureFromFileA");
|
||||
|
||||
LPD3DXBUFFER pBufferErrors = NULL;
|
||||
effectProc(device, (const void*)shader_source.data(), lstrlenA(shader_source), NULL, NULL, 0, NULL, &effect, &pBufferErrors);
|
||||
|
||||
D3DXHANDLE hTech;
|
||||
effect->FindNextValidTechnique(NULL, &hTech);
|
||||
effect->SetTechnique(hTech);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
RECT rd;
|
||||
GetClientRect(settings.handle, &rd);
|
||||
state.width = rd.right;
|
||||
state.height = rd.bottom;
|
||||
|
||||
lpd3d = Direct3DCreate9(D3D_SDK_VERSION);
|
||||
if(!lpd3d) return false;
|
||||
|
||||
memset(&presentation, 0, sizeof(presentation));
|
||||
presentation.Flags = D3DPRESENTFLAG_VIDEO;
|
||||
presentation.SwapEffect = D3DSWAPEFFECT_FLIP;
|
||||
presentation.hDeviceWindow = settings.handle;
|
||||
presentation.BackBufferCount = 1;
|
||||
presentation.MultiSampleType = D3DMULTISAMPLE_NONE;
|
||||
presentation.MultiSampleQuality = 0;
|
||||
presentation.EnableAutoDepthStencil = false;
|
||||
presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN;
|
||||
presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
||||
presentation.Windowed = true;
|
||||
presentation.BackBufferFormat = D3DFMT_UNKNOWN;
|
||||
presentation.BackBufferWidth = 0;
|
||||
presentation.BackBufferHeight = 0;
|
||||
|
||||
if(lpd3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, settings.handle,
|
||||
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device->GetDeviceCaps(&d3dcaps);
|
||||
|
||||
caps.dynamic = bool(d3dcaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES);
|
||||
caps.shader = d3dcaps.PixelShaderVersion > D3DPS_VERSION(1, 4);
|
||||
|
||||
if(caps.dynamic == true) {
|
||||
flags.t_usage = D3DUSAGE_DYNAMIC;
|
||||
flags.v_usage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC;
|
||||
flags.t_pool = D3DPOOL_DEFAULT;
|
||||
flags.v_pool = D3DPOOL_DEFAULT;
|
||||
flags.lock = D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD;
|
||||
} else {
|
||||
flags.t_usage = 0;
|
||||
flags.v_usage = D3DUSAGE_WRITEONLY;
|
||||
flags.t_pool = D3DPOOL_MANAGED;
|
||||
flags.v_pool = D3DPOOL_MANAGED;
|
||||
flags.lock = D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD;
|
||||
}
|
||||
|
||||
lost = false;
|
||||
recover();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto release_resources() -> void {
|
||||
if(effect) { effect->Release(); effect = 0; }
|
||||
if(vertex_buffer) { vertex_buffer->Release(); vertex_buffer = 0; }
|
||||
if(surface) { surface->Release(); surface = 0; }
|
||||
if(texture) { texture->Release(); texture = 0; }
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
release_resources();
|
||||
if(device) { device->Release(); device = 0; }
|
||||
if(lpd3d) { lpd3d->Release(); lpd3d = 0; }
|
||||
}
|
||||
};
|
||||
|
||||
#undef D3DVERTEX
|
171
video/directdraw.cpp
Normal file
171
video/directdraw.cpp
Normal file
|
@ -0,0 +1,171 @@
|
|||
#include <ddraw.h>
|
||||
|
||||
struct VideoDD : Video {
|
||||
~VideoDD() { term(); }
|
||||
|
||||
LPDIRECTDRAW lpdd = nullptr;
|
||||
LPDIRECTDRAW7 lpdd7 = nullptr;
|
||||
LPDIRECTDRAWSURFACE7 screen = nullptr;
|
||||
LPDIRECTDRAWSURFACE7 raster = nullptr;
|
||||
LPDIRECTDRAWCLIPPER clipper = nullptr;
|
||||
DDSURFACEDESC2 ddsd;
|
||||
DDSCAPS2 ddscaps;
|
||||
unsigned iwidth;
|
||||
unsigned iheight;
|
||||
|
||||
struct {
|
||||
HWND handle = nullptr;
|
||||
bool synchronize = false;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Video::Handle) return true;
|
||||
if(name == Video::Synchronize) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Video::Handle) return (uintptr_t)settings.handle;
|
||||
if(name == Video::Synchronize) return settings.synchronize;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Video::Handle && value.is<uintptr_t>()) {
|
||||
settings.handle = (HWND)value.get<uintptr_t>();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Synchronize && value.is<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto resize(unsigned width, unsigned height) -> void {
|
||||
if(iwidth >= width && iheight >= height) return;
|
||||
|
||||
iwidth = max(width, iwidth);
|
||||
iheight = max(height, iheight);
|
||||
|
||||
if(raster) raster->Release();
|
||||
|
||||
screen->GetSurfaceDesc(&ddsd);
|
||||
int depth = ddsd.ddpfPixelFormat.dwRGBBitCount;
|
||||
if(depth == 32) goto try_native_surface;
|
||||
|
||||
memset(&ddsd, 0, sizeof(DDSURFACEDESC2));
|
||||
ddsd.dwSize = sizeof(DDSURFACEDESC2);
|
||||
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT;
|
||||
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; //DDSCAPS_SYSTEMMEMORY
|
||||
ddsd.dwWidth = iwidth;
|
||||
ddsd.dwHeight = iheight;
|
||||
|
||||
ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
|
||||
ddsd.ddpfPixelFormat.dwFlags = DDPF_RGB;
|
||||
ddsd.ddpfPixelFormat.dwRGBBitCount = 32;
|
||||
ddsd.ddpfPixelFormat.dwRBitMask = 0xff0000;
|
||||
ddsd.ddpfPixelFormat.dwGBitMask = 0x00ff00;
|
||||
ddsd.ddpfPixelFormat.dwBBitMask = 0x0000ff;
|
||||
|
||||
if(lpdd7->CreateSurface(&ddsd, &raster, 0) == DD_OK) return clear();
|
||||
|
||||
try_native_surface:
|
||||
memset(&ddsd, 0, sizeof(DDSURFACEDESC2));
|
||||
ddsd.dwSize = sizeof(DDSURFACEDESC2);
|
||||
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
|
||||
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; //DDSCAPS_SYSTEMMEMORY
|
||||
ddsd.dwWidth = iwidth;
|
||||
ddsd.dwHeight = iheight;
|
||||
|
||||
if(lpdd7->CreateSurface(&ddsd, &raster, 0) == DD_OK) return clear();
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
DDBLTFX fx;
|
||||
fx.dwSize = sizeof(DDBLTFX);
|
||||
fx.dwFillColor = 0x00000000;
|
||||
screen->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx);
|
||||
raster->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx);
|
||||
}
|
||||
|
||||
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
|
||||
if(width != settings.width || height != settings.height) {
|
||||
resize(settings.width = width, settings.height = height);
|
||||
}
|
||||
|
||||
if(raster->Lock(0, &ddsd, DDLOCK_WAIT, 0) != DD_OK) {
|
||||
raster->Restore();
|
||||
if(raster->Lock(0, &ddsd, DDLOCK_WAIT, 0) != DD_OK) return false;
|
||||
}
|
||||
pitch = ddsd.lPitch;
|
||||
return data = (uint32_t*)ddsd.lpSurface;
|
||||
}
|
||||
|
||||
auto unlock() -> void {
|
||||
raster->Unlock(0);
|
||||
}
|
||||
|
||||
auto refresh() -> void {
|
||||
if(settings.synchronize) {
|
||||
while(true) {
|
||||
BOOL in_vblank;
|
||||
lpdd7->GetVerticalBlankStatus(&in_vblank);
|
||||
if(in_vblank == true) break;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT hr;
|
||||
RECT rd, rs;
|
||||
SetRect(&rs, 0, 0, settings.width, settings.height);
|
||||
|
||||
POINT p = {0, 0};
|
||||
ClientToScreen(settings.handle, &p);
|
||||
GetClientRect(settings.handle, &rd);
|
||||
OffsetRect(&rd, p.x, p.y);
|
||||
|
||||
if(screen->Blt(&rd, raster, &rs, DDBLT_WAIT, 0) == DDERR_SURFACELOST) {
|
||||
screen->Restore();
|
||||
raster->Restore();
|
||||
}
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
DirectDrawCreate(0, &lpdd, 0);
|
||||
lpdd->QueryInterface(IID_IDirectDraw7, (void**)&lpdd7);
|
||||
if(lpdd) { lpdd->Release(); lpdd = 0; }
|
||||
|
||||
lpdd7->SetCooperativeLevel(settings.handle, DDSCL_NORMAL);
|
||||
|
||||
memset(&ddsd, 0, sizeof(DDSURFACEDESC2));
|
||||
ddsd.dwSize = sizeof(DDSURFACEDESC2);
|
||||
|
||||
ddsd.dwFlags = DDSD_CAPS;
|
||||
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
|
||||
lpdd7->CreateSurface(&ddsd, &screen, 0);
|
||||
|
||||
lpdd7->CreateClipper(0, &clipper, 0);
|
||||
clipper->SetHWnd(0, settings.handle);
|
||||
screen->SetClipper(clipper);
|
||||
|
||||
raster = 0;
|
||||
iwidth = 0;
|
||||
iheight = 0;
|
||||
resize(settings.width = 256, settings.height = 256);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(clipper) { clipper->Release(); clipper = 0; }
|
||||
if(raster) { raster->Release(); raster = 0; }
|
||||
if(screen) { screen->Release(); screen = 0; }
|
||||
if(lpdd7) { lpdd7->Release(); lpdd7 = 0; }
|
||||
if(lpdd) { lpdd->Release(); lpdd = 0; }
|
||||
}
|
||||
};
|
89
video/gdi.cpp
Normal file
89
video/gdi.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include <assert.h>
|
||||
|
||||
struct VideoGDI : Video {
|
||||
~VideoGDI() { term(); }
|
||||
|
||||
uint32_t* buffer = nullptr;
|
||||
HBITMAP bitmap = nullptr;
|
||||
HDC bitmapdc = nullptr;
|
||||
BITMAPINFO bmi;
|
||||
|
||||
struct {
|
||||
HWND handle = nullptr;
|
||||
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Video::Handle) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Video::Handle) return (uintptr_t)settings.handle;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Video::Handle && value.is<uintptr_t>()) {
|
||||
settings.handle = (HWND)value.get<uintptr_t>();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
|
||||
settings.width = width;
|
||||
settings.height = height;
|
||||
|
||||
pitch = 1024 * 4;
|
||||
return data = buffer;
|
||||
}
|
||||
|
||||
auto unlock() -> void {}
|
||||
|
||||
auto clear() -> void {}
|
||||
|
||||
auto refresh() -> void {
|
||||
RECT rc;
|
||||
GetClientRect(settings.handle, &rc);
|
||||
|
||||
SetDIBits(bitmapdc, bitmap, 0, settings.height, (void*)buffer, &bmi, DIB_RGB_COLORS);
|
||||
HDC hdc = GetDC(settings.handle);
|
||||
StretchBlt(hdc, rc.left, rc.top, rc.right, rc.bottom, bitmapdc, 0, 1024 - settings.height, settings.width, settings.height, SRCCOPY);
|
||||
ReleaseDC(settings.handle, hdc);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
buffer = (uint32_t*)memory::allocate(1024 * 1024 * sizeof(uint32_t));
|
||||
|
||||
HDC hdc = GetDC(settings.handle);
|
||||
bitmapdc = CreateCompatibleDC(hdc);
|
||||
assert(bitmapdc);
|
||||
bitmap = CreateCompatibleBitmap(hdc, 1024, 1024);
|
||||
assert(bitmap);
|
||||
SelectObject(bitmapdc, bitmap);
|
||||
ReleaseDC(settings.handle, hdc);
|
||||
|
||||
memset(&bmi, 0, sizeof(BITMAPINFO));
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = 1024;
|
||||
bmi.bmiHeader.biHeight = -1024;
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32; //biBitCount of 15 is invalid, biBitCount of 16 is really RGB555
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
bmi.bmiHeader.biSizeImage = 1024 * 1024 * sizeof(uint32_t);
|
||||
|
||||
settings.width = 256;
|
||||
settings.height = 256;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
DeleteObject(bitmap);
|
||||
DeleteDC(bitmapdc);
|
||||
if(buffer) { memory::free(buffer); buffer = nullptr; }
|
||||
}
|
||||
};
|
250
video/glx.cpp
Normal file
250
video/glx.cpp
Normal file
|
@ -0,0 +1,250 @@
|
|||
#include "opengl/opengl.hpp"
|
||||
|
||||
#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
|
||||
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
|
||||
|
||||
auto VideoGLX_X11ErrorHandler(Display*, XErrorEvent*) -> int {
|
||||
return 0; //suppress errors
|
||||
}
|
||||
|
||||
struct VideoGLX : Video, OpenGL {
|
||||
~VideoGLX() { term(); }
|
||||
|
||||
auto (*glXSwapInterval)(signed) -> signed = nullptr;
|
||||
|
||||
Display* display = nullptr;
|
||||
signed screen = 0;
|
||||
Window xwindow = 0;
|
||||
Colormap colormap = 0;
|
||||
GLXContext glxcontext = nullptr;
|
||||
GLXWindow glxwindow = 0;
|
||||
|
||||
struct {
|
||||
signed versionMajor = 0;
|
||||
signed versionMinor = 0;
|
||||
bool doubleBuffer = false;
|
||||
bool isDirect = false;
|
||||
} glx;
|
||||
|
||||
struct {
|
||||
Window handle = 0;
|
||||
bool synchronize = false;
|
||||
unsigned depth = 24;
|
||||
unsigned filter = 1; //linear
|
||||
string shader;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Video::Handle) return true;
|
||||
if(name == Video::Synchronize) return true;
|
||||
if(name == Video::Depth) return true;
|
||||
if(name == Video::Filter) return true;
|
||||
if(name == Video::Shader) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Video::Handle) return (uintptr_t)settings.handle;
|
||||
if(name == Video::Synchronize) return settings.synchronize;
|
||||
if(name == Video::Depth) return settings.depth;
|
||||
if(name == Video::Filter) return settings.filter;
|
||||
if(name == Video::Shader) return settings.shader;
|
||||
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::Synchronize && value.is<bool>()) {
|
||||
if(settings.synchronize != value.get<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
if(glXSwapInterval) glXSwapInterval(settings.synchronize);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(name == Video::Depth && value.is<unsigned>()) {
|
||||
unsigned depth = value.get<unsigned>();
|
||||
if(depth > DefaultDepth(display, screen)) return false;
|
||||
|
||||
switch(depth) {
|
||||
case 24: inputFormat = GL_RGBA8; break;
|
||||
case 30: inputFormat = GL_RGB10_A2; break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
settings.depth = depth;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Filter && value.is<unsigned>()) {
|
||||
settings.filter = value.get<unsigned>();
|
||||
if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Shader && value.is<string>()) {
|
||||
settings.shader = value.get<string>();
|
||||
OpenGL::shader(settings.shader);
|
||||
if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
|
||||
OpenGL::size(width, height);
|
||||
return OpenGL::lock(data, pitch);
|
||||
}
|
||||
|
||||
auto unlock() -> void {
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
OpenGL::clear();
|
||||
if(glx.doubleBuffer) glXSwapBuffers(display, glxwindow);
|
||||
}
|
||||
|
||||
auto refresh() -> void {
|
||||
//we must ensure that the child window is the same size as the parent window.
|
||||
//unfortunately, we cannot hook the parent window resize event notification,
|
||||
//as we did not create the parent window, nor have any knowledge of the toolkit used.
|
||||
//therefore, inelegant as it may be, we query each window size and resize as needed.
|
||||
XWindowAttributes parent, child;
|
||||
XGetWindowAttributes(display, settings.handle, &parent);
|
||||
XGetWindowAttributes(display, xwindow, &child);
|
||||
if(child.width != parent.width || child.height != parent.height) {
|
||||
XResizeWindow(display, xwindow, parent.width, parent.height);
|
||||
}
|
||||
|
||||
outputWidth = parent.width, outputHeight = parent.height;
|
||||
OpenGL::refresh();
|
||||
if(glx.doubleBuffer) glXSwapBuffers(display, glxwindow);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
display = XOpenDisplay(0);
|
||||
screen = DefaultScreen(display);
|
||||
|
||||
//require GLX 1.2+ API
|
||||
glXQueryVersion(display, &glx.versionMajor, &glx.versionMinor);
|
||||
if(glx.versionMajor < 1 || (glx.versionMajor == 1 && glx.versionMinor < 2)) return false;
|
||||
|
||||
XWindowAttributes windowAttributes;
|
||||
XGetWindowAttributes(display, settings.handle, &windowAttributes);
|
||||
|
||||
//let GLX determine the best Visual to use for GL output; provide a few hints
|
||||
//note: some video drivers will override double buffering attribute
|
||||
signed attributeList[] = {
|
||||
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
|
||||
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
||||
GLX_DOUBLEBUFFER, True,
|
||||
GLX_RED_SIZE, (signed)(settings.depth / 3),
|
||||
GLX_GREEN_SIZE, (signed)(settings.depth / 3) + (signed)(settings.depth % 3),
|
||||
GLX_BLUE_SIZE, (signed)(settings.depth / 3),
|
||||
None
|
||||
};
|
||||
|
||||
signed fbCount = 0;
|
||||
GLXFBConfig* fbConfig = glXChooseFBConfig(display, screen, attributeList, &fbCount);
|
||||
if(fbCount == 0) return false;
|
||||
|
||||
XVisualInfo* vi = glXGetVisualFromFBConfig(display, fbConfig[0]);
|
||||
|
||||
//Window settings.handle has already been realized, most likely with DefaultVisual.
|
||||
//GLX requires that the GL output window has the same Visual as the GLX context.
|
||||
//it is not possible to change the Visual of an already realized (created) window.
|
||||
//therefore a new child window, using the same GLX Visual, must be created and binded to settings.handle.
|
||||
colormap = XCreateColormap(display, RootWindow(display, vi->screen), vi->visual, AllocNone);
|
||||
XSetWindowAttributes attributes;
|
||||
attributes.colormap = colormap;
|
||||
attributes.border_pixel = 0;
|
||||
xwindow = XCreateWindow(display, /* parent = */ settings.handle,
|
||||
/* x = */ 0, /* y = */ 0, windowAttributes.width, windowAttributes.height,
|
||||
/* border_width = */ 0, vi->depth, InputOutput, vi->visual,
|
||||
CWColormap | CWBorderPixel, &attributes);
|
||||
XSetWindowBackground(display, xwindow, /* color = */ 0);
|
||||
XMapWindow(display, xwindow);
|
||||
XFlush(display);
|
||||
|
||||
//window must be realized (appear onscreen) before we make the context current
|
||||
while(XPending(display)) {
|
||||
XEvent event;
|
||||
XNextEvent(display, &event);
|
||||
}
|
||||
|
||||
glxcontext = glXCreateContext(display, vi, /* sharelist = */ 0, /* direct = */ GL_TRUE);
|
||||
glXMakeCurrent(display, glxwindow = xwindow, glxcontext);
|
||||
|
||||
//glXSwapInterval is used to toggle Vsync
|
||||
//note that the ordering is very important! MESA declares SGI, but the SGI function does nothing
|
||||
glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalEXT");
|
||||
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalMESA");
|
||||
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalSGI");
|
||||
|
||||
if(auto glXCreateContextAttribs = (auto (*)(Display*, GLXFBConfig, GLXContext, signed, const signed*) -> GLXContext)glGetProcAddress("glXCreateContextAttribsARB")) {
|
||||
signed attributes[] = {
|
||||
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
|
||||
GLX_CONTEXT_MINOR_VERSION_ARB, 2,
|
||||
None
|
||||
};
|
||||
|
||||
//glXCreateContextAttribs tends to throw BadRequest errors instead of simply failing gracefully
|
||||
auto originalHandler = XSetErrorHandler(VideoGLX_X11ErrorHandler);
|
||||
auto context = glXCreateContextAttribs(display, fbConfig[0], nullptr, true, attributes);
|
||||
XSync(display, False);
|
||||
XSetErrorHandler(originalHandler);
|
||||
|
||||
if(context) {
|
||||
glXMakeCurrent(display, 0, nullptr);
|
||||
glXDestroyContext(display, glxcontext);
|
||||
glXMakeCurrent(display, glxwindow, glxcontext = context);
|
||||
} else {
|
||||
//OpenGL 3.2+ not supported (most likely OpenGL 2.x)
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
//missing required glXCreateContextAtribs function
|
||||
return false;
|
||||
}
|
||||
|
||||
if(glXSwapInterval) glXSwapInterval(settings.synchronize);
|
||||
|
||||
//read attributes of frame buffer for later use, as requested attributes from above are not always granted
|
||||
signed value = 0;
|
||||
glXGetConfig(display, vi, GLX_DOUBLEBUFFER, &value);
|
||||
glx.doubleBuffer = value;
|
||||
glx.isDirect = glXIsDirect(display, glxcontext);
|
||||
|
||||
OpenGL::init();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
OpenGL::term();
|
||||
|
||||
if(glxcontext) {
|
||||
glXDestroyContext(display, glxcontext);
|
||||
glxcontext = nullptr;
|
||||
}
|
||||
|
||||
if(xwindow) {
|
||||
XUnmapWindow(display, xwindow);
|
||||
xwindow = 0;
|
||||
}
|
||||
|
||||
if(colormap) {
|
||||
XFreeColormap(display, colormap);
|
||||
colormap = 0;
|
||||
}
|
||||
|
||||
if(display) {
|
||||
XCloseDisplay(display);
|
||||
display = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
245
video/glx2.cpp
Normal file
245
video/glx2.cpp
Normal file
|
@ -0,0 +1,245 @@
|
|||
//Xorg/GLX OpenGL 2.0 driver
|
||||
|
||||
//note: this is a fallback driver for use when OpenGL 3.2 is not available.
|
||||
//see glx.cpp for comments on how this driver operates (they are very similar.)
|
||||
|
||||
struct VideoGLX2 : Video {
|
||||
~VideoGLX2() { term(); }
|
||||
|
||||
auto (*glXSwapInterval)(signed) -> signed = nullptr;
|
||||
Display* display = nullptr;
|
||||
signed screen = 0;
|
||||
Window xwindow = 0;
|
||||
Colormap colormap = 0;
|
||||
GLXContext glxcontext = nullptr;
|
||||
GLXWindow glxwindow = 0;
|
||||
|
||||
struct {
|
||||
Window handle = 0;
|
||||
bool synchronize = false;
|
||||
unsigned filter = 1; //linear
|
||||
|
||||
unsigned width = 256;
|
||||
unsigned height = 256;
|
||||
|
||||
bool isDoubleBuffered = false;
|
||||
bool isDirect = false;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Video::Handle) return true;
|
||||
if(name == Video::Synchronize) return true;
|
||||
if(name == Video::Filter) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Video::Handle) return (uintptr_t)settings.handle;
|
||||
if(name == Video::Synchronize) return settings.synchronize;
|
||||
if(name == Video::Filter) return settings.filter;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Video::Handle && value.is<uintptr_t>()) {
|
||||
settings.handle = value.get<uintptr_t>();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Synchronize && value.is<bool>()) {
|
||||
if(settings.synchronize != value.get<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
if(glXSwapInterval) glXSwapInterval(settings.synchronize);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(name == Video::Filter && value.is<unsigned>()) {
|
||||
settings.filter = value.get<unsigned>();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
|
||||
if(width != settings.width || height != settings.height) resize(width, height);
|
||||
pitch = glwidth * sizeof(uint32_t);
|
||||
return data = glbuffer;
|
||||
}
|
||||
|
||||
auto unlock() -> void {
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
memory::fill(glbuffer, glwidth * glheight * sizeof(uint32_t));
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glFlush();
|
||||
if(settings.isDoubleBuffered) glXSwapBuffers(display, glxwindow);
|
||||
}
|
||||
|
||||
auto refresh() -> void {
|
||||
XWindowAttributes parent, child;
|
||||
XGetWindowAttributes(display, settings.handle, &parent);
|
||||
XGetWindowAttributes(display, xwindow, &child);
|
||||
if(child.width != parent.width || child.height != parent.height) {
|
||||
XResizeWindow(display, xwindow, parent.width, parent.height);
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, settings.filter ? GL_LINEAR : GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, settings.filter ? GL_LINEAR : GL_NEAREST);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glOrtho(0, parent.width, 0, parent.height, -1.0, 1.0);
|
||||
glViewport(0, 0, parent.width, parent.height);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, glwidth);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, settings.width, settings.height,
|
||||
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, glbuffer);
|
||||
|
||||
double w = (double)settings.width / (double)glwidth;
|
||||
double h = (double)settings.height / (double)glheight;
|
||||
signed u = parent.width;
|
||||
signed v = parent.height;
|
||||
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0, 0); glVertex3i(0, v, 0);
|
||||
glTexCoord2f(w, 0); glVertex3i(u, v, 0);
|
||||
glTexCoord2f(0, h); glVertex3i(0, 0, 0);
|
||||
glTexCoord2f(w, h); glVertex3i(u, 0, 0);
|
||||
glEnd();
|
||||
glFlush();
|
||||
|
||||
if(settings.isDoubleBuffered) glXSwapBuffers(display, glxwindow);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
display = XOpenDisplay(0);
|
||||
screen = DefaultScreen(display);
|
||||
|
||||
signed versionMajor = 0, versionMinor = 0;
|
||||
glXQueryVersion(display, &versionMajor, &versionMinor);
|
||||
if(versionMajor < 1 || (versionMajor == 1 && versionMinor < 2)) return false;
|
||||
|
||||
XWindowAttributes windowAttributes;
|
||||
XGetWindowAttributes(display, settings.handle, &windowAttributes);
|
||||
|
||||
signed attributeList[] = {
|
||||
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
|
||||
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
||||
GLX_DOUBLEBUFFER, True,
|
||||
GLX_RED_SIZE, 8,
|
||||
GLX_GREEN_SIZE, 8,
|
||||
GLX_BLUE_SIZE, 8,
|
||||
None
|
||||
};
|
||||
|
||||
signed fbCount = 0;
|
||||
auto fbConfig = glXChooseFBConfig(display, screen, attributeList, &fbCount);
|
||||
if(fbCount == 0) return false;
|
||||
|
||||
auto vi = glXGetVisualFromFBConfig(display, fbConfig[0]);
|
||||
colormap = XCreateColormap(display, RootWindow(display, vi->screen), vi->visual, AllocNone);
|
||||
XSetWindowAttributes attributes;
|
||||
attributes.colormap = colormap;
|
||||
attributes.border_pixel = 0;
|
||||
xwindow = XCreateWindow(display, settings.handle, 0, 0, windowAttributes.width, windowAttributes.height,
|
||||
0, vi->depth, InputOutput, vi->visual, CWColormap | CWBorderPixel, &attributes);
|
||||
XSetWindowBackground(display, xwindow, 0);
|
||||
XMapWindow(display, xwindow);
|
||||
XFlush(display);
|
||||
|
||||
while(XPending(display)) {
|
||||
XEvent event;
|
||||
XNextEvent(display, &event);
|
||||
}
|
||||
|
||||
glxcontext = glXCreateContext(display, vi, 0, GL_TRUE);
|
||||
glXMakeCurrent(display, glxwindow = xwindow, glxcontext);
|
||||
|
||||
glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalEXT");
|
||||
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalMESA");
|
||||
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalSGI");
|
||||
|
||||
if(glXSwapInterval) glXSwapInterval(settings.synchronize);
|
||||
|
||||
signed value = 0;
|
||||
glXGetConfig(display, vi, GLX_DOUBLEBUFFER, &value);
|
||||
settings.isDoubleBuffered = value;
|
||||
settings.isDirect = glXIsDirect(display, glxcontext);
|
||||
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_POLYGON_SMOOTH);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
glEnable(GL_DITHER);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
resize(256, 256);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
if(gltexture) {
|
||||
glDeleteTextures(1, &gltexture);
|
||||
gltexture = 0;
|
||||
}
|
||||
|
||||
if(glbuffer) {
|
||||
delete[] glbuffer;
|
||||
glbuffer = 0;
|
||||
}
|
||||
|
||||
glwidth = 0;
|
||||
glheight = 0;
|
||||
|
||||
if(glxcontext) {
|
||||
glXDestroyContext(display, glxcontext);
|
||||
glxcontext = nullptr;
|
||||
}
|
||||
|
||||
if(xwindow) {
|
||||
XUnmapWindow(display, xwindow);
|
||||
xwindow = 0;
|
||||
}
|
||||
|
||||
if(colormap) {
|
||||
XFreeColormap(display, colormap);
|
||||
colormap = 0;
|
||||
}
|
||||
|
||||
if(display) {
|
||||
XCloseDisplay(display);
|
||||
display = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
GLuint gltexture = 0;
|
||||
uint32_t* glbuffer = nullptr;
|
||||
unsigned glwidth = 0;
|
||||
unsigned glheight = 0;
|
||||
|
||||
auto resize(unsigned width, unsigned height) -> void {
|
||||
settings.width = width;
|
||||
settings.height = height;
|
||||
|
||||
if(gltexture == 0) glGenTextures(1, &gltexture);
|
||||
glwidth = max(glwidth, width);
|
||||
glheight = max(glheight, height);
|
||||
if(glbuffer) delete[] glbuffer;
|
||||
glbuffer = new uint32_t[glwidth * glheight]();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gltexture);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, glwidth);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, glwidth, glheight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, glbuffer);
|
||||
}
|
||||
};
|
101
video/opengl/bind.hpp
Normal file
101
video/opengl/bind.hpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
#if defined(DISPLAY_WINDOWS) || defined(DISPLAY_XORG)
|
||||
PFNGLCREATEPROGRAMPROC glCreateProgram = nullptr;
|
||||
PFNGLDELETEPROGRAMPROC glDeleteProgram = nullptr;
|
||||
PFNGLUSEPROGRAMPROC glUseProgram = nullptr;
|
||||
PFNGLCREATESHADERPROC glCreateShader = nullptr;
|
||||
PFNGLDELETESHADERPROC glDeleteShader = nullptr;
|
||||
PFNGLSHADERSOURCEPROC glShaderSource = nullptr;
|
||||
PFNGLCOMPILESHADERPROC glCompileShader = nullptr;
|
||||
PFNGLGETSHADERIVPROC glGetShaderiv = nullptr;
|
||||
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr;
|
||||
PFNGLATTACHSHADERPROC glAttachShader = nullptr;
|
||||
PFNGLDETACHSHADERPROC glDetachShader = nullptr;
|
||||
PFNGLLINKPROGRAMPROC glLinkProgram = nullptr;
|
||||
PFNGLVALIDATEPROGRAMPROC glValidateProgram = nullptr;
|
||||
PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr;
|
||||
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = nullptr;
|
||||
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = nullptr;
|
||||
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = nullptr;
|
||||
PFNGLBINDVERTEXARRAYPROC glBindVertexArray = nullptr;
|
||||
PFNGLGENBUFFERSPROC glGenBuffers = nullptr;
|
||||
PFNGLDELETEBUFFERSPROC glDeleteBuffers = nullptr;
|
||||
PFNGLBINDBUFFERPROC glBindBuffer = nullptr;
|
||||
PFNGLBUFFERDATAPROC glBufferData = nullptr;
|
||||
PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation = nullptr;
|
||||
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr;
|
||||
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = nullptr;
|
||||
PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray = nullptr;
|
||||
PFNGLBINDFRAGDATALOCATIONPROC glBindFragDataLocation = nullptr;
|
||||
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr;
|
||||
PFNGLGETUNIFORMIVPROC glGetUniformiv = nullptr;
|
||||
PFNGLUNIFORM1IPROC glUniform1i = nullptr;
|
||||
PFNGLUNIFORM1FPROC glUniform1f = nullptr;
|
||||
PFNGLUNIFORM2FPROC glUniform2f = nullptr;
|
||||
PFNGLUNIFORM2FVPROC glUniform2fv = nullptr;
|
||||
PFNGLUNIFORM4FPROC glUniform4f = nullptr;
|
||||
PFNGLUNIFORM4FVPROC glUniform4fv = nullptr;
|
||||
PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv = nullptr;
|
||||
PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers = nullptr;
|
||||
PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers = nullptr;
|
||||
PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer = nullptr;
|
||||
PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D = nullptr;
|
||||
#endif
|
||||
#if defined(DISPLAY_WINDOWS)
|
||||
PFNGLACTIVETEXTUREPROC glActiveTexture = nullptr;
|
||||
#endif
|
||||
|
||||
static bool OpenGLBind() {
|
||||
#define bind(prototype, function) \
|
||||
function = (prototype)glGetProcAddress(#function); \
|
||||
if(function == nullptr) return false
|
||||
|
||||
#if defined(DISPLAY_WINDOWS) || defined(DISPLAY_XORG)
|
||||
bind(PFNGLCREATEPROGRAMPROC, glCreateProgram);
|
||||
bind(PFNGLDELETEPROGRAMPROC, glDeleteProgram);
|
||||
bind(PFNGLUSEPROGRAMPROC, glUseProgram);
|
||||
bind(PFNGLCREATESHADERPROC, glCreateShader);
|
||||
bind(PFNGLDELETESHADERPROC, glDeleteShader);
|
||||
bind(PFNGLSHADERSOURCEPROC, glShaderSource);
|
||||
bind(PFNGLCOMPILESHADERPROC, glCompileShader);
|
||||
bind(PFNGLGETSHADERIVPROC, glGetShaderiv);
|
||||
bind(PFNGLGETSHADERINFOLOGPROC, glGetShaderInfoLog);
|
||||
bind(PFNGLATTACHSHADERPROC, glAttachShader);
|
||||
bind(PFNGLDETACHSHADERPROC, glDetachShader);
|
||||
bind(PFNGLLINKPROGRAMPROC, glLinkProgram);
|
||||
bind(PFNGLVALIDATEPROGRAMPROC, glValidateProgram);
|
||||
bind(PFNGLGETPROGRAMIVPROC, glGetProgramiv);
|
||||
bind(PFNGLGETPROGRAMINFOLOGPROC, glGetProgramInfoLog);
|
||||
bind(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays);
|
||||
bind(PFNGLDELETEVERTEXARRAYSPROC, glDeleteVertexArrays);
|
||||
bind(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray);
|
||||
bind(PFNGLGENBUFFERSPROC, glGenBuffers);
|
||||
bind(PFNGLDELETEBUFFERSPROC, glDeleteBuffers);
|
||||
bind(PFNGLBINDBUFFERPROC, glBindBuffer);
|
||||
bind(PFNGLBUFFERDATAPROC, glBufferData);
|
||||
bind(PFNGLGETATTRIBLOCATIONPROC, glGetAttribLocation);
|
||||
bind(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer);
|
||||
bind(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray);
|
||||
bind(PFNGLDISABLEVERTEXATTRIBARRAYPROC, glDisableVertexAttribArray);
|
||||
bind(PFNGLBINDFRAGDATALOCATIONPROC, glBindFragDataLocation);
|
||||
bind(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation);
|
||||
bind(PFNGLGETUNIFORMIVPROC, glGetUniformiv);
|
||||
bind(PFNGLUNIFORM1IPROC, glUniform1i);
|
||||
bind(PFNGLUNIFORM1FPROC, glUniform1f);
|
||||
bind(PFNGLUNIFORM2FPROC, glUniform2f);
|
||||
bind(PFNGLUNIFORM2FVPROC, glUniform2fv);
|
||||
bind(PFNGLUNIFORM4FPROC, glUniform4f);
|
||||
bind(PFNGLUNIFORM4FVPROC, glUniform4fv);
|
||||
bind(PFNGLUNIFORMMATRIX4FVPROC, glUniformMatrix4fv);
|
||||
bind(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers);
|
||||
bind(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers);
|
||||
bind(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer);
|
||||
bind(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D);
|
||||
#endif
|
||||
#if defined(DISPLAY_WINDOWS)
|
||||
bind(PFNGLACTIVETEXTUREPROC, glActiveTexture);
|
||||
#endif
|
||||
|
||||
#undef bind
|
||||
|
||||
return true;
|
||||
}
|
212
video/opengl/main.hpp
Normal file
212
video/opengl/main.hpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
auto OpenGL::shader(const string& pathname) -> void {
|
||||
for(auto& program : programs) program.release();
|
||||
programs.reset();
|
||||
|
||||
settings.reset();
|
||||
|
||||
format = inputFormat;
|
||||
filter = GL_LINEAR;
|
||||
wrap = GL_CLAMP_TO_BORDER;
|
||||
absoluteWidth = 0, absoluteHeight = 0;
|
||||
relativeWidth = 0, relativeHeight = 0;
|
||||
|
||||
unsigned historySize = 0;
|
||||
if(pathname) {
|
||||
auto document = BML::unserialize(file::read({pathname, "manifest.bml"}));
|
||||
|
||||
for(auto node : document["settings"]) {
|
||||
settings.insert({node.name(), node.text()});
|
||||
}
|
||||
|
||||
for(auto node : document["input"]) {
|
||||
if(node.name() == "history") historySize = node.natural();
|
||||
if(node.name() == "format") format = glrFormat(node.text());
|
||||
if(node.name() == "filter") filter = glrFilter(node.text());
|
||||
if(node.name() == "wrap") wrap = glrWrap(node.text());
|
||||
}
|
||||
|
||||
for(auto node : document["output"]) {
|
||||
string text = node.text();
|
||||
if(node.name() == "width") {
|
||||
if(text.endsWith("%")) relativeWidth = real(text.rtrim("%", 1L)) / 100.0;
|
||||
else absoluteWidth = text.natural();
|
||||
}
|
||||
if(node.name() == "height") {
|
||||
if(text.endsWith("%")) relativeHeight = real(text.rtrim("%", 1L)) / 100.0;
|
||||
else absoluteHeight = text.natural();
|
||||
}
|
||||
}
|
||||
|
||||
for(auto node : document.find("program")) {
|
||||
unsigned n = programs.size();
|
||||
programs(n).bind(this, node, pathname);
|
||||
}
|
||||
}
|
||||
|
||||
//changing shaders may change input format, which requires the input texture to be recreated
|
||||
if(texture) { glDeleteTextures(1, &texture); texture = 0; }
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, getFormat(), getType(), buffer);
|
||||
allocateHistory(historySize);
|
||||
}
|
||||
|
||||
auto OpenGL::allocateHistory(unsigned size) -> void {
|
||||
for(auto& frame : history) glDeleteTextures(1, &frame.texture);
|
||||
history.reset();
|
||||
while(size--) {
|
||||
OpenGLTexture frame;
|
||||
frame.filter = filter;
|
||||
frame.wrap = wrap;
|
||||
glGenTextures(1, &frame.texture);
|
||||
glBindTexture(GL_TEXTURE_2D, frame.texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format, frame.width = width, frame.height = height, 0, getFormat(), getType(), buffer);
|
||||
history.append(frame);
|
||||
}
|
||||
}
|
||||
|
||||
auto OpenGL::lock(uint32_t*& data, unsigned& pitch) -> bool {
|
||||
pitch = width * sizeof(uint32_t);
|
||||
return data = buffer;
|
||||
}
|
||||
|
||||
auto OpenGL::clear() -> void {
|
||||
for(auto& p : programs) {
|
||||
glUseProgram(p.program);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, p.framebuffer);
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
glUseProgram(0);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
auto OpenGL::refresh() -> void {
|
||||
clear();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, getFormat(), getType(), buffer);
|
||||
|
||||
struct Source {
|
||||
GLuint texture;
|
||||
unsigned width, height;
|
||||
GLuint filter, wrap;
|
||||
};
|
||||
vector<Source> sources;
|
||||
sources.prepend({texture, width, height, filter, wrap});
|
||||
|
||||
for(auto& p : programs) {
|
||||
unsigned targetWidth = p.absoluteWidth ? p.absoluteWidth : outputWidth;
|
||||
unsigned targetHeight = p.absoluteHeight ? p.absoluteHeight : outputHeight;
|
||||
if(p.relativeWidth) targetWidth = sources[0].width * p.relativeWidth;
|
||||
if(p.relativeHeight) targetHeight = sources[0].height * p.relativeHeight;
|
||||
|
||||
p.size(targetWidth, targetHeight);
|
||||
glUseProgram(p.program);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, p.framebuffer);
|
||||
|
||||
glrUniform1i("phase", p.phase);
|
||||
glrUniform1i("historyLength", history.size());
|
||||
glrUniform1i("sourceLength", sources.size());
|
||||
glrUniform1i("pixmapLength", p.pixmaps.size());
|
||||
glrUniform4f("targetSize", targetWidth, targetHeight, 1.0 / targetWidth, 1.0 / targetHeight);
|
||||
glrUniform4f("outputSize", outputWidth, outputHeight, 1.0 / outputWidth, 1.0 / outputHeight);
|
||||
|
||||
unsigned aid = 0;
|
||||
for(auto& frame : history) {
|
||||
glrUniform1i({"history[", aid, "]"}, aid);
|
||||
glrUniform4f({"historySize[", aid, "]"}, frame.width, frame.height, 1.0 / frame.width, 1.0 / frame.height);
|
||||
glActiveTexture(GL_TEXTURE0 + (aid++));
|
||||
glBindTexture(GL_TEXTURE_2D, frame.texture);
|
||||
glrParameters(frame.filter, frame.wrap);
|
||||
}
|
||||
|
||||
unsigned bid = 0;
|
||||
for(auto& source : sources) {
|
||||
glrUniform1i({"source[", bid, "]"}, aid + bid);
|
||||
glrUniform4f({"sourceSize[", bid, "]"}, source.width, source.height, 1.0 / source.width, 1.0 / source.height);
|
||||
glActiveTexture(GL_TEXTURE0 + aid + (bid++));
|
||||
glBindTexture(GL_TEXTURE_2D, source.texture);
|
||||
glrParameters(source.filter, source.wrap);
|
||||
}
|
||||
|
||||
unsigned cid = 0;
|
||||
for(auto& pixmap : p.pixmaps) {
|
||||
glrUniform1i({"pixmap[", cid, "]"}, aid + bid + cid);
|
||||
glrUniform4f({"pixmapSize[", bid, "]"}, pixmap.width, pixmap.height, 1.0 / pixmap.width, 1.0 / pixmap.height);
|
||||
glActiveTexture(GL_TEXTURE0 + aid + bid + (cid++));
|
||||
glBindTexture(GL_TEXTURE_2D, pixmap.texture);
|
||||
glrParameters(pixmap.filter, pixmap.wrap);
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glrParameters(sources[0].filter, sources[0].wrap);
|
||||
p.render(sources[0].width, sources[0].height, targetWidth, targetHeight);
|
||||
glBindTexture(GL_TEXTURE_2D, p.texture);
|
||||
|
||||
p.phase = (p.phase + 1) % p.modulo;
|
||||
sources.prepend({p.texture, p.width, p.height, p.filter, p.wrap});
|
||||
}
|
||||
|
||||
unsigned targetWidth = absoluteWidth ? absoluteWidth : outputWidth;
|
||||
unsigned targetHeight = absoluteHeight ? absoluteHeight : outputHeight;
|
||||
if(relativeWidth) targetWidth = sources[0].width * relativeWidth;
|
||||
if(relativeHeight) targetHeight = sources[0].height * relativeHeight;
|
||||
|
||||
glUseProgram(program);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
||||
glrUniform1i("source[0]", 0);
|
||||
glrUniform4f("targetSize", targetWidth, targetHeight, 1.0 / targetWidth, 1.0 / targetHeight);
|
||||
glrUniform4f("outputSize", outputWidth, outputHeight, 1.0 / outputWidth, 1.0 / outputHeight);
|
||||
|
||||
glrParameters(sources[0].filter, sources[0].wrap);
|
||||
render(sources[0].width, sources[0].height, outputWidth, outputHeight);
|
||||
|
||||
if(history.size() > 0) {
|
||||
OpenGLTexture frame = history.takeLast();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, frame.texture);
|
||||
if(width == frame.width && height == frame.height) {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, getFormat(), getType(), buffer);
|
||||
} else {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format, frame.width = width, frame.height = height, 0, getFormat(), getType(), buffer);
|
||||
}
|
||||
|
||||
history.prepend(frame);
|
||||
}
|
||||
}
|
||||
|
||||
auto OpenGL::init() -> bool {
|
||||
if(!OpenGLBind()) return false;
|
||||
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_POLYGON_SMOOTH);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
glEnable(GL_DITHER);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
program = glCreateProgram();
|
||||
vertex = glrCreateShader(program, GL_VERTEX_SHADER, OpenGLOutputVertexShader);
|
||||
//geometry = glrCreateShader(program, GL_GEOMETRY_SHADER, OpenGLGeometryShader);
|
||||
fragment = glrCreateShader(program, GL_FRAGMENT_SHADER, OpenGLFragmentShader);
|
||||
OpenGLSurface::allocate();
|
||||
glrLinkProgram(program);
|
||||
|
||||
shader("");
|
||||
return initialized = true;
|
||||
}
|
||||
|
||||
auto OpenGL::term() -> void {
|
||||
if(initialized == false) return;
|
||||
shader(""); //release shader resources (eg frame[] history)
|
||||
OpenGLSurface::release();
|
||||
if(buffer) { delete[] buffer; buffer = nullptr; }
|
||||
initialized = false;
|
||||
}
|
93
video/opengl/opengl.hpp
Normal file
93
video/opengl/opengl.hpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
#if defined(DISPLAY_XORG)
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glx.h>
|
||||
#define glGetProcAddress(name) (*glXGetProcAddress)((const GLubyte*)(name))
|
||||
#elif defined(DISPLAY_QUARTZ)
|
||||
#include <OpenGL/gl3.h>
|
||||
#elif defined(DISPLAY_WINDOWS)
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
#define glGetProcAddress(name) wglGetProcAddress(name)
|
||||
#else
|
||||
#error "ruby::OpenGL: unsupported platform"
|
||||
#endif
|
||||
|
||||
#include "bind.hpp"
|
||||
#include "shaders.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
struct OpenGL;
|
||||
|
||||
struct OpenGLTexture {
|
||||
auto getFormat() const -> GLuint;
|
||||
auto getType() const -> GLuint;
|
||||
|
||||
GLuint texture = 0;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
GLuint format = GL_RGBA8;
|
||||
GLuint filter = GL_LINEAR;
|
||||
GLuint wrap = GL_CLAMP_TO_BORDER;
|
||||
};
|
||||
|
||||
struct OpenGLSurface : OpenGLTexture {
|
||||
auto allocate() -> void;
|
||||
auto size(unsigned width, unsigned height) -> void;
|
||||
auto release() -> void;
|
||||
auto render(unsigned sourceWidth, unsigned sourceHeight, unsigned targetWidth, unsigned targetHeight) -> void;
|
||||
|
||||
GLuint program = 0;
|
||||
GLuint framebuffer = 0;
|
||||
GLuint vao = 0;
|
||||
GLuint vbo[3] = {0, 0, 0};
|
||||
GLuint vertex = 0;
|
||||
GLuint geometry = 0;
|
||||
GLuint fragment = 0;
|
||||
uint32_t* buffer = nullptr;
|
||||
};
|
||||
|
||||
struct OpenGLProgram : OpenGLSurface {
|
||||
auto bind(OpenGL* instance, const Markup::Node& node, const string& pathname) -> void;
|
||||
auto parse(OpenGL* instance, string& source) -> void;
|
||||
auto release() -> void;
|
||||
|
||||
unsigned phase = 0; //frame counter
|
||||
unsigned modulo = 0; //frame counter modulus
|
||||
unsigned absoluteWidth = 0;
|
||||
unsigned absoluteHeight = 0;
|
||||
double relativeWidth = 0;
|
||||
double relativeHeight = 0;
|
||||
vector<OpenGLTexture> pixmaps;
|
||||
};
|
||||
|
||||
struct OpenGL : OpenGLProgram {
|
||||
auto shader(const string& pathname) -> void;
|
||||
auto allocateHistory(unsigned size) -> void;
|
||||
auto lock(uint32_t*& data, unsigned& pitch) -> bool;
|
||||
auto clear() -> void;
|
||||
auto refresh() -> void;
|
||||
auto init() -> bool;
|
||||
auto term() -> void;
|
||||
|
||||
vector<OpenGLProgram> programs;
|
||||
vector<OpenGLTexture> history;
|
||||
GLuint inputFormat = GL_RGBA8;
|
||||
unsigned outputWidth = 0;
|
||||
unsigned outputHeight = 0;
|
||||
struct Setting {
|
||||
string name;
|
||||
string value;
|
||||
bool operator< (const Setting& source) const { return name < source.name; }
|
||||
bool operator==(const Setting& source) const { return name == source.name; }
|
||||
Setting() = default;
|
||||
Setting(const string& name) : name(name) {}
|
||||
Setting(const string& name, const string& value) : name(name), value(value) {}
|
||||
};
|
||||
set<Setting> settings;
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
#include "texture.hpp"
|
||||
#include "surface.hpp"
|
||||
#include "program.hpp"
|
||||
#include "main.hpp"
|
108
video/opengl/program.hpp
Normal file
108
video/opengl/program.hpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
auto OpenGLProgram::bind(OpenGL* instance, const Markup::Node& node, const string& pathname) -> void {
|
||||
filter = glrFilter(node["filter"].text());
|
||||
wrap = glrWrap(node["wrap"].text());
|
||||
modulo = glrModulo(node["modulo"].integer());
|
||||
|
||||
string w = node["width"].text(), h = node["height"].text();
|
||||
if(w.endsWith("%")) relativeWidth = real(w.rtrim("%", 1L)) / 100.0;
|
||||
else absoluteWidth = w.natural();
|
||||
if(h.endsWith("%")) relativeHeight = real(h.rtrim("%", 1L)) / 100.0;
|
||||
else absoluteHeight = h.natural();
|
||||
|
||||
format = glrFormat(node["format"].text());
|
||||
|
||||
program = glCreateProgram();
|
||||
glGenFramebuffers(1, &framebuffer);
|
||||
|
||||
if(file::exists({pathname, node["vertex"].text()})) {
|
||||
string source = file::read({pathname, node["vertex"].text()});
|
||||
parse(instance, source);
|
||||
vertex = glrCreateShader(program, GL_VERTEX_SHADER, source);
|
||||
} else {
|
||||
vertex = glrCreateShader(program, GL_VERTEX_SHADER, OpenGLVertexShader);
|
||||
}
|
||||
|
||||
if(file::exists({pathname, node["geometry"].text()})) {
|
||||
string source = file::read({pathname, node["geometry"].text()});
|
||||
parse(instance, source);
|
||||
geometry = glrCreateShader(program, GL_GEOMETRY_SHADER, source);
|
||||
} else {
|
||||
//geometry shaders, when attached, must pass all vertex output through to the fragment shaders
|
||||
//geometry = glrCreateShader(program, GL_GEOMETRY_SHADER, OpenGLGeometryShader);
|
||||
}
|
||||
|
||||
if(file::exists({pathname, node["fragment"].text()})) {
|
||||
string source = file::read({pathname, node["fragment"].text()});
|
||||
parse(instance, source);
|
||||
fragment = glrCreateShader(program, GL_FRAGMENT_SHADER, source);
|
||||
} else {
|
||||
fragment = glrCreateShader(program, GL_FRAGMENT_SHADER, OpenGLFragmentShader);
|
||||
}
|
||||
|
||||
for(auto& leaf : node.find("pixmap")) {
|
||||
nall::image image({pathname, leaf.text()});
|
||||
image.transform();
|
||||
if(image.empty()) continue;
|
||||
|
||||
GLuint texture;
|
||||
glGenTextures(1, &texture);
|
||||
|
||||
unsigned n = pixmaps.size();
|
||||
pixmaps(n).texture = texture;
|
||||
pixmaps(n).width = image.width();
|
||||
pixmaps(n).height = image.height();
|
||||
pixmaps(n).format = format;
|
||||
pixmaps(n).filter = filter;
|
||||
pixmaps(n).wrap = wrap;
|
||||
if(leaf["format"]) pixmaps(n).format = glrFormat(leaf["format"].text());
|
||||
if(leaf["filter"]) pixmaps(n).filter = glrFilter(leaf["filter"].text());
|
||||
if(leaf["wrap"]) pixmaps(n).wrap = glrWrap(leaf["wrap"].text());
|
||||
|
||||
unsigned w = glrSize(image.width()), h = glrSize(image.height());
|
||||
uint32_t* buffer = new uint32_t[w * h]();
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, pixmaps(n).format, w, h, 0, pixmaps(n).getFormat(), pixmaps(n).getType(), buffer);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image.width(), image.height(), getFormat(), getType(), image.data());
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
OpenGLSurface::allocate();
|
||||
glrLinkProgram(program);
|
||||
}
|
||||
|
||||
//apply manifest settings to shader source #in tags
|
||||
auto OpenGLProgram::parse(OpenGL* instance, string& source) -> void {
|
||||
lstring lines = source.split("\n");
|
||||
for(auto& line : lines) {
|
||||
string s = line;
|
||||
if(auto position = s.find("//")) s.resize(position()); //strip comments
|
||||
s.strip(); //remove extraneous whitespace
|
||||
if(s.match("#in ?*")) {
|
||||
s.ltrim("#in ", 1L).strip();
|
||||
if(auto setting = instance->settings.find({s})) {
|
||||
line = {"#define ", setting().name, " ", setting().value};
|
||||
} else {
|
||||
line.reset(); //undefined variable (test in source with #ifdef)
|
||||
}
|
||||
}
|
||||
}
|
||||
source = lines.merge("\n");
|
||||
}
|
||||
|
||||
auto OpenGLProgram::release() -> void {
|
||||
OpenGLSurface::release();
|
||||
for(auto& pixmap : pixmaps) glDeleteTextures(1, &pixmap.texture);
|
||||
pixmaps.reset();
|
||||
|
||||
width = 0;
|
||||
height = 0;
|
||||
format = GL_RGBA8;
|
||||
filter = GL_LINEAR;
|
||||
wrap = GL_CLAMP_TO_BORDER;
|
||||
phase = 0;
|
||||
modulo = 0;
|
||||
absoluteWidth = 0;
|
||||
absoluteHeight = 0;
|
||||
relativeWidth = 0;
|
||||
relativeHeight = 0;
|
||||
}
|
91
video/opengl/shaders.hpp
Normal file
91
video/opengl/shaders.hpp
Normal file
|
@ -0,0 +1,91 @@
|
|||
static string OpenGLOutputVertexShader = R"(
|
||||
#version 150
|
||||
|
||||
uniform vec4 targetSize;
|
||||
uniform vec4 outputSize;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
out Vertex {
|
||||
vec2 texCoord;
|
||||
} vertexOut;
|
||||
|
||||
void main() {
|
||||
//center image within output window
|
||||
if(gl_VertexID == 0 || gl_VertexID == 2) {
|
||||
gl_Position.x = -(targetSize.x / outputSize.x);
|
||||
} else {
|
||||
gl_Position.x = +(targetSize.x / outputSize.x);
|
||||
}
|
||||
|
||||
//center and flip vertically (buffer[0, 0] = top-left; OpenGL[0, 0] = bottom-left)
|
||||
if(gl_VertexID == 0 || gl_VertexID == 1) {
|
||||
gl_Position.y = +(targetSize.y / outputSize.y);
|
||||
} else {
|
||||
gl_Position.y = -(targetSize.y / outputSize.y);
|
||||
}
|
||||
|
||||
//align image to even pixel boundary to prevent aliasing
|
||||
vec2 align = fract((outputSize.xy + targetSize.xy) / 2.0) * 2.0;
|
||||
gl_Position.xy -= align / outputSize.xy;
|
||||
gl_Position.zw = vec2(0.0, 1.0);
|
||||
|
||||
vertexOut.texCoord = texCoord;
|
||||
}
|
||||
)";
|
||||
|
||||
static string OpenGLVertexShader = R"(
|
||||
#version 150
|
||||
|
||||
in vec4 position;
|
||||
in vec2 texCoord;
|
||||
|
||||
out Vertex {
|
||||
vec2 texCoord;
|
||||
} vertexOut;
|
||||
|
||||
void main() {
|
||||
gl_Position = position;
|
||||
vertexOut.texCoord = texCoord;
|
||||
}
|
||||
)";
|
||||
|
||||
static string OpenGLGeometryShader = R"(
|
||||
#version 150
|
||||
|
||||
layout(triangles) in;
|
||||
layout(triangle_strip, max_vertices = 3) out;
|
||||
|
||||
in Vertex {
|
||||
vec2 texCoord;
|
||||
} vertexIn[];
|
||||
|
||||
out Vertex {
|
||||
vec2 texCoord;
|
||||
};
|
||||
|
||||
void main() {
|
||||
for(int i = 0; i < gl_in.length(); i++) {
|
||||
gl_Position = gl_in[i].gl_Position;
|
||||
texCoord = vertexIn[i].texCoord;
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
)";
|
||||
|
||||
static string OpenGLFragmentShader = R"(
|
||||
#version 150
|
||||
|
||||
uniform sampler2D source[];
|
||||
|
||||
in Vertex {
|
||||
vec2 texCoord;
|
||||
};
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = texture(source[0], texCoord);
|
||||
}
|
||||
)";
|
114
video/opengl/surface.hpp
Normal file
114
video/opengl/surface.hpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
auto OpenGLSurface::allocate() -> void {
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
glGenBuffers(3, &vbo[0]);
|
||||
}
|
||||
|
||||
auto OpenGLSurface::size(unsigned w, unsigned h) -> void {
|
||||
if(width == w && height == h) return;
|
||||
width = w, height = h;
|
||||
w = glrSize(w), h = glrSize(h);
|
||||
|
||||
if(texture) { glDeleteTextures(1, &texture); texture = 0; }
|
||||
if(buffer) { delete[] buffer; buffer = nullptr; }
|
||||
|
||||
buffer = new uint32_t[w * h]();
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, getFormat(), getType(), buffer);
|
||||
|
||||
if(framebuffer) {
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
|
||||
delete[] buffer;
|
||||
buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto OpenGLSurface::release() -> void {
|
||||
if(vbo[0]) { glDeleteBuffers(3, &vbo[0]); for(auto &o : vbo) o = 0; }
|
||||
if(vao) { glDeleteVertexArrays(1, &vao); vao = 0; }
|
||||
if(vertex) { glDetachShader(program, vertex); glDeleteShader(vertex); vertex = 0; }
|
||||
if(geometry) { glDetachShader(program, geometry); glDeleteShader(geometry); geometry = 0; }
|
||||
if(fragment) { glDetachShader(program, fragment); glDeleteShader(fragment); fragment = 0; }
|
||||
if(texture) { glDeleteTextures(1, &texture); texture = 0; }
|
||||
if(framebuffer) { glDeleteFramebuffers(1, &framebuffer); framebuffer = 0; }
|
||||
if(program) { glDeleteProgram(program); program = 0; }
|
||||
width = 0, height = 0;
|
||||
}
|
||||
|
||||
auto OpenGLSurface::render(unsigned sourceWidth, unsigned sourceHeight, unsigned targetWidth, unsigned targetHeight) -> void {
|
||||
glViewport(0, 0, targetWidth, targetHeight);
|
||||
|
||||
float w = (float)sourceWidth / (float)glrSize(sourceWidth);
|
||||
float h = (float)sourceHeight / (float)glrSize(sourceHeight);
|
||||
float u = (float)targetWidth, v = (float)targetHeight;
|
||||
GLint location;
|
||||
|
||||
GLfloat modelView[] = {
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1,
|
||||
};
|
||||
|
||||
GLfloat projection[] = {
|
||||
2.0f/u, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 2.0f/v, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, -1.0f, 0.0f,
|
||||
-1.0f, -1.0f, 0.0f, 1.0f,
|
||||
};
|
||||
|
||||
GLfloat modelViewProjection[4 * 4];
|
||||
Matrix::Multiply(modelViewProjection, modelView, 4, 4, projection, 4, 4);
|
||||
|
||||
GLfloat vertices[] = {
|
||||
0, 0, 0, 1,
|
||||
u, 0, 0, 1,
|
||||
0, v, 0, 1,
|
||||
u, v, 0, 1,
|
||||
};
|
||||
|
||||
GLfloat positions[4 * 4];
|
||||
for(unsigned n = 0; n < 16; n += 4) {
|
||||
Matrix::Multiply(&positions[n], &vertices[n], 1, 4, modelViewProjection, 4, 4);
|
||||
}
|
||||
|
||||
GLfloat texCoords[] = {
|
||||
0, 0,
|
||||
w, 0,
|
||||
0, h,
|
||||
w, h,
|
||||
};
|
||||
|
||||
glrUniformMatrix4fv("modelView", modelView);
|
||||
glrUniformMatrix4fv("projection", projection);
|
||||
glrUniformMatrix4fv("modelViewProjection", modelViewProjection);
|
||||
|
||||
glBindVertexArray(vao);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
|
||||
glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(GLfloat), vertices, GL_STATIC_DRAW);
|
||||
GLuint locationVertex = glGetAttribLocation(program, "vertex");
|
||||
glEnableVertexAttribArray(locationVertex);
|
||||
glVertexAttribPointer(locationVertex, 4, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
|
||||
glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(GLfloat), positions, GL_STATIC_DRAW);
|
||||
GLuint locationPosition = glGetAttribLocation(program, "position");
|
||||
glEnableVertexAttribArray(locationPosition);
|
||||
glVertexAttribPointer(locationPosition, 4, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
|
||||
glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), texCoords, GL_STATIC_DRAW);
|
||||
GLuint locationTexCoord = glGetAttribLocation(program, "texCoord");
|
||||
glEnableVertexAttribArray(locationTexCoord);
|
||||
glVertexAttribPointer(locationTexCoord, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
|
||||
glBindFragDataLocation(program, 0, "fragColor");
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
glDisableVertexAttribArray(locationVertex);
|
||||
glDisableVertexAttribArray(locationPosition);
|
||||
glDisableVertexAttribArray(locationTexCoord);
|
||||
}
|
12
video/opengl/texture.hpp
Normal file
12
video/opengl/texture.hpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
auto OpenGLTexture::getFormat() const -> GLuint {
|
||||
if(format == GL_R32I) return GL_RED_INTEGER;
|
||||
if(format == GL_R32UI) return GL_RED_INTEGER;
|
||||
return GL_BGRA;
|
||||
}
|
||||
|
||||
auto OpenGLTexture::getType() const -> GLuint {
|
||||
if(format == GL_R32I) return GL_UNSIGNED_INT;
|
||||
if(format == GL_R32UI) return GL_UNSIGNED_INT;
|
||||
if(format == GL_RGB10_A2) return GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||
return GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
}
|
106
video/opengl/utility.hpp
Normal file
106
video/opengl/utility.hpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
static auto glrSize(unsigned size) -> unsigned {
|
||||
return size;
|
||||
//return bit::round(size); //return nearest power of two
|
||||
}
|
||||
|
||||
static auto glrFormat(const string& format) -> GLuint {
|
||||
if(format == "r32i" ) return GL_R32I;
|
||||
if(format == "r32ui" ) return GL_R32UI;
|
||||
if(format == "rgba8" ) return GL_RGBA8;
|
||||
if(format == "rgb10a2") return GL_RGB10_A2;
|
||||
if(format == "rgba12" ) return GL_RGBA12;
|
||||
if(format == "rgba16" ) return GL_RGBA16;
|
||||
if(format == "rgba16f") return GL_RGBA16F;
|
||||
if(format == "rgba32f") return GL_RGBA32F;
|
||||
return GL_RGBA8;
|
||||
}
|
||||
|
||||
static auto glrFilter(const string& filter) -> GLuint {
|
||||
if(filter == "nearest") return GL_NEAREST;
|
||||
if(filter == "linear" ) return GL_LINEAR;
|
||||
return GL_LINEAR;
|
||||
}
|
||||
|
||||
static auto glrWrap(const string& wrap) -> GLuint {
|
||||
if(wrap == "border") return GL_CLAMP_TO_BORDER;
|
||||
if(wrap == "edge" ) return GL_CLAMP_TO_EDGE;
|
||||
if(wrap == "repeat") return GL_REPEAT;
|
||||
return GL_CLAMP_TO_BORDER;
|
||||
}
|
||||
|
||||
static auto glrModulo(unsigned modulo) -> unsigned {
|
||||
if(modulo) return modulo;
|
||||
return 300; //divisible by 2, 3, 4, 5, 6, 10, 12, 15, 20, 25, 30, 50, 60, 100, 150
|
||||
}
|
||||
|
||||
static auto glrProgram() -> GLuint {
|
||||
GLuint program = 0;
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&program);
|
||||
return program;
|
||||
}
|
||||
|
||||
static auto glrUniform1i(const string& name, GLint value) -> void {
|
||||
GLint location = glGetUniformLocation(glrProgram(), name);
|
||||
glUniform1i(location, value);
|
||||
}
|
||||
|
||||
static auto glrUniform4f(const string& name, GLfloat value0, GLfloat value1, GLfloat value2, GLfloat value3) -> void {
|
||||
GLint location = glGetUniformLocation(glrProgram(), name);
|
||||
glUniform4f(location, value0, value1, value2, value3);
|
||||
}
|
||||
|
||||
static auto glrUniformMatrix4fv(const string& name, GLfloat* values) -> void {
|
||||
GLint location = glGetUniformLocation(glrProgram(), name);
|
||||
glUniformMatrix4fv(location, 1, GL_FALSE, values);
|
||||
}
|
||||
|
||||
static auto glrParameters(GLuint filter, GLuint wrap) -> void {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap);
|
||||
}
|
||||
|
||||
static auto glrCreateShader(GLuint program, GLuint type, const char* source) -> GLuint {
|
||||
GLuint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &source, 0);
|
||||
glCompileShader(shader);
|
||||
GLint result = GL_FALSE;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
|
||||
if(result == GL_FALSE) {
|
||||
GLint length = 0;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
|
||||
char text[length + 1];
|
||||
glGetShaderInfoLog(shader, length, &length, text);
|
||||
text[length] = 0;
|
||||
print("[ruby::OpenGL: shader compiler error]\n", (const char*)text, "\n\n");
|
||||
return 0;
|
||||
}
|
||||
glAttachShader(program, shader);
|
||||
return shader;
|
||||
}
|
||||
|
||||
static auto glrLinkProgram(GLuint program) -> void {
|
||||
glLinkProgram(program);
|
||||
GLint result = GL_FALSE;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &result);
|
||||
if(result == GL_FALSE) {
|
||||
GLint length = 0;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
|
||||
char text[length + 1];
|
||||
glGetProgramInfoLog(program, length, &length, text);
|
||||
text[length] = 0;
|
||||
print("[ruby::OpenGL: shader linker error]\n", (const char*)text, "\n\n");
|
||||
}
|
||||
glValidateProgram(program);
|
||||
result = GL_FALSE;
|
||||
glGetProgramiv(program, GL_VALIDATE_STATUS, &result);
|
||||
if(result == GL_FALSE) {
|
||||
GLint length = 0;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
|
||||
char text[length + 1];
|
||||
glGetProgramInfoLog(program, length, &length, text);
|
||||
text[length] = 0;
|
||||
print("[ruby::OpenGL: shader validation error]\n", (const char*)text, "\n\n");
|
||||
}
|
||||
}
|
135
video/sdl.cpp
Normal file
135
video/sdl.cpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <X11/extensions/Xv.h>
|
||||
#include <X11/extensions/Xvlib.h>
|
||||
#include <X11/extensions/XShm.h>
|
||||
#include <SDL/SDL.h>
|
||||
|
||||
struct VideoSDL : Video {
|
||||
~VideoSDL() { term(); }
|
||||
|
||||
Display* display = nullptr;
|
||||
SDL_Surface* screen = nullptr;
|
||||
SDL_Surface* buffer = nullptr;
|
||||
unsigned iwidth = 0;
|
||||
unsigned iheight = 0;
|
||||
|
||||
struct {
|
||||
uintptr_t handle = 0;
|
||||
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Video::Handle) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Video::Handle) return settings.handle;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Video::Handle && value.is<uintptr_t>()) {
|
||||
settings.handle = value.get<uintptr_t>();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto resize(unsigned width, unsigned height) -> void {
|
||||
if(iwidth >= width && iheight >= height) return;
|
||||
|
||||
iwidth = max(width, iwidth);
|
||||
iheight = max(height, iheight);
|
||||
|
||||
if(buffer) SDL_FreeSurface(buffer);
|
||||
buffer = SDL_CreateRGBSurface(
|
||||
SDL_SWSURFACE, iwidth, iheight, 32,
|
||||
0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000
|
||||
);
|
||||
}
|
||||
|
||||
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
|
||||
if(width != settings.width || height != settings.height) {
|
||||
resize(settings.width = width, settings.height = height);
|
||||
}
|
||||
|
||||
if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer);
|
||||
pitch = buffer->pitch;
|
||||
return data = (uint32_t*)buffer->pixels;
|
||||
}
|
||||
|
||||
auto unlock() -> void {
|
||||
if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer);
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer);
|
||||
for(unsigned y = 0; y < iheight; y++) {
|
||||
uint32_t* data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2);
|
||||
for(unsigned x = 0; x < iwidth; x++) *data++ = 0xff000000;
|
||||
}
|
||||
if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer);
|
||||
refresh();
|
||||
}
|
||||
|
||||
auto refresh() -> void {
|
||||
//ruby input is X8R8G8B8, top 8-bits are ignored.
|
||||
//as SDL forces us to use a 32-bit buffer, we must set alpha to 255 (full opacity)
|
||||
//to prevent blending against the window beneath when X window visual is 32-bits.
|
||||
if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer);
|
||||
for(unsigned y = 0; y < settings.height; y++) {
|
||||
uint32_t* data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2);
|
||||
for(unsigned x = 0; x < settings.width; x++) *data++ |= 0xff000000;
|
||||
}
|
||||
if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer);
|
||||
|
||||
XWindowAttributes attributes;
|
||||
XGetWindowAttributes(display, settings.handle, &attributes);
|
||||
|
||||
SDL_Rect src, dest;
|
||||
|
||||
src.x = 0;
|
||||
src.y = 0;
|
||||
src.w = settings.width;
|
||||
src.h = settings.height;
|
||||
|
||||
dest.x = 0;
|
||||
dest.y = 0;
|
||||
dest.w = attributes.width;
|
||||
dest.h = attributes.height;
|
||||
|
||||
SDL_SoftStretch(buffer, &src, screen, &dest);
|
||||
SDL_UpdateRect(screen, dest.x, dest.y, dest.w, dest.h);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
display = XOpenDisplay(0);
|
||||
|
||||
//todo: this causes a segfault inside SDL_SetVideoMode on FreeBSD (works under Linux)
|
||||
char env[512];
|
||||
sprintf(env, "SDL_WINDOWID=%ld", (long)settings.handle);
|
||||
putenv(env);
|
||||
|
||||
SDL_InitSubSystem(SDL_INIT_VIDEO);
|
||||
screen = SDL_SetVideoMode(2560, 1600, 32, SDL_HWSURFACE);
|
||||
XUndefineCursor(display, settings.handle);
|
||||
|
||||
buffer = 0;
|
||||
iwidth = 0;
|
||||
iheight = 0;
|
||||
resize(settings.width = 256, settings.height = 256);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
XCloseDisplay(display);
|
||||
SDL_FreeSurface(buffer);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
}
|
||||
};
|
142
video/wgl.cpp
Normal file
142
video/wgl.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
#include "opengl/opengl.hpp"
|
||||
|
||||
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
|
||||
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
|
||||
|
||||
struct VideoWGL : Video, OpenGL {
|
||||
~VideoWGL() { term(); }
|
||||
|
||||
auto (APIENTRY* wglCreateContextAttribs)(HDC, HGLRC, const int*) -> HGLRC = nullptr;
|
||||
auto (APIENTRY* wglSwapInterval)(int) -> BOOL = nullptr;
|
||||
|
||||
HDC display = nullptr;
|
||||
HGLRC wglcontext = nullptr;
|
||||
HWND window = nullptr;
|
||||
HINSTANCE glwindow = nullptr;
|
||||
|
||||
struct {
|
||||
HWND handle = nullptr;
|
||||
bool synchronize = false;
|
||||
uint filter = Video::FilterNearest;
|
||||
string shader;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Video::Handle) return true;
|
||||
if(name == Video::Synchronize) return true;
|
||||
if(name == Video::Filter) return true;
|
||||
if(name == Video::Shader) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Video::Handle) return (uintptr)settings.handle;
|
||||
if(name == Video::Synchronize) return settings.synchronize;
|
||||
if(name == Video::Filter) return settings.filter;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto set(const string& name, const any& value) -> bool {
|
||||
if(name == Video::Handle && value.is<uintptr>()) {
|
||||
settings.handle = (HWND)value.get<uintptr>();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Synchronize && value.is<bool>()) {
|
||||
if(settings.synchronize != value.get<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
if(wglcontext) {
|
||||
init();
|
||||
OpenGL::shader(settings.shader);
|
||||
if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(name == Video::Filter && value.is<uint>()) {
|
||||
settings.filter = value.get<uint>();
|
||||
if(!settings.shader) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Video::Shader && value.is<string>()) {
|
||||
settings.shader = value.get<string>();
|
||||
OpenGL::shader(settings.shader);
|
||||
if(!settings.shader) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
|
||||
OpenGL::size(width, height);
|
||||
return OpenGL::lock(data, pitch);
|
||||
}
|
||||
|
||||
auto unlock() -> void {
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
OpenGL::clear();
|
||||
SwapBuffers(display);
|
||||
}
|
||||
|
||||
auto refresh() -> void {
|
||||
RECT rc;
|
||||
GetClientRect(settings.handle, &rc);
|
||||
outputWidth = rc.right - rc.left, outputHeight = rc.bottom - rc.top;
|
||||
OpenGL::refresh();
|
||||
SwapBuffers(display);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
GLuint pixel_format;
|
||||
PIXELFORMATDESCRIPTOR pfd;
|
||||
memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
|
||||
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
|
||||
pfd.nVersion = 1;
|
||||
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
||||
pfd.iPixelType = PFD_TYPE_RGBA;
|
||||
|
||||
display = GetDC(settings.handle);
|
||||
pixel_format = ChoosePixelFormat(display, &pfd);
|
||||
SetPixelFormat(display, pixel_format, &pfd);
|
||||
|
||||
wglcontext = wglCreateContext(display);
|
||||
wglMakeCurrent(display, wglcontext);
|
||||
|
||||
wglCreateContextAttribs = (HGLRC (APIENTRY*)(HDC, HGLRC, const int*))glGetProcAddress("wglCreateContextAttribsARB");
|
||||
wglSwapInterval = (BOOL (APIENTRY*)(int))glGetProcAddress("wglSwapIntervalEXT");
|
||||
|
||||
if(wglCreateContextAttribs) {
|
||||
int attributes[] = {
|
||||
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
|
||||
WGL_CONTEXT_MINOR_VERSION_ARB, 2,
|
||||
0
|
||||
};
|
||||
HGLRC context = wglCreateContextAttribs(display, 0, attributes);
|
||||
if(context) {
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
wglDeleteContext(wglcontext);
|
||||
wglMakeCurrent(display, wglcontext = context);
|
||||
}
|
||||
}
|
||||
|
||||
if(wglSwapInterval) {
|
||||
wglSwapInterval(settings.synchronize);
|
||||
}
|
||||
|
||||
OpenGL::init();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
OpenGL::term();
|
||||
|
||||
if(wglcontext) {
|
||||
wglDeleteContext(wglcontext);
|
||||
wglcontext = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
205
video/xshm.cpp
Normal file
205
video/xshm.cpp
Normal file
|
@ -0,0 +1,205 @@
|
|||
//XShm driver for Xorg
|
||||
|
||||
//Note that on composited displays, the alpha bits will allow translucency underneath the active window
|
||||
//As this is not a feature of ruby, this driver must always set the alpha bits on clear() and refresh()
|
||||
|
||||
//Linear interpolation is only applied horizontally for performance reasons, although Nearest is still much faster
|
||||
|
||||
#include <sys/shm.h>
|
||||
#include <X11/extensions/XShm.h>
|
||||
|
||||
struct VideoXShm : Video {
|
||||
~VideoXShm() { term(); }
|
||||
|
||||
struct Device {
|
||||
Display* display = nullptr;
|
||||
signed screen = 0;
|
||||
signed depth = 0;
|
||||
Visual* visual = nullptr;
|
||||
Window window = 0;
|
||||
|
||||
XShmSegmentInfo shmInfo;
|
||||
XImage* image = nullptr;
|
||||
uint32_t* buffer = nullptr;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
} device;
|
||||
|
||||
struct Settings {
|
||||
uintptr_t handle = 0;
|
||||
unsigned filter = Video::FilterLinear;
|
||||
|
||||
uint32_t* buffer = nullptr;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Video::Handle) return true;
|
||||
if(name == Video::Filter) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
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<unsigned>()) {
|
||||
settings.filter = value.get<unsigned>();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
|
||||
if(settings.buffer == nullptr || 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
|
||||
}
|
||||
|
||||
data = settings.buffer;
|
||||
pitch = settings.width * sizeof(uint32_t);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto unlock() -> void {
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
if(settings.buffer == nullptr) return;
|
||||
uint32_t* dp = settings.buffer;
|
||||
unsigned length = settings.width * settings.height;
|
||||
while(length--) *dp++ = 255u << 24;
|
||||
refresh();
|
||||
}
|
||||
|
||||
auto refresh() -> void {
|
||||
if(settings.buffer == nullptr) return;
|
||||
size();
|
||||
|
||||
float xratio = (float)settings.width / (float)device.width;
|
||||
float yratio = (float)settings.height / (float)device.height;
|
||||
|
||||
#pragma omp parallel for
|
||||
for(unsigned y = 0; y < device.height; y++) {
|
||||
float ystep = y * yratio;
|
||||
float xstep = 0;
|
||||
|
||||
uint32_t* sp = settings.buffer + (unsigned)ystep * settings.width;
|
||||
uint32_t* dp = device.buffer + y * device.width;
|
||||
|
||||
if(settings.filter == Video::FilterNearest) {
|
||||
for(unsigned x = 0; x < device.width; x++) {
|
||||
*dp++ = 255u << 24 | sp[(unsigned)xstep];
|
||||
xstep += xratio;
|
||||
}
|
||||
} else { //settings.filter == Video::FilterLinear
|
||||
for(unsigned x = 0; x < device.width; x++) {
|
||||
*dp++ = 255u << 24 | interpolate(xstep - (unsigned)xstep, sp[(unsigned)xstep], sp[(unsigned)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);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
device.display = XOpenDisplay(0);
|
||||
device.screen = DefaultScreen(device.display);
|
||||
|
||||
XWindowAttributes getAttributes;
|
||||
XGetWindowAttributes(device.display, (Window)settings.handle, &getAttributes);
|
||||
device.depth = getAttributes.depth;
|
||||
device.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) {
|
||||
free();
|
||||
return false;
|
||||
}
|
||||
|
||||
XSetWindowAttributes setAttributes = {0};
|
||||
setAttributes.border_pixel = 0;
|
||||
device.window = XCreateWindow(device.display, (Window)settings.handle,
|
||||
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);
|
||||
|
||||
while(XPending(device.display)) {
|
||||
XEvent event;
|
||||
XNextEvent(device.display, &event);
|
||||
}
|
||||
|
||||
if(size() == false) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
free();
|
||||
if(device.display) {
|
||||
XCloseDisplay(device.display);
|
||||
device.display = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
auto size() -> bool {
|
||||
XWindowAttributes windowAttributes;
|
||||
XGetWindowAttributes(device.display, settings.handle, &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);
|
||||
free();
|
||||
|
||||
device.shmInfo.shmid = shmget(IPC_PRIVATE, device.width * device.height * sizeof(uint32_t), IPC_CREAT | 0777);
|
||||
if(device.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
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto free() -> void {
|
||||
if(device.buffer == nullptr) return;
|
||||
device.buffer = nullptr;
|
||||
XShmDetach(device.display, &device.shmInfo);
|
||||
XDestroyImage(device.image);
|
||||
shmdt(device.shmInfo.shmaddr);
|
||||
shmctl(device.shmInfo.shmid, IPC_RMID, 0);
|
||||
}
|
||||
|
||||
alwaysinline auto interpolate(float mu, uint32_t a, uint32_t b) -> uint32_t {
|
||||
uint8_t ar = a >> 16, ag = a >> 8, ab = a >> 0;
|
||||
uint8_t br = b >> 16, bg = b >> 8, bb = b >> 0;
|
||||
uint8_t cr = ar * (1.0 - mu) + br * mu;
|
||||
uint8_t cg = ag * (1.0 - mu) + bg * mu;
|
||||
uint8_t cb = ab * (1.0 - mu) + bb * mu;
|
||||
return cr << 16 | cg << 8 | cb << 0;
|
||||
}
|
||||
};
|
489
video/xv.cpp
Normal file
489
video/xv.cpp
Normal file
|
@ -0,0 +1,489 @@
|
|||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <X11/extensions/XShm.h>
|
||||
#include <X11/extensions/Xv.h>
|
||||
#include <X11/extensions/Xvlib.h>
|
||||
|
||||
extern "C" auto XvShmCreateImage(Display*, XvPortID, signed, char*, signed, signed, XShmSegmentInfo*) -> XvImage*;
|
||||
|
||||
struct VideoXv : Video {
|
||||
~VideoXv() { term(); }
|
||||
|
||||
uint32_t* buffer = nullptr;
|
||||
uint8_t* ytable = nullptr;
|
||||
uint8_t* utable = nullptr;
|
||||
uint8_t* vtable = nullptr;
|
||||
|
||||
enum XvFormat : unsigned {
|
||||
XvFormatRGB32,
|
||||
XvFormatRGB24,
|
||||
XvFormatRGB16,
|
||||
XvFormatRGB15,
|
||||
XvFormatYUY2,
|
||||
XvFormatUYVY,
|
||||
XvFormatUnknown,
|
||||
};
|
||||
|
||||
struct {
|
||||
Display* display = nullptr;
|
||||
GC gc = 0;
|
||||
Window window = 0;
|
||||
Colormap colormap = 0;
|
||||
XShmSegmentInfo shminfo;
|
||||
|
||||
signed port = -1;
|
||||
signed depth = 0;
|
||||
signed visualid = 0;
|
||||
|
||||
XvImage* image = nullptr;
|
||||
XvFormat format = XvFormatUnknown;
|
||||
uint32_t fourcc = 0;
|
||||
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
} device;
|
||||
|
||||
struct {
|
||||
Window handle = 0;
|
||||
bool synchronize = false;
|
||||
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Video::Handle) return true;
|
||||
if(name == Video::Synchronize) {
|
||||
Display* display = XOpenDisplay(nullptr);
|
||||
bool result = XInternAtom(display, "XV_SYNC_TO_VBLANK", true) != None;
|
||||
XCloseDisplay(display);
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get(const string& name) -> any {
|
||||
if(name == Video::Handle) return settings.handle;
|
||||
if(name == Video::Synchronize) return settings.synchronize;
|
||||
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::Synchronize && value.is<bool>()) {
|
||||
bool result = false;
|
||||
Display* display = XOpenDisplay(nullptr);
|
||||
Atom atom = XInternAtom(display, "XV_SYNC_TO_VBLANK", true);
|
||||
if(atom != None && device.port >= 0) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
XvSetPortAttribute(display, device.port, atom, settings.synchronize);
|
||||
result = true;
|
||||
}
|
||||
XCloseDisplay(display);
|
||||
return result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto resize(unsigned width, unsigned height) -> void {
|
||||
if(device.width >= width && device.height >= height) return;
|
||||
device.width = max(width, device.width);
|
||||
device.height = max(height, device.height);
|
||||
|
||||
XShmDetach(device.display, &device.shminfo);
|
||||
shmdt(device.shminfo.shmaddr);
|
||||
shmctl(device.shminfo.shmid, IPC_RMID, nullptr);
|
||||
XFree(device.image);
|
||||
delete[] buffer;
|
||||
|
||||
device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, device.width, device.height, &device.shminfo);
|
||||
|
||||
device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777);
|
||||
device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0);
|
||||
device.shminfo.readOnly = false;
|
||||
XShmAttach(device.display, &device.shminfo);
|
||||
|
||||
buffer = new uint32_t[device.width * device.height];
|
||||
}
|
||||
|
||||
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
|
||||
if(width != settings.width || height != settings.height) {
|
||||
resize(settings.width = width, settings.height = height);
|
||||
}
|
||||
|
||||
pitch = device.width * 4;
|
||||
return data = buffer;
|
||||
}
|
||||
|
||||
auto unlock() -> void {
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
memory::fill(buffer, device.width * device.height * sizeof(uint32_t));
|
||||
//clear twice in case video is double buffered ...
|
||||
refresh();
|
||||
refresh();
|
||||
}
|
||||
|
||||
auto refresh() -> void {
|
||||
unsigned width = settings.width;
|
||||
unsigned height = settings.height;
|
||||
|
||||
XWindowAttributes target;
|
||||
XGetWindowAttributes(device.display, device.window, &target);
|
||||
|
||||
//we must ensure that the child window is the same size as the parent window.
|
||||
//unfortunately, we cannot hook the parent window resize event notification,
|
||||
//as we did not create the parent window, nor have any knowledge of the toolkit used.
|
||||
//therefore, query each window size and resize as needed.
|
||||
XWindowAttributes parent;
|
||||
XGetWindowAttributes(device.display, settings.handle, &parent);
|
||||
if(target.width != parent.width || target.height != parent.height) {
|
||||
XResizeWindow(device.display, device.window, parent.width, parent.height);
|
||||
}
|
||||
|
||||
//update target width and height attributes
|
||||
XGetWindowAttributes(device.display, device.window, &target);
|
||||
|
||||
switch(device.format) {
|
||||
case XvFormatRGB32: renderRGB32(width, height); break;
|
||||
case XvFormatRGB24: renderRGB24(width, height); break;
|
||||
case XvFormatRGB16: renderRGB16(width, height); break;
|
||||
case XvFormatRGB15: renderRGB15(width, height); break;
|
||||
case XvFormatYUY2: renderYUY2 (width, height); break;
|
||||
case XvFormatUYVY: renderUYVY (width, height); break;
|
||||
}
|
||||
|
||||
XvShmPutImage(device.display, device.port, device.window, device.gc, device.image,
|
||||
0, 0, width, height,
|
||||
0, 0, target.width, target.height,
|
||||
true);
|
||||
}
|
||||
|
||||
auto init() -> bool {
|
||||
device.display = XOpenDisplay(nullptr);
|
||||
|
||||
if(!XShmQueryExtension(device.display)) {
|
||||
fprintf(stderr, "VideoXv: XShm extension not found.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
//find an appropriate Xv port
|
||||
device.port = -1;
|
||||
XvAdaptorInfo* adaptor_info;
|
||||
unsigned adaptor_count;
|
||||
XvQueryAdaptors(device.display, DefaultRootWindow(device.display), &adaptor_count, &adaptor_info);
|
||||
for(unsigned i = 0; i < adaptor_count; i++) {
|
||||
//find adaptor that supports both input (memory->drawable) and image (drawable->screen) masks
|
||||
if(adaptor_info[i].num_formats < 1) continue;
|
||||
if(!(adaptor_info[i].type & XvInputMask)) continue;
|
||||
if(!(adaptor_info[i].type & XvImageMask)) continue;
|
||||
|
||||
device.port = adaptor_info[i].base_id;
|
||||
device.depth = adaptor_info[i].formats->depth;
|
||||
device.visualid = adaptor_info[i].formats->visual_id;
|
||||
break;
|
||||
}
|
||||
XvFreeAdaptorInfo(adaptor_info);
|
||||
if(device.port < 0) {
|
||||
fprintf(stderr, "VideoXv: failed to find valid XvPort.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
//create child window to attach to parent window.
|
||||
//this is so that even if parent window visual depth doesn't match Xv visual
|
||||
//(common with composited windows), Xv can still render to child window.
|
||||
XWindowAttributes window_attributes;
|
||||
XGetWindowAttributes(device.display, settings.handle, &window_attributes);
|
||||
|
||||
XVisualInfo visualtemplate;
|
||||
visualtemplate.visualid = device.visualid;
|
||||
visualtemplate.screen = DefaultScreen(device.display);
|
||||
visualtemplate.depth = device.depth;
|
||||
visualtemplate.visual = 0;
|
||||
signed visualmatches = 0;
|
||||
XVisualInfo* visualinfo = XGetVisualInfo(device.display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualtemplate, &visualmatches);
|
||||
if(visualmatches < 1 || !visualinfo->visual) {
|
||||
if(visualinfo) XFree(visualinfo);
|
||||
fprintf(stderr, "VideoXv: unable to find Xv-compatible visual.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
device.colormap = XCreateColormap(device.display, settings.handle, visualinfo->visual, AllocNone);
|
||||
XSetWindowAttributes attributes;
|
||||
attributes.colormap = device.colormap;
|
||||
attributes.border_pixel = 0;
|
||||
attributes.event_mask = StructureNotifyMask;
|
||||
device.window = XCreateWindow(device.display, /* parent = */ settings.handle,
|
||||
/* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height,
|
||||
/* border_width = */ 0, device.depth, InputOutput, visualinfo->visual,
|
||||
CWColormap | CWBorderPixel | CWEventMask, &attributes);
|
||||
XFree(visualinfo);
|
||||
XSetWindowBackground(device.display, device.window, /* color = */ 0);
|
||||
XMapWindow(device.display, device.window);
|
||||
|
||||
device.gc = XCreateGC(device.display, device.window, 0, 0);
|
||||
|
||||
//set colorkey to auto paint, so that Xv video output is always visible
|
||||
Atom atom = XInternAtom(device.display, "XV_AUTOPAINT_COLORKEY", true);
|
||||
if(atom != None) XvSetPortAttribute(device.display, device.port, atom, 1);
|
||||
|
||||
//find optimal rendering format
|
||||
device.format = XvFormatUnknown;
|
||||
signed format_count;
|
||||
XvImageFormatValues* format = XvListImageFormats(device.display, device.port, &format_count);
|
||||
|
||||
if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) {
|
||||
if(format[i].type == XvRGB && format[i].bits_per_pixel == 32) {
|
||||
device.format = XvFormatRGB32;
|
||||
device.fourcc = format[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) {
|
||||
if(format[i].type == XvRGB && format[i].bits_per_pixel == 24) {
|
||||
device.format = XvFormatRGB24;
|
||||
device.fourcc = format[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) {
|
||||
if(format[i].type == XvRGB && format[i].bits_per_pixel <= 16 && format[i].red_mask == 0xf800) {
|
||||
device.format = XvFormatRGB16;
|
||||
device.fourcc = format[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) {
|
||||
if(format[i].type == XvRGB && format[i].bits_per_pixel <= 16 && format[i].red_mask == 0x7c00) {
|
||||
device.format = XvFormatRGB15;
|
||||
device.fourcc = format[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) {
|
||||
if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) {
|
||||
if(format[i].component_order[0] == 'Y' && format[i].component_order[1] == 'U'
|
||||
&& format[i].component_order[2] == 'Y' && format[i].component_order[3] == 'V'
|
||||
) {
|
||||
device.format = XvFormatYUY2;
|
||||
device.fourcc = format[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) {
|
||||
if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) {
|
||||
if(format[i].component_order[0] == 'U' && format[i].component_order[1] == 'Y'
|
||||
&& format[i].component_order[2] == 'V' && format[i].component_order[3] == 'Y'
|
||||
) {
|
||||
device.format = XvFormatUYVY;
|
||||
device.fourcc = format[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(format);
|
||||
if(device.format == XvFormatUnknown) {
|
||||
fprintf(stderr, "VideoXv: unable to find a supported image format.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
device.width = 256;
|
||||
device.height = 256;
|
||||
|
||||
device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, device.width, device.height, &device.shminfo);
|
||||
if(!device.image) {
|
||||
fprintf(stderr, "VideoXv: XShmCreateImage failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777);
|
||||
device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0);
|
||||
device.shminfo.readOnly = false;
|
||||
if(!XShmAttach(device.display, &device.shminfo)) {
|
||||
fprintf(stderr, "VideoXv: XShmAttach failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer = new uint32_t[device.width * device.height];
|
||||
settings.width = 256;
|
||||
settings.height = 256;
|
||||
initTables();
|
||||
clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto term() -> void {
|
||||
XShmDetach(device.display, &device.shminfo);
|
||||
shmdt(device.shminfo.shmaddr);
|
||||
shmctl(device.shminfo.shmid, IPC_RMID, nullptr);
|
||||
XFree(device.image);
|
||||
|
||||
if(device.window) {
|
||||
XUnmapWindow(device.display, device.window);
|
||||
device.window = 0;
|
||||
}
|
||||
|
||||
if(device.colormap) {
|
||||
XFreeColormap(device.display, device.colormap);
|
||||
device.colormap = 0;
|
||||
}
|
||||
|
||||
if(device.display) {
|
||||
XCloseDisplay(device.display);
|
||||
device.display = nullptr;
|
||||
}
|
||||
|
||||
if(buffer) { delete[] buffer; buffer = nullptr; }
|
||||
if(ytable) { delete[] ytable; ytable = nullptr; }
|
||||
if(utable) { delete[] utable; utable = nullptr; }
|
||||
if(vtable) { delete[] vtable; vtable = nullptr; }
|
||||
}
|
||||
|
||||
private:
|
||||
auto renderRGB32(unsigned width, unsigned height) -> void {
|
||||
uint32_t* input = (uint32_t*)buffer;
|
||||
uint32_t* output = (uint32_t*)device.image->data;
|
||||
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
memcpy(output, input, width * 4);
|
||||
input += device.width;
|
||||
output += device.width;
|
||||
}
|
||||
}
|
||||
|
||||
auto renderRGB24(unsigned width, unsigned height) -> void {
|
||||
uint32_t* input = (uint32_t*)buffer;
|
||||
uint8_t* output = (uint8_t*)device.image->data;
|
||||
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
uint32_t p = *input++;
|
||||
*output++ = p;
|
||||
*output++ = p >> 8;
|
||||
*output++ = p >> 16;
|
||||
}
|
||||
|
||||
input += (device.width - width);
|
||||
output += (device.width - width) * 3;
|
||||
}
|
||||
}
|
||||
|
||||
auto renderRGB16(unsigned width, unsigned height) -> void {
|
||||
uint32_t* input = (uint32_t*)buffer;
|
||||
uint16_t* output = (uint16_t*)device.image->data;
|
||||
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
uint32_t p = *input++;
|
||||
*output++ = ((p >> 8) & 0xf800) | ((p >> 5) & 0x07e0) | ((p >> 3) & 0x001f); //RGB32->RGB16
|
||||
}
|
||||
|
||||
input += device.width - width;
|
||||
output += device.width - width;
|
||||
}
|
||||
}
|
||||
|
||||
auto renderRGB15(unsigned width, unsigned height) -> void {
|
||||
uint32_t* input = (uint32_t*)buffer;
|
||||
uint16_t* output = (uint16_t*)device.image->data;
|
||||
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
uint32_t p = *input++;
|
||||
*output++ = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x001f); //RGB32->RGB15
|
||||
}
|
||||
|
||||
input += device.width - width;
|
||||
output += device.width - width;
|
||||
}
|
||||
}
|
||||
|
||||
auto renderYUY2(unsigned width, unsigned height) -> void {
|
||||
uint32_t* input = (uint32_t*)buffer;
|
||||
uint16_t* output = (uint16_t*)device.image->data;
|
||||
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
for(unsigned x = 0; x < width >> 1; x++) {
|
||||
uint32_t p0 = *input++;
|
||||
uint32_t p1 = *input++;
|
||||
p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); //RGB32->RGB16
|
||||
p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); //RGB32->RGB16
|
||||
|
||||
uint8_t u = (utable[p0] + utable[p1]) >> 1;
|
||||
uint8_t v = (vtable[p0] + vtable[p1]) >> 1;
|
||||
|
||||
*output++ = (u << 8) | ytable[p0];
|
||||
*output++ = (v << 8) | ytable[p1];
|
||||
}
|
||||
|
||||
input += device.width - width;
|
||||
output += device.width - width;
|
||||
}
|
||||
}
|
||||
|
||||
auto renderUYVY(unsigned width, unsigned height) -> void {
|
||||
uint32_t* input = (uint32_t*)buffer;
|
||||
uint16_t* output = (uint16_t*)device.image->data;
|
||||
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
for(unsigned x = 0; x < width >> 1; x++) {
|
||||
uint32_t p0 = *input++;
|
||||
uint32_t p1 = *input++;
|
||||
p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f);
|
||||
p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f);
|
||||
|
||||
uint8_t u = (utable[p0] + utable[p1]) >> 1;
|
||||
uint8_t v = (vtable[p0] + vtable[p1]) >> 1;
|
||||
|
||||
*output++ = (ytable[p0] << 8) | u;
|
||||
*output++ = (ytable[p1] << 8) | v;
|
||||
}
|
||||
|
||||
input += device.width - width;
|
||||
output += device.width - width;
|
||||
}
|
||||
}
|
||||
|
||||
auto initTables() -> void {
|
||||
ytable = new uint8_t[65536];
|
||||
utable = new uint8_t[65536];
|
||||
vtable = new uint8_t[65536];
|
||||
|
||||
for(unsigned i = 0; i < 65536; i++) {
|
||||
//extract RGB565 color data from i
|
||||
uint8_t r = (i >> 11) & 31, g = (i >> 5) & 63, b = (i) & 31;
|
||||
r = (r << 3) | (r >> 2); //R5->R8
|
||||
g = (g << 2) | (g >> 4); //G6->G8
|
||||
b = (b << 3) | (b >> 2); //B5->B8
|
||||
|
||||
//ITU-R Recommendation BT.601
|
||||
//double lr = 0.299, lg = 0.587, lb = 0.114;
|
||||
int y = int( +(double(r) * 0.257) + (double(g) * 0.504) + (double(b) * 0.098) + 16.0 );
|
||||
int u = int( -(double(r) * 0.148) - (double(g) * 0.291) + (double(b) * 0.439) + 128.0 );
|
||||
int v = int( +(double(r) * 0.439) - (double(g) * 0.368) - (double(b) * 0.071) + 128.0 );
|
||||
|
||||
//ITU-R Recommendation BT.709
|
||||
//double lr = 0.2126, lg = 0.7152, lb = 0.0722;
|
||||
//int y = int( double(r) * lr + double(g) * lg + double(b) * lb );
|
||||
//int u = int( (double(b) - y) / (2.0 - 2.0 * lb) + 128.0 );
|
||||
//int v = int( (double(r) - y) / (2.0 - 2.0 * lr) + 128.0 );
|
||||
|
||||
ytable[i] = y < 0 ? 0 : y > 255 ? 255 : y;
|
||||
utable[i] = u < 0 ? 0 : u > 255 ? 255 : u;
|
||||
vtable[i] = v < 0 ? 0 : v > 255 ? 255 : v;
|
||||
}
|
||||
}
|
||||
};
|
Loading…
Reference in a new issue