First version split into asnes and bsnes.

This commit is contained in:
Tim Allen
2010-08-09 23:28:56 +10:00
commit 165f1e74b5
698 changed files with 145483 additions and 0 deletions

23
ruby/audio.hpp Executable file
View File

@@ -0,0 +1,23 @@
class Audio {
public:
static const char *Volume;
static const char *Resample;
static const char *ResampleRatio;
static const char *Handle;
static const char *Synchronize;
static const char *Frequency;
static const char *Latency;
virtual bool cap(const nall::string& name) { return false; }
virtual nall::any get(const nall::string& name) { return false; }
virtual bool set(const nall::string& name, const nall::any& value) { return false; }
virtual void sample(uint16_t left, uint16_t right) {}
virtual void clear() {}
virtual bool init() { return true; }
virtual void term() {}
Audio() {}
virtual ~Audio() {}
};

240
ruby/audio/alsa.cpp Executable file
View File

@@ -0,0 +1,240 @@
//audio.alsa (2009-11-30)
//authors: BearOso, byuu, Nach, RedDwarf
#include <alsa/asoundlib.h>
namespace ruby {
class pAudioALSA {
public:
struct {
snd_pcm_t *handle;
snd_pcm_format_t format;
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
int channels;
const char *name;
} device;
struct {
uint32_t *data;
unsigned length;
} buffer;
struct {
bool synchronize;
unsigned frequency;
unsigned latency;
} settings;
bool cap(const string& name) {
if(name == Audio::Synchronize) return true;
if(name == Audio::Frequency) return true;
if(name == Audio::Latency) return true;
return false;
}
any get(const string& name) {
if(name == Audio::Synchronize) return settings.synchronize;
if(name == Audio::Frequency) return settings.frequency;
if(name == Audio::Latency) return settings.latency;
return false;
}
bool set(const string& name, const any& value) {
if(name == Audio::Synchronize) {
if(settings.synchronize != any_cast<bool>(value)) {
settings.synchronize = any_cast<bool>(value);
if(device.handle) init();
}
return true;
}
if(name == Audio::Frequency) {
if(settings.frequency != any_cast<unsigned>(value)) {
settings.frequency = any_cast<unsigned>(value);
if(device.handle) init();
}
return true;
}
if(name == Audio::Latency) {
if(settings.latency != any_cast<unsigned>(value)) {
settings.latency = any_cast<unsigned>(value);
if(device.handle) init();
}
return true;
}
return false;
}
void sample(uint16_t left, uint16_t right) {
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));
}
}
void clear() {
}
bool init() {
term();
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;
}
void term() {
if(device.handle) {
snd_pcm_drain(device.handle);
snd_pcm_close(device.handle);
device.handle = 0;
}
if(buffer.data) {
delete[] buffer.data;
buffer.data = 0;
}
}
pAudioALSA() {
device.handle = 0;
device.format = SND_PCM_FORMAT_S16_LE;
device.channels = 2;
device.name = "default";
buffer.data = 0;
buffer.length = 0;
settings.synchronize = false;
settings.frequency = 22050;
settings.latency = 60;
}
~pAudioALSA() {
term();
}
};
DeclareAudio(ALSA)
};

94
ruby/audio/ao.cpp Executable file
View File

@@ -0,0 +1,94 @@
/*
audio.ao (2008-06-01)
authors: Nach, RedDwarf
*/
#include <ao/ao.h>
namespace ruby {
class pAudioAO {
public:
int driver_id;
ao_sample_format driver_format;
ao_device *audio_device;
struct {
unsigned frequency;
} settings;
bool cap(const string& name) {
if(name == Audio::Frequency) return true;
return false;
}
any get(const string& name) {
if(name == Audio::Frequency) return settings.frequency;
return false;
}
bool set(const string& name, const any& value) {
if(name == Audio::Frequency) {
settings.frequency = any_cast<unsigned>(value);
if(audio_device) init();
return true;
}
return false;
}
void sample(uint16_t l_sample, uint16_t r_sample) {
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
}
void clear() {
}
bool init() {
term();
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 = 0;
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;
}
void term() {
if(audio_device) {
ao_close(audio_device);
audio_device = 0;
}
}
pAudioAO() {
audio_device = 0;
ao_initialize();
settings.frequency = 22050;
}
~pAudioAO() {
term();
//ao_shutdown(); //FIXME: this is causing a segfault for some reason when called ...
}
};
DeclareAudio(AO)
};

212
ruby/audio/directsound.cpp Executable file
View File

@@ -0,0 +1,212 @@
/*
audio.directsound (2007-12-26)
author: byuu
*/
#include <dsound.h>
namespace ruby {
class pAudioDS {
public:
LPDIRECTSOUND ds;
LPDIRECTSOUNDBUFFER dsb_p, dsb_b;
DSBUFFERDESC dsbd;
WAVEFORMATEX wfx;
struct {
unsigned rings;
unsigned latency;
uint32_t *buffer;
unsigned bufferoffset;
unsigned readring;
unsigned writering;
int distance;
} device;
struct {
HWND handle;
bool synchronize;
unsigned frequency;
unsigned latency;
} settings;
bool cap(const string& name) {
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;
}
any get(const string& name) {
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 false;
}
bool set(const string& name, const any& value) {
if(name == Audio::Handle) {
settings.handle = (HWND)any_cast<uintptr_t>(value);
return true;
}
if(name == Audio::Synchronize) {
settings.synchronize = any_cast<bool>(value);
if(ds) clear();
return true;
}
if(name == Audio::Frequency) {
settings.frequency = any_cast<unsigned>(value);
if(ds) init();
return true;
}
if(name == Audio::Latency) {
settings.latency = any_cast<unsigned>(value);
if(ds) init();
return true;
}
return false;
}
void sample(uint16_t left, uint16_t right) {
device.buffer[device.bufferoffset++] = left + (right << 16);
if(device.bufferoffset < device.latency) return;
device.bufferoffset = 0;
DWORD pos, size;
void *output;
if(settings.synchronize == true) {
//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);
unsigned activering = pos / (device.latency * 4);
if(activering == device.readring) {
if(settings.synchronize == false) Sleep(1);
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);
}
}
void clear() {
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);
}
bool init() {
term();
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;
DirectSoundCreate(0, &ds, 0);
ds->SetCooperativeLevel((HWND)settings.handle, DSSCL_PRIORITY);
memset(&dsbd, 0, sizeof(dsbd));
dsbd.dwSize = sizeof(dsbd);
dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
dsbd.dwBufferBytes = 0;
dsbd.lpwfxFormat = 0;
ds->CreateSoundBuffer(&dsbd, &dsb_p, 0);
memset(&wfx, 0, 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);
memset(&dsbd, 0, 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;
}
void term() {
if(device.buffer) {
delete[] device.buffer;
device.buffer = 0;
}
if(dsb_b) { dsb_b->Stop(); dsb_b->Release(); dsb_b = 0; }
if(dsb_p) { dsb_p->Stop(); dsb_p->Release(); dsb_p = 0; }
if(ds) { ds->Release(); ds = 0; }
}
pAudioDS() {
ds = 0;
dsb_p = 0;
dsb_b = 0;
device.buffer = 0;
device.bufferoffset = 0;
device.readring = 0;
device.writering = 0;
device.distance = 0;
settings.handle = GetDesktopWindow();
settings.synchronize = false;
settings.frequency = 22050;
settings.latency = 120;
}
};
DeclareAudio(DS)
};

210
ruby/audio/openal.cpp Executable file
View File

@@ -0,0 +1,210 @@
/*
audio.openal (2007-12-26)
author: Nach
contributors: byuu, wertigon, _willow_
*/
#if defined(PLATFORM_OSX)
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
namespace ruby {
class pAudioOpenAL {
public:
struct {
ALCdevice *handle;
ALCcontext *context;
ALuint source;
ALenum format;
unsigned latency;
unsigned queue_length;
} device;
struct {
uint32_t *data;
unsigned length;
unsigned size;
} buffer;
struct {
bool synchronize;
unsigned frequency;
unsigned latency;
} settings;
bool cap(const string& name) {
if(name == Audio::Synchronize) return true;
if(name == Audio::Frequency) return true;
if(name == Audio::Latency) return true;
return false;
}
any get(const string& name) {
if(name == Audio::Synchronize) return settings.synchronize;
if(name == Audio::Frequency) return settings.frequency;
if(name == Audio::Latency) return settings.latency;
return false;
}
bool set(const string& name, const any& value) {
if(name == Audio::Synchronize) {
settings.synchronize = any_cast<bool>(value);
return true;
}
if(name == Audio::Frequency) {
settings.frequency = any_cast<unsigned>(value);
return true;
}
if(name == Audio::Latency) {
if(settings.latency != any_cast<unsigned>(value)) {
settings.latency = any_cast<unsigned>(value);
update_latency();
}
return true;
}
return false;
}
void sample(uint16_t sl, uint16_t sr) {
buffer.data[buffer.length++] = sl + (sr << 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.queue_length--;
}
//wait for buffer playback to catch up to sample generation if not synchronizing
if(settings.synchronize == false || device.queue_length < 3) break;
}
if(device.queue_length < 3) {
alGenBuffers(1, &albuffer);
alBufferData(albuffer, device.format, buffer.data, buffer.size * 4, settings.frequency);
alSourceQueueBuffers(device.source, 1, &albuffer);
device.queue_length++;
}
ALint playing;
alGetSourcei(device.source, AL_SOURCE_STATE, &playing);
if(playing != AL_PLAYING) alSourcePlay(device.source);
buffer.length = 0;
}
void clear() {
}
void update_latency() {
if(buffer.data) delete[] buffer.data;
buffer.size = settings.frequency * settings.latency / 1000.0 + 0.5;
buffer.data = new uint32_t[buffer.size];
}
bool init() {
update_latency();
device.queue_length = 0;
bool success = false;
if(device.handle = alcOpenDevice(NULL)) {
if(device.context = alcCreateContext(device.handle, NULL)) {
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;
}
void term() {
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.queue_length--;
}
}
alDeleteSources(1, &device.source);
device.source = 0;
}
if(device.context) {
alcMakeContextCurrent(NULL);
alcDestroyContext(device.context);
device.context = 0;
}
if(device.handle) {
alcCloseDevice(device.handle);
device.handle = 0;
}
if(buffer.data) {
delete[] buffer.data;
buffer.data = 0;
}
}
pAudioOpenAL() {
device.source = 0;
device.handle = 0;
device.context = 0;
device.format = AL_FORMAT_STEREO16;
device.queue_length = 0;
buffer.data = 0;
buffer.length = 0;
buffer.size = 0;
settings.synchronize = true;
settings.frequency = 22050;
settings.latency = 40;
}
~pAudioOpenAL() {
term();
}
};
DeclareAudio(OpenAL)
};

113
ruby/audio/oss.cpp Executable file
View File

@@ -0,0 +1,113 @@
/*
audio.oss (2007-12-26)
author: Nach
*/
#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, int)
#endif
#ifndef SNDCTL_DSP_POLICY
#define SNDCTL_DSP_POLICY _IOW('P', 45, int)
#endif
namespace ruby {
class pAudioOSS {
public:
struct {
int fd;
int format;
int channels;
const char *name;
} device;
struct {
unsigned frequency;
} settings;
bool cap(const string& name) {
if(name == Audio::Frequency) return true;
return false;
}
any get(const string& name) {
if(name == Audio::Frequency) return settings.frequency;
return false;
}
bool set(const string& name, const any& value) {
if(name == Audio::Frequency) {
settings.frequency = any_cast<unsigned>(value);
if(device.fd > 0) init();
return true;
}
return false;
}
void sample(uint16_t sl, uint16_t sr) {
uint32_t sample = sl + (sr << 16);
unsigned unused = write(device.fd, &sample, 4);
}
void clear() {
}
bool init() {
term();
device.fd = open(device.name, 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
int 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
int 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);
return true;
}
void term() {
if(device.fd > 0) {
close(device.fd);
device.fd = -1;
}
}
pAudioOSS() {
device.fd = -1;
device.format = AFMT_S16_LE;
device.channels = 2;
device.name = "/dev/dsp";
settings.frequency = 22050;
}
~pAudioOSS() {
term();
}
};
DeclareAudio(OSS)
};

177
ruby/audio/pulseaudio.cpp Executable file
View File

@@ -0,0 +1,177 @@
//audio.pulseaudio (2010-01-05)
//author: RedDwarf
#include <pulse/pulseaudio.h>
namespace ruby {
class pAudioPulseAudio {
public:
struct {
pa_mainloop *mainloop;
pa_context *context;
pa_stream *stream;
pa_sample_spec spec;
pa_buffer_attr buffer_attr;
bool first;
} device;
struct {
uint32_t *data;
size_t size;
unsigned offset;
} buffer;
struct {
bool synchronize;
unsigned frequency;
unsigned latency;
} settings;
bool cap(const string& name) {
if(name == Audio::Synchronize) return true;
if(name == Audio::Frequency) return true;
if(name == Audio::Latency) return true;
}
any get(const string& name) {
if(name == Audio::Synchronize) return settings.synchronize;
if(name == Audio::Frequency) return settings.frequency;
if(name == Audio::Latency) return settings.latency;
}
bool set(const string& name, const any& value) {
if(name == Audio::Synchronize) {
settings.synchronize = any_cast<bool>(value);
return true;
}
if(name == Audio::Frequency) {
settings.frequency = any_cast<unsigned>(value);
if(device.stream) {
pa_operation_unref(pa_stream_update_sample_rate(device.stream, settings.frequency, NULL, NULL));
}
return true;
}
if(name == Audio::Latency) {
settings.latency = any_cast<unsigned>(value);
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;
}
}
void sample(uint16_t left, uint16_t right) {
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;
}
void clear() {
}
bool init() {
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;
}
void term() {
if(buffer.data) {
pa_stream_cancel_write(device.stream);
buffer.data = 0;
}
if(device.stream) {
pa_stream_disconnect(device.stream);
pa_stream_unref(device.stream);
device.stream = 0;
}
if(device.context) {
pa_context_disconnect(device.context);
pa_context_unref(device.context);
device.context = 0;
}
if(device.mainloop) {
pa_mainloop_free(device.mainloop);
device.mainloop = 0;
}
}
pAudioPulseAudio() {
device.mainloop = 0;
device.context = 0;
device.stream = 0;
buffer.data = 0;
settings.synchronize = false;
settings.frequency = 22050;
settings.latency = 60;
}
~pAudioPulseAudio() {
term();
}
};
DeclareAudio(PulseAudio)
}

115
ruby/audio/pulseaudiosimple.cpp Executable file
View File

@@ -0,0 +1,115 @@
//audio.pulseaudiosimple (2010-01-05)
//author: byuu
#include <pulse/simple.h>
#include <pulse/error.h>
namespace ruby {
class pAudioPulseAudioSimple {
public:
struct {
pa_simple *handle;
pa_sample_spec spec;
} device;
struct {
uint32_t *data;
unsigned offset;
} buffer;
struct {
unsigned frequency;
} settings;
bool cap(const string& name) {
if(name == Audio::Frequency) return true;
return false;
}
any get(const string& name) {
if(name == Audio::Frequency) return settings.frequency;
return false;
}
bool set(const string& name, const any& value) {
if(name == Audio::Frequency) {
settings.frequency = any_cast<unsigned>(value);
if(device.handle) init();
return true;
}
return false;
}
void sample(uint16_t left, uint16_t right) {
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;
}
}
void clear() {
}
bool init() {
term();
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;
}
void term() {
if(device.handle) {
int error;
pa_simple_flush(device.handle, &error);
pa_simple_free(device.handle);
device.handle = 0;
}
if(buffer.data) {
delete[] buffer.data;
buffer.data = 0;
}
}
pAudioPulseAudioSimple() {
device.handle = 0;
buffer.data = 0;
settings.frequency = 22050;
}
~pAudioPulseAudioSimple() {
term();
}
};
DeclareAudio(PulseAudioSimple)
};

22
ruby/input.hpp Executable file
View File

@@ -0,0 +1,22 @@
class Input {
public:
static const char *Handle;
static const char *KeyboardSupport;
static const char *MouseSupport;
static const char *JoypadSupport;
virtual bool cap(const nall::string& name) { return false; }
virtual nall::any get(const nall::string& name) { return false; }
virtual bool set(const nall::string& name, const nall::any& value) { return false; }
virtual bool acquire() { return false; }
virtual bool unacquire() { return false; }
virtual bool acquired() { return false; }
virtual bool poll(int16_t *table) { return false; }
virtual bool init() { return true; }
virtual void term() {}
Input() {}
virtual ~Input() {}
};

157
ruby/input/carbon.cpp Executable file
View File

@@ -0,0 +1,157 @@
namespace ruby {
class pInputCarbon {
public:
bool cap(const string& name) {
return false;
}
any get(const string& name) {
return false;
}
bool set(const string& name, const any& value) {
return false;
}
bool acquire() { return false; }
bool unacquire() { return false; }
bool acquired() { return false; }
bool poll(int16_t *table) {
memset(table, 0, Scancode::Limit * sizeof(int16_t));
KeyMap keys;
GetKeys(keys);
uint8_t *keymap = (uint8_t*)keys;
#define map(id, name) table[keyboard(0)[name]] = (bool)(keymap[id >> 3] & (1 << (id & 7)))
map(0x35, Keyboard::Escape);
map(0x7a, Keyboard::F1);
map(0x78, Keyboard::F2);
map(0x63, Keyboard::F3);
map(0x76, Keyboard::F4);
map(0x60, Keyboard::F5);
map(0x61, Keyboard::F6);
map(0x62, Keyboard::F7);
map(0x64, Keyboard::F8);
map(0x65, Keyboard::F9);
map(0x6d, Keyboard::F10);
map(0x67, Keyboard::F11);
//map(0x??, Keyboard::F12);
map(0x69, Keyboard::PrintScreen);
//map(0x??, Keyboard::ScrollLock);
map(0x71, Keyboard::Pause);
map(0x32, Keyboard::Tilde);
map(0x12, Keyboard::Num1);
map(0x13, Keyboard::Num2);
map(0x14, Keyboard::Num3);
map(0x15, Keyboard::Num4);
map(0x17, Keyboard::Num5);
map(0x16, Keyboard::Num6);
map(0x1a, Keyboard::Num7);
map(0x1c, Keyboard::Num8);
map(0x19, Keyboard::Num9);
map(0x1d, Keyboard::Num0);
map(0x1b, Keyboard::Dash);
map(0x18, Keyboard::Equal);
map(0x33, Keyboard::Backspace);
map(0x72, Keyboard::Insert);
map(0x75, Keyboard::Delete);
map(0x73, Keyboard::Home);
map(0x77, Keyboard::End);
map(0x74, Keyboard::PageUp);
map(0x79, Keyboard::PageDown);
map(0x00, Keyboard::A);
map(0x0b, Keyboard::B);
map(0x08, Keyboard::C);
map(0x02, Keyboard::D);
map(0x0e, Keyboard::E);
map(0x03, Keyboard::F);
map(0x05, Keyboard::G);
map(0x04, Keyboard::H);
map(0x22, Keyboard::I);
map(0x26, Keyboard::J);
map(0x28, Keyboard::K);
map(0x25, Keyboard::L);
map(0x2e, Keyboard::M);
map(0x2d, Keyboard::N);
map(0x1f, Keyboard::O);
map(0x23, Keyboard::P);
map(0x0c, Keyboard::Q);
map(0x0f, Keyboard::R);
map(0x01, Keyboard::S);
map(0x11, Keyboard::T);
map(0x20, Keyboard::U);
map(0x09, Keyboard::V);
map(0x0d, Keyboard::W);
map(0x07, Keyboard::X);
map(0x10, Keyboard::Y);
map(0x06, Keyboard::Z);
map(0x21, Keyboard::LeftBracket);
map(0x1e, Keyboard::RightBracket);
map(0x2a, Keyboard::Backslash);
map(0x29, Keyboard::Semicolon);
map(0x27, Keyboard::Apostrophe);
map(0x2b, Keyboard::Comma);
map(0x2f, Keyboard::Period);
map(0x2c, Keyboard::Slash);
map(0x53, Keyboard::Keypad1);
map(0x54, Keyboard::Keypad2);
map(0x55, Keyboard::Keypad3);
map(0x56, Keyboard::Keypad4);
map(0x57, Keyboard::Keypad5);
map(0x58, Keyboard::Keypad6);
map(0x59, Keyboard::Keypad7);
map(0x5b, Keyboard::Keypad8);
map(0x5c, Keyboard::Keypad9);
map(0x52, Keyboard::Keypad0);
//map(0x??, Keyboard::Point);
map(0x4c, Keyboard::Enter);
map(0x45, Keyboard::Add);
map(0x4e, Keyboard::Subtract);
map(0x43, Keyboard::Multiply);
map(0x4b, Keyboard::Divide);
map(0x47, Keyboard::NumLock);
//map(0x39, Keyboard::CapsLock);
map(0x7e, Keyboard::Up);
map(0x7d, Keyboard::Down);
map(0x7b, Keyboard::Left);
map(0x7c, Keyboard::Right);
map(0x30, Keyboard::Tab);
map(0x24, Keyboard::Return);
map(0x31, Keyboard::Spacebar);
//map(0x??, Keyboard::Menu);
map(0x38, Keyboard::Shift);
map(0x3b, Keyboard::Control);
map(0x3a, Keyboard::Alt);
map(0x37, Keyboard::Super);
#undef map
return true;
}
bool init() {
return true;
}
void term() {
}
};
DeclareInput(Carbon)
};

387
ruby/input/directinput.cpp Executable file
View File

@@ -0,0 +1,387 @@
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
namespace ruby {
static BOOL CALLBACK DI_EnumJoypadsCallback(const DIDEVICEINSTANCE*, void*);
static BOOL CALLBACK DI_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE*, void*);
using namespace nall;
class pInputDI {
public:
struct {
LPDIRECTINPUT8 context;
LPDIRECTINPUTDEVICE8 keyboard;
LPDIRECTINPUTDEVICE8 mouse;
LPDIRECTINPUTDEVICE8 gamepad[Joypad::Count];
bool mouseacquired;
} device;
struct {
HWND handle;
} settings;
bool cap(const string& name) {
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;
}
any get(const string& name) {
if(name == Input::Handle) return (uintptr_t)settings.handle;
return false;
}
bool set(const string& name, const any& value) {
if(name == Input::Handle) {
settings.handle = (HWND)any_cast<uintptr_t>(value);
return true;
}
return false;
}
bool poll(int16_t *table) {
memset(table, 0, Scancode::Limit * sizeof(int16_t));
//========
//Keyboard
//========
if(device.keyboard) {
uint8_t state[256];
if(FAILED(device.keyboard->GetDeviceState(sizeof state, state))) {
device.keyboard->Acquire();
if(FAILED(device.keyboard->GetDeviceState(sizeof state, state))) {
memset(state, 0, sizeof state);
}
}
#define key(id) table[keyboard(0)[id]]
key(Keyboard::Escape) = (bool)(state[0x01] & 0x80);
key(Keyboard::F1 ) = (bool)(state[0x3b] & 0x80);
key(Keyboard::F2 ) = (bool)(state[0x3c] & 0x80);
key(Keyboard::F3 ) = (bool)(state[0x3d] & 0x80);
key(Keyboard::F4 ) = (bool)(state[0x3e] & 0x80);
key(Keyboard::F5 ) = (bool)(state[0x3f] & 0x80);
key(Keyboard::F6 ) = (bool)(state[0x40] & 0x80);
key(Keyboard::F7 ) = (bool)(state[0x41] & 0x80);
key(Keyboard::F8 ) = (bool)(state[0x42] & 0x80);
key(Keyboard::F9 ) = (bool)(state[0x43] & 0x80);
key(Keyboard::F10 ) = (bool)(state[0x44] & 0x80);
key(Keyboard::F11 ) = (bool)(state[0x57] & 0x80);
key(Keyboard::F12 ) = (bool)(state[0x58] & 0x80);
key(Keyboard::PrintScreen) = (bool)(state[0xb7] & 0x80);
key(Keyboard::ScrollLock ) = (bool)(state[0x46] & 0x80);
key(Keyboard::Pause ) = (bool)(state[0xc5] & 0x80);
key(Keyboard::Tilde ) = (bool)(state[0x29] & 0x80);
key(Keyboard::Num1) = (bool)(state[0x02] & 0x80);
key(Keyboard::Num2) = (bool)(state[0x03] & 0x80);
key(Keyboard::Num3) = (bool)(state[0x04] & 0x80);
key(Keyboard::Num4) = (bool)(state[0x05] & 0x80);
key(Keyboard::Num5) = (bool)(state[0x06] & 0x80);
key(Keyboard::Num6) = (bool)(state[0x07] & 0x80);
key(Keyboard::Num7) = (bool)(state[0x08] & 0x80);
key(Keyboard::Num8) = (bool)(state[0x09] & 0x80);
key(Keyboard::Num9) = (bool)(state[0x0a] & 0x80);
key(Keyboard::Num0) = (bool)(state[0x0b] & 0x80);
key(Keyboard::Dash ) = (bool)(state[0x0c] & 0x80);
key(Keyboard::Equal ) = (bool)(state[0x0d] & 0x80);
key(Keyboard::Backspace) = (bool)(state[0x0e] & 0x80);
key(Keyboard::Insert ) = (bool)(state[0xd2] & 0x80);
key(Keyboard::Delete ) = (bool)(state[0xd3] & 0x80);
key(Keyboard::Home ) = (bool)(state[0xc7] & 0x80);
key(Keyboard::End ) = (bool)(state[0xcf] & 0x80);
key(Keyboard::PageUp ) = (bool)(state[0xc9] & 0x80);
key(Keyboard::PageDown) = (bool)(state[0xd1] & 0x80);
key(Keyboard::A) = (bool)(state[0x1e] & 0x80);
key(Keyboard::B) = (bool)(state[0x30] & 0x80);
key(Keyboard::C) = (bool)(state[0x2e] & 0x80);
key(Keyboard::D) = (bool)(state[0x20] & 0x80);
key(Keyboard::E) = (bool)(state[0x12] & 0x80);
key(Keyboard::F) = (bool)(state[0x21] & 0x80);
key(Keyboard::G) = (bool)(state[0x22] & 0x80);
key(Keyboard::H) = (bool)(state[0x23] & 0x80);
key(Keyboard::I) = (bool)(state[0x17] & 0x80);
key(Keyboard::J) = (bool)(state[0x24] & 0x80);
key(Keyboard::K) = (bool)(state[0x25] & 0x80);
key(Keyboard::L) = (bool)(state[0x26] & 0x80);
key(Keyboard::M) = (bool)(state[0x32] & 0x80);
key(Keyboard::N) = (bool)(state[0x31] & 0x80);
key(Keyboard::O) = (bool)(state[0x18] & 0x80);
key(Keyboard::P) = (bool)(state[0x19] & 0x80);
key(Keyboard::Q) = (bool)(state[0x10] & 0x80);
key(Keyboard::R) = (bool)(state[0x13] & 0x80);
key(Keyboard::S) = (bool)(state[0x1f] & 0x80);
key(Keyboard::T) = (bool)(state[0x14] & 0x80);
key(Keyboard::U) = (bool)(state[0x16] & 0x80);
key(Keyboard::V) = (bool)(state[0x2f] & 0x80);
key(Keyboard::W) = (bool)(state[0x11] & 0x80);
key(Keyboard::X) = (bool)(state[0x2d] & 0x80);
key(Keyboard::Y) = (bool)(state[0x15] & 0x80);
key(Keyboard::Z) = (bool)(state[0x2c] & 0x80);
key(Keyboard::LeftBracket ) = (bool)(state[0x1a] & 0x80);
key(Keyboard::RightBracket) = (bool)(state[0x1b] & 0x80);
key(Keyboard::Backslash ) = (bool)(state[0x2b] & 0x80);
key(Keyboard::Semicolon ) = (bool)(state[0x27] & 0x80);
key(Keyboard::Apostrophe ) = (bool)(state[0x28] & 0x80);
key(Keyboard::Comma ) = (bool)(state[0x33] & 0x80);
key(Keyboard::Period ) = (bool)(state[0x34] & 0x80);
key(Keyboard::Slash ) = (bool)(state[0x35] & 0x80);
key(Keyboard::Keypad0) = (bool)(state[0x4f] & 0x80);
key(Keyboard::Keypad1) = (bool)(state[0x50] & 0x80);
key(Keyboard::Keypad2) = (bool)(state[0x51] & 0x80);
key(Keyboard::Keypad3) = (bool)(state[0x4b] & 0x80);
key(Keyboard::Keypad4) = (bool)(state[0x4c] & 0x80);
key(Keyboard::Keypad5) = (bool)(state[0x4d] & 0x80);
key(Keyboard::Keypad6) = (bool)(state[0x47] & 0x80);
key(Keyboard::Keypad7) = (bool)(state[0x48] & 0x80);
key(Keyboard::Keypad8) = (bool)(state[0x49] & 0x80);
key(Keyboard::Keypad9) = (bool)(state[0x52] & 0x80);
key(Keyboard::Point ) = (bool)(state[0x53] & 0x80);
key(Keyboard::Add ) = (bool)(state[0x4e] & 0x80);
key(Keyboard::Subtract) = (bool)(state[0x4a] & 0x80);
key(Keyboard::Multiply) = (bool)(state[0x37] & 0x80);
key(Keyboard::Divide ) = (bool)(state[0xb5] & 0x80);
key(Keyboard::Enter ) = (bool)(state[0x9c] & 0x80);
key(Keyboard::NumLock ) = (bool)(state[0x45] & 0x80);
key(Keyboard::CapsLock) = (bool)(state[0x3a] & 0x80);
key(Keyboard::Up ) = (bool)(state[0xc8] & 0x80);
key(Keyboard::Down ) = (bool)(state[0xd0] & 0x80);
key(Keyboard::Left ) = (bool)(state[0xcb] & 0x80);
key(Keyboard::Right) = (bool)(state[0xcd] & 0x80);
key(Keyboard::Tab ) = (bool)(state[0x0f] & 0x80);
key(Keyboard::Return ) = (bool)(state[0x1c] & 0x80);
key(Keyboard::Spacebar) = (bool)(state[0x39] & 0x80);
key(Keyboard::Menu ) = (bool)(state[0xdd] & 0x80);
key(Keyboard::Shift ) = (bool)(state[0x2a] & 0x80) || (bool)(state[0x36] & 0x80);
key(Keyboard::Control) = (bool)(state[0x1d] & 0x80) || (bool)(state[0x9d] & 0x80);
key(Keyboard::Alt ) = (bool)(state[0x38] & 0x80) || (bool)(state[0xb8] & 0x80);
key(Keyboard::Super ) = (bool)(state[0xdb] & 0x80) || (bool)(state[0xdc] & 0x80);
#undef key
}
//=====
//Mouse
//=====
if(device.mouse) {
DIMOUSESTATE2 state;
if(FAILED(device.mouse->GetDeviceState(sizeof(DIMOUSESTATE2), (void*)&state))) {
device.mouse->Acquire();
if(FAILED(device.mouse->GetDeviceState(sizeof(DIMOUSESTATE2), (void*)&state))) {
memset(&state, 0, sizeof(DIMOUSESTATE2));
}
}
table[mouse(0).axis(0)] = state.lX;
table[mouse(0).axis(1)] = state.lY;
table[mouse(0).axis(2)] = state.lZ / WHEEL_DELTA;
for(unsigned n = 0; n < Mouse::Buttons; n++) {
table[mouse(0).button(n)] = (bool)state.rgbButtons[n];
}
//on Windows, 0 = left, 1 = right, 2 = middle
//swap middle and right buttons for consistency with Linux
int16_t temp = table[mouse(0).button(1)];
table[mouse(0).button(1)] = table[mouse(0).button(2)];
table[mouse(0).button(2)] = temp;
}
//=========
//Joypad(s)
//=========
for(unsigned i = 0; i < Joypad::Count; i++) {
if(!device.gamepad[i]) continue;
if(FAILED(device.gamepad[i]->Poll())) {
device.gamepad[i]->Acquire();
continue;
}
DIJOYSTATE2 state;
device.gamepad[i]->GetDeviceState(sizeof(DIJOYSTATE2), &state);
//POV hats
for(unsigned n = 0; n < min((unsigned)Joypad::Hats, 4); n++) {
//POV value is in clockwise-hundredth degree units.
unsigned pov = state.rgdwPOV[n];
//some drivers report a centered POV hat as -1U, others as 65535U.
//>= 36000 will match both, as well as invalid ranges.
if(pov < 36000) {
if(pov >= 31500 || pov <= 4500) table[joypad(i).hat(n)] |= Joypad::HatUp;
if(pov >= 4500 && pov <= 13500) table[joypad(i).hat(n)] |= Joypad::HatRight;
if(pov >= 13500 && pov <= 22500) table[joypad(i).hat(n)] |= Joypad::HatDown;
if(pov >= 22500 && pov <= 31500) table[joypad(i).hat(n)] |= Joypad::HatLeft;
}
}
//axes
table[joypad(i).axis(0)] = state.lX;
table[joypad(i).axis(1)] = state.lY;
table[joypad(i).axis(2)] = state.lZ;
table[joypad(i).axis(3)] = state.lRx;
table[joypad(i).axis(4)] = state.lRy;
table[joypad(i).axis(5)] = state.lRz;
//buttons
for(unsigned n = 0; n < min((unsigned)Joypad::Buttons, 128); n++) {
table[joypad(i).button(n)] = (bool)state.rgbButtons[n];
}
}
return true;
}
bool init_joypad(const DIDEVICEINSTANCE *instance) {
unsigned n;
for(n = 0; n < Joypad::Count; n++) { if(!device.gamepad[n]) break; }
if(n >= Joypad::Count) return DIENUM_STOP;
if(FAILED(device.context->CreateDevice(instance->guidInstance, &device.gamepad[n], 0))) {
return DIENUM_CONTINUE; //continue and try next gamepad
}
device.gamepad[n]->SetDataFormat(&c_dfDIJoystick2);
device.gamepad[n]->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
device.gamepad[n]->EnumObjects(DI_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS);
return DIENUM_CONTINUE;
}
bool init_axis(const DIDEVICEOBJECTINSTANCE *instance) {
signed n;
for(n = Joypad::Count - 1; n >= 0; n--) { if(device.gamepad[n]) break; }
if(n < 0) return DIENUM_STOP;
DIPROPRANGE range;
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.gamepad[n]->SetProperty(DIPROP_RANGE, &range.diph);
return DIENUM_CONTINUE;
}
bool init() {
device.context = 0;
device.keyboard = 0;
device.mouse = 0;
for(unsigned i = 0; i < Joypad::Count; i++) device.gamepad[i] = 0;
device.mouseacquired = false;
DirectInput8Create(GetModuleHandle(0), 0x0800, IID_IDirectInput8, (void**)&device.context, 0);
device.context->CreateDevice(GUID_SysKeyboard, &device.keyboard, 0);
device.keyboard->SetDataFormat(&c_dfDIKeyboard);
device.keyboard->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
device.keyboard->Acquire();
device.context->CreateDevice(GUID_SysMouse, &device.mouse, 0);
device.mouse->SetDataFormat(&c_dfDIMouse2);
HRESULT hr = device.mouse->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
device.mouse->Acquire();
device.context->EnumDevices(DI8DEVCLASS_GAMECTRL, DI_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY);
return true;
}
void term() {
if(device.keyboard) {
device.keyboard->Unacquire();
device.keyboard->Release();
device.keyboard = 0;
}
if(device.mouse) {
device.mouse->Unacquire();
device.mouse->Release();
device.mouse = 0;
}
for(unsigned i = 0; i < Joypad::Count; i++) {
if(device.gamepad[i]) {
device.gamepad[i]->Unacquire();
device.gamepad[i]->Release();
device.gamepad[i] = 0;
}
}
if(device.context) {
device.context->Release();
device.context = 0;
}
}
bool acquire() {
if(!device.mouse) return false;
if(acquired() == false) {
device.mouse->Unacquire();
device.mouse->SetCooperativeLevel(settings.handle, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
device.mouse->Acquire();
device.mouseacquired = true;
}
return true;
}
bool unacquire() {
if(!device.mouse) return false;
if(acquired() == true) {
device.mouse->Unacquire();
device.mouse->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
device.mouse->Acquire();
device.mouseacquired = false;
}
return true;
}
bool acquired() {
return device.mouseacquired;
}
pInputDI() {
device.context = 0;
device.keyboard = 0;
device.mouse = 0;
for(unsigned i = 0; i < Joypad::Count; i++) device.gamepad[i] = 0;
device.mouseacquired = false;
settings.handle = 0;
}
~pInputDI() { term(); }
};
BOOL CALLBACK DI_EnumJoypadsCallback(const DIDEVICEINSTANCE *instance, void *p) {
return ((pInputDI*)p)->init_joypad(instance);
}
BOOL CALLBACK DI_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE *instance, void *p) {
return ((pInputDI*)p)->init_axis(instance);
}
DeclareInput(DI)
};

797
ruby/input/rawinput.cpp Executable file
View File

@@ -0,0 +1,797 @@
//RawInput driver
//author: byuu
//this driver utilizes RawInput (WM_INPUT) to capture keyboard and mouse input.
//although this requires WinXP or newer, it is the only way to uniquely identify
//and independently map multiple keyboards and mice. DirectInput merges all
//keyboards and mice into one device per.
//
//as WM_INPUT lacks specific RAWINPUT structures for gamepads, giving only raw
//data, and because DirectInput supports up to 16 joypads, DirectInput is used
//for joypad mapping.
//
//further, Xbox 360 controllers are explicitly detected and supported through
//XInput. this is because under DirectInput, the LT / RT (trigger) buttons are
//merged into a single Z-axis -- making it impossible to detect both buttons
//being pressed at the same time. with XInput, the state of both trigger
//buttons can be read independently.
//
//so in essence, this is actually more of a hybrid driver.
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
#include <xinput.h>
namespace ruby {
static DWORD WINAPI RawInputThreadProc(void*);
static LRESULT CALLBACK RawInputWindowProc(HWND, UINT, WPARAM, LPARAM);
class RawInput {
public:
HANDLE mutex;
HWND hwnd;
bool initialized;
bool ready;
struct Device {
HANDLE handle;
};
struct Keyboard : Device {
bool state[nall::Keyboard::Size];
void update(RAWINPUT *input) {
unsigned code = input->data.keyboard.MakeCode;
unsigned flags = input->data.keyboard.Flags;
#define map(id, flag, name) if(code == id) state[name] = (bool)(flags == flag);
map(0x0001, 0, nall::Keyboard::Escape)
map(0x003b, 0, nall::Keyboard::F1)
map(0x003c, 0, nall::Keyboard::F2)
map(0x003d, 0, nall::Keyboard::F3)
map(0x003e, 0, nall::Keyboard::F4)
map(0x003f, 0, nall::Keyboard::F5)
map(0x0040, 0, nall::Keyboard::F6)
map(0x0041, 0, nall::Keyboard::F7)
map(0x0042, 0, nall::Keyboard::F8)
map(0x0043, 0, nall::Keyboard::F9)
map(0x0044, 0, nall::Keyboard::F10)
map(0x0057, 0, nall::Keyboard::F11)
map(0x0058, 0, nall::Keyboard::F12)
map(0x0037, 2, nall::Keyboard::PrintScreen)
map(0x0046, 0, nall::Keyboard::ScrollLock)
map(0x001d, 4, nall::Keyboard::Pause)
map(0x0029, 0, nall::Keyboard::Tilde)
map(0x0002, 0, nall::Keyboard::Num1)
map(0x0003, 0, nall::Keyboard::Num2)
map(0x0004, 0, nall::Keyboard::Num3)
map(0x0005, 0, nall::Keyboard::Num4)
map(0x0006, 0, nall::Keyboard::Num5)
map(0x0007, 0, nall::Keyboard::Num6)
map(0x0008, 0, nall::Keyboard::Num7)
map(0x0009, 0, nall::Keyboard::Num8)
map(0x000a, 0, nall::Keyboard::Num9)
map(0x000b, 0, nall::Keyboard::Num0)
map(0x000c, 0, nall::Keyboard::Dash)
map(0x000d, 0, nall::Keyboard::Equal)
map(0x000e, 0, nall::Keyboard::Backspace)
map(0x0052, 2, nall::Keyboard::Insert)
map(0x0053, 2, nall::Keyboard::Delete)
map(0x0047, 2, nall::Keyboard::Home)
map(0x004f, 2, nall::Keyboard::End)
map(0x0049, 2, nall::Keyboard::PageUp)
map(0x0051, 2, nall::Keyboard::PageDown)
map(0x001e, 0, nall::Keyboard::A)
map(0x0030, 0, nall::Keyboard::B)
map(0x002e, 0, nall::Keyboard::C)
map(0x0020, 0, nall::Keyboard::D)
map(0x0012, 0, nall::Keyboard::E)
map(0x0021, 0, nall::Keyboard::F)
map(0x0022, 0, nall::Keyboard::G)
map(0x0023, 0, nall::Keyboard::H)
map(0x0017, 0, nall::Keyboard::I)
map(0x0024, 0, nall::Keyboard::J)
map(0x0025, 0, nall::Keyboard::K)
map(0x0026, 0, nall::Keyboard::L)
map(0x0032, 0, nall::Keyboard::M)
map(0x0031, 0, nall::Keyboard::N)
map(0x0018, 0, nall::Keyboard::O)
map(0x0019, 0, nall::Keyboard::P)
map(0x0010, 0, nall::Keyboard::Q)
map(0x0013, 0, nall::Keyboard::R)
map(0x001f, 0, nall::Keyboard::S)
map(0x0014, 0, nall::Keyboard::T)
map(0x0016, 0, nall::Keyboard::U)
map(0x002f, 0, nall::Keyboard::V)
map(0x0011, 0, nall::Keyboard::W)
map(0x002d, 0, nall::Keyboard::X)
map(0x0015, 0, nall::Keyboard::Y)
map(0x002c, 0, nall::Keyboard::Z)
map(0x001a, 0, nall::Keyboard::LeftBracket)
map(0x001b, 0, nall::Keyboard::RightBracket)
map(0x002b, 0, nall::Keyboard::Backslash)
map(0x0027, 0, nall::Keyboard::Semicolon)
map(0x0028, 0, nall::Keyboard::Apostrophe)
map(0x0033, 0, nall::Keyboard::Comma)
map(0x0034, 0, nall::Keyboard::Period)
map(0x0035, 0, nall::Keyboard::Slash)
map(0x004f, 0, nall::Keyboard::Keypad1)
map(0x0050, 0, nall::Keyboard::Keypad2)
map(0x0051, 0, nall::Keyboard::Keypad3)
map(0x004b, 0, nall::Keyboard::Keypad4)
map(0x004c, 0, nall::Keyboard::Keypad5)
map(0x004d, 0, nall::Keyboard::Keypad6)
map(0x0047, 0, nall::Keyboard::Keypad7)
map(0x0048, 0, nall::Keyboard::Keypad8)
map(0x0049, 0, nall::Keyboard::Keypad9)
map(0x0052, 0, nall::Keyboard::Keypad0)
map(0x0053, 0, nall::Keyboard::Point)
map(0x001c, 2, nall::Keyboard::Enter)
map(0x004e, 0, nall::Keyboard::Add)
map(0x004a, 0, nall::Keyboard::Subtract)
map(0x0037, 0, nall::Keyboard::Multiply)
map(0x0035, 2, nall::Keyboard::Divide)
map(0x0045, 0, nall::Keyboard::NumLock)
map(0x003a, 0, nall::Keyboard::CapsLock)
//Pause signals 0x1d:4 + 0x45:0, whereas NumLock signals only 0x45:0.
//this makes it impractical to detect both Pause+NumLock independently.
//workaround: always detect Pause; detect NumLock only when Pause is released.
if(state[nall::Keyboard::Pause]) state[nall::Keyboard::NumLock] = false;
map(0x0048, 2, nall::Keyboard::Up)
map(0x0050, 2, nall::Keyboard::Down)
map(0x004b, 2, nall::Keyboard::Left)
map(0x004d, 2, nall::Keyboard::Right)
map(0x000f, 0, nall::Keyboard::Tab)
map(0x001c, 0, nall::Keyboard::Return)
map(0x0039, 0, nall::Keyboard::Spacebar)
map(0x005d, 2, nall::Keyboard::Menu)
//merge left and right modifiers to one ID
if(code == 0x002a && flags == 0) state[nall::Keyboard::Shift] = 1; //left shift
if(code == 0x002a && flags == 1) state[nall::Keyboard::Shift] = 0;
if(code == 0x0036 && flags == 0) state[nall::Keyboard::Shift] = 1; //right shift
if(code == 0x0036 && flags == 1) state[nall::Keyboard::Shift] = 0;
if(code == 0x001d && flags == 0) state[nall::Keyboard::Control] = 1; //left control
if(code == 0x001d && flags == 1) state[nall::Keyboard::Control] = 0;
if(code == 0x001d && flags == 2) state[nall::Keyboard::Control] = 1; //right control
if(code == 0x001d && flags == 3) state[nall::Keyboard::Control] = 0;
if(code == 0x0038 && flags == 0) state[nall::Keyboard::Alt] = 1; //left alt
if(code == 0x0038 && flags == 1) state[nall::Keyboard::Alt] = 0;
if(code == 0x0038 && flags == 2) state[nall::Keyboard::Alt] = 1; //right alt
if(code == 0x0038 && flags == 3) state[nall::Keyboard::Alt] = 0;
if(code == 0x005b && flags == 2) state[nall::Keyboard::Super] = 1; //left super
if(code == 0x005b && flags == 3) state[nall::Keyboard::Super] = 0;
if(code == 0x005c && flags == 2) state[nall::Keyboard::Super] = 1; //right super
if(code == 0x005c && flags == 3) state[nall::Keyboard::Super] = 0;
#undef map
}
Keyboard() {
for(unsigned i = 0; i < nall::Keyboard::Size; i++) state[i] = false;
}
};
struct Mouse : Device {
signed xDistance;
signed yDistance;
signed zDistance;
unsigned buttonState;
void sync() {
xDistance = 0;
yDistance = 0;
zDistance = 0;
}
void update(RAWINPUT *input) {
if((input->data.mouse.usFlags & 1) == MOUSE_MOVE_RELATIVE) {
xDistance += input->data.mouse.lLastX;
yDistance += input->data.mouse.lLastY;
}
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) buttonState |= 1 << 0;
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP ) buttonState &=~ 1 << 0;
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) buttonState |= 1 << 2; //swap middle and right buttons,
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP ) buttonState &=~ 1 << 2; //for consistency with Linux:
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) buttonState |= 1 << 1; //left = 0, middle = 1, right = 2
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP ) buttonState &=~ 1 << 1;
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) buttonState |= 1 << 3;
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP ) buttonState &=~ 1 << 3;
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) buttonState |= 1 << 4;
if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP ) buttonState &=~ 1 << 4;
if(input->data.mouse.usButtonFlags & RI_MOUSE_WHEEL) {
zDistance += (int16_t)input->data.mouse.usButtonData;
}
}
Mouse() {
xDistance = yDistance = zDistance = 0;
buttonState = 0;
}
};
//keep track of gamepads for the sole purpose of distinguishing XInput devices
//from all other devices. this is necessary, as DirectInput does not provide
//a way to retrieve the necessary RIDI_DEVICENAME string.
struct Gamepad : Device {
bool isXInputDevice;
uint16_t vendorId;
uint16_t productId;
};
linear_vector<Keyboard> lkeyboard;
linear_vector<Mouse> lmouse;
linear_vector<Gamepad> lgamepad;
LRESULT window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
if(msg == WM_INPUT) {
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) {
for(unsigned i = 0; i < lkeyboard.size(); i++) {
if(input->header.hDevice == lkeyboard[i].handle) {
lkeyboard[i].update(input);
break;
}
}
} else if(input->header.dwType == RIM_TYPEMOUSE) {
for(unsigned i = 0; i < lmouse.size(); i++) {
if(input->header.hDevice == lmouse[i].handle) {
lmouse[i].update(input);
break;
}
}
}
ReleaseMutex(mutex);
//allow propogation of WM_INPUT message
LRESULT result = DefRawInputProc(&input, size, sizeof(RAWINPUTHEADER));
delete[] input;
return result;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
//this is used to sort device IDs
struct DevicePool {
HANDLE handle;
char name[4096];
bool operator<(const DevicePool &pool) const { return strcmp(name, pool.name) < 0; }
};
int main() {
//create an invisible window to act as a sink, capturing all WM_INPUT messages
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 = "RawInputClass";
wc.lpszMenuName = 0;
wc.style = CS_VREDRAW | CS_HREDRAW;
RegisterClass(&wc);
hwnd = CreateWindow("RawInputClass", "RawInputClass", WS_POPUP,
0, 0, 64, 64, 0, 0, GetModuleHandle(0), 0);
//enumerate all HID devices
unsigned devices = 0;
GetRawInputDeviceList(NULL, &devices, sizeof(RAWINPUTDEVICELIST));
RAWINPUTDEVICELIST *list = new RAWINPUTDEVICELIST[devices];
GetRawInputDeviceList(list, &devices, sizeof(RAWINPUTDEVICELIST));
//sort all devices by name. this has two important properties:
//1) it consistently orders peripherals, so mapped IDs remain constant
//2) it sorts the virtual keyboard and mouse to the bottom of the list
// (real devices start with \\?\HID#, virtual with \\?\Root#)
DevicePool pool[devices];
for(unsigned i = 0; i < devices; i++) {
pool[i].handle = list[i].hDevice;
unsigned size = sizeof(pool[i].name) - 1;
GetRawInputDeviceInfo(list[i].hDevice, RIDI_DEVICENAME, &pool[i].name, &size);
}
nall::sort(pool, devices);
delete[] list;
for(unsigned i = 0; i < devices; i++) {
RID_DEVICE_INFO info;
info.cbSize = sizeof(RID_DEVICE_INFO);
unsigned size = info.cbSize;
GetRawInputDeviceInfo(pool[i].handle, RIDI_DEVICEINFO, &info, &size);
if(info.dwType == RIM_TYPEKEYBOARD) {
unsigned n = lkeyboard.size();
lkeyboard[n].handle = pool[i].handle;
} else if(info.dwType == RIM_TYPEMOUSE) {
unsigned n = lmouse.size();
lmouse[n].handle = pool[i].handle;
} else if(info.dwType == RIM_TYPEHID) {
//if this is a gamepad or joystick device ...
if(info.hid.usUsagePage == 1 && (info.hid.usUsage == 4 || info.hid.usUsage == 5)) {
//... then cache device information for later use
unsigned n = lgamepad.size();
lgamepad[n].handle = pool[i].handle;
lgamepad[n].vendorId = (uint16_t)info.hid.dwVendorId;
lgamepad[n].productId = (uint16_t)info.hid.dwProductId;
//per MSDN: XInput devices have "IG_" in their device strings,
//which is how they should be identified.
const char *p = strstr(pool[i].name, "IG_");
lgamepad[n].isXInputDevice = (bool)p;
}
}
}
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);
}
return 0;
}
RawInput() : initialized(false), ready(false) {
}
};
static RawInput rawinput;
DWORD WINAPI RawInputThreadProc(void*) {
return rawinput.main();
}
LRESULT CALLBACK RawInputWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
return rawinput.window_proc(hwnd, msg, wparam, lparam);
}
class XInput {
public:
HMODULE libxinput;
DWORD WINAPI (*pXInputGetState)(DWORD, XINPUT_STATE*);
struct Gamepad {
unsigned id;
int16_t hat;
int16_t axis[6];
bool button[10];
void poll(XINPUT_STATE &state) {
hat = Joypad::HatCenter;
if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ) hat |= Joypad::HatUp;
if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) hat |= Joypad::HatRight;
if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) hat |= Joypad::HatDown;
if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) hat |= Joypad::HatLeft;
axis[0] = (int16_t)state.Gamepad.sThumbLX;
axis[1] = (int16_t)state.Gamepad.sThumbLY;
axis[2] = (int16_t)state.Gamepad.sThumbRX;
axis[3] = (int16_t)state.Gamepad.sThumbRY;
//transform left and right trigger ranges:
//from: 0 (low, eg released) to 255 (high, eg pressed all the way down)
//to: +32767 (low) to -32768 (high)
uint16_t triggerX = state.Gamepad.bLeftTrigger;
uint16_t triggerY = state.Gamepad.bRightTrigger;
triggerX = (triggerX << 8) | triggerX;
triggerY = (triggerY << 8) | triggerY;
axis[4] = (~triggerX) - 32768;
axis[5] = (~triggerY) - 32768;
button[0] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_A);
button[1] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_B);
button[2] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_X);
button[3] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y);
button[4] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK);
button[5] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_START);
button[6] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER);
button[7] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER);
button[8] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB);
button[9] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB);
}
Gamepad() {
hat = Joypad::HatCenter;
for(unsigned n = 0; n < 6; n++) axis[n] = 0;
for(unsigned n = 0; n < 10; n++) button[n] = false;
}
};
linear_vector<Gamepad> lgamepad;
void poll() {
if(!pXInputGetState) return;
for(unsigned i = 0; i < lgamepad.size(); i++) {
XINPUT_STATE state;
DWORD result = pXInputGetState(lgamepad[i].id, &state);
if(result == ERROR_SUCCESS) lgamepad[i].poll(state);
}
}
void init() {
if(!pXInputGetState) return;
//XInput only supports up to four controllers
for(unsigned i = 0; i <= 3; i++) {
XINPUT_STATE state;
DWORD result = pXInputGetState(i, &state);
if(result == ERROR_SUCCESS) {
//valid controller detected, add to gamepad list
unsigned n = lgamepad.size();
lgamepad[n].id = i;
}
}
}
XInput() : pXInputGetState(0) {
//bind xinput1 dynamically, as it does not ship with Windows Vista or below
libxinput = LoadLibraryA("xinput1_3.dll");
if(!libxinput) libxinput = LoadLibraryA("xinput1_2.dll");
if(!libxinput) libxinput = LoadLibraryA("xinput1_1.dll");
if(!libxinput) return;
pXInputGetState = (DWORD WINAPI (*)(DWORD, XINPUT_STATE*))GetProcAddress(libxinput, "XInputGetState");
}
~XInput() {
if(libxinput) FreeLibrary(libxinput);
}
};
static BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE*, void*);
static BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE*, void*);
class DirectInput {
public:
HWND handle;
LPDIRECTINPUT8 context;
struct Gamepad {
LPDIRECTINPUTDEVICE8 handle;
int16_t hat[4];
int16_t axis[6];
bool button[128];
void poll(DIJOYSTATE2 &state) {
//POV hats
for(unsigned n = 0; n < 4; n++) {
hat[n] = Joypad::HatCenter;
//POV value is in clockwise-hundredth degree units
unsigned pov = state.rgdwPOV[n];
//some drivers report a centered POV hat as -1U, others as 65535U.
//>= 36000 will match both, as well as invalid ranges.
if(pov >= 36000) continue;
if(pov >= 31500 || pov <= 4500) hat[n] |= Joypad::HatUp;
if(pov >= 4500 && pov <= 13500) hat[n] |= Joypad::HatRight;
if(pov >= 13500 && pov <= 22500) hat[n] |= Joypad::HatDown;
if(pov >= 22500 && pov <= 31500) hat[n] |= Joypad::HatLeft;
}
//axes
axis[0] = state.lX;
axis[1] = state.lY;
axis[2] = state.lZ;
axis[3] = state.lRx;
axis[4] = state.lRy;
axis[5] = state.lRz;
//buttons
for(unsigned n = 0; n < 128; n++) {
button[n] = (bool)state.rgbButtons[n];
}
}
Gamepad() {
handle = 0;
for(unsigned n = 0; n < 4; n++) hat[n] = Joypad::HatCenter;
for(unsigned n = 0; n < 6; n++) axis[n] = 0;
for(unsigned n = 0; n < 128; n++) button[n] = false;
}
};
linear_vector<Gamepad> lgamepad;
void poll() {
for(unsigned i = 0; i < lgamepad.size(); i++) {
if(FAILED(lgamepad[i].handle->Poll())) {
lgamepad[i].handle->Acquire();
continue;
}
DIJOYSTATE2 state;
lgamepad[i].handle->GetDeviceState(sizeof(DIJOYSTATE2), &state);
lgamepad[i].poll(state);
}
}
bool init_joypad(const DIDEVICEINSTANCE *instance) {
//if this is an XInput device, do not acquire it via DirectInput ...
//the XInput driver above will handle said device.
for(unsigned i = 0; i < rawinput.lgamepad.size(); i++) {
uint32_t guid = MAKELONG(rawinput.lgamepad[i].vendorId, rawinput.lgamepad[i].productId);
if(guid == instance->guidProduct.Data1) {
if(rawinput.lgamepad[i].isXInputDevice == true) {
return DIENUM_CONTINUE;
}
}
}
if(FAILED(context->CreateDevice(instance->guidInstance, &device, 0))) {
return DIENUM_CONTINUE;
}
device->SetDataFormat(&c_dfDIJoystick2);
device->SetCooperativeLevel(handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
device->EnumObjects(DirectInput_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS);
unsigned n = lgamepad.size();
lgamepad[n].handle = device;
return DIENUM_CONTINUE;
}
bool init_axis(const DIDEVICEOBJECTINSTANCE *instance) {
DIPROPRANGE range;
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;
}
void init(HWND handle_) {
handle = handle_;
DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&context, 0);
context->EnumDevices(DI8DEVCLASS_GAMECTRL, DirectInput_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY);
}
void term() {
for(unsigned i = 0; i < lgamepad.size(); i++) {
lgamepad[i].handle->Unacquire();
lgamepad[i].handle->Release();
}
lgamepad.reset();
if(context) {
context->Release();
context = 0;
}
}
private:
LPDIRECTINPUTDEVICE8 device;
};
BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE *instance, void *p) {
return ((DirectInput*)p)->init_joypad(instance);
}
BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE *instance, void *p) {
return ((DirectInput*)p)->init_axis(instance);
}
class pInputRaw {
public:
XInput xinput;
DirectInput dinput;
bool acquire_mouse;
bool cursor_visible;
struct {
HWND handle;
} settings;
bool cap(const string& name) {
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;
}
any get(const string& name) {
if(name == Input::Handle) return (uintptr_t)settings.handle;
return false;
}
bool set(const string& name, const any& value) {
if(name == Input::Handle) {
settings.handle = (HWND)any_cast<uintptr_t>(value);
return true;
}
return false;
}
bool acquire() {
acquire_mouse = true;
if(cursor_visible == true) {
ShowCursor(cursor_visible = false);
}
return acquired();
}
bool unacquire() {
acquire_mouse = false;
ReleaseCapture();
ClipCursor(NULL);
if(cursor_visible == false) {
ShowCursor(cursor_visible = true);
}
return true;
}
bool acquired() {
if(acquire_mouse == true) {
SetFocus(settings.handle);
SetCapture(settings.handle);
RECT rc;
GetWindowRect(settings.handle, &rc);
ClipCursor(&rc);
}
return GetCapture() == settings.handle;
}
bool poll(int16_t *table) {
memset(table, 0, Scancode::Limit * sizeof(int16_t));
WaitForSingleObject(rawinput.mutex, INFINITE);
//=========
//Keyboards
//=========
for(unsigned i = 0; i < min(rawinput.lkeyboard.size(), (unsigned)Keyboard::Count); i++) {
for(unsigned n = 0; n < nall::Keyboard::Size; n++) {
table[keyboard(i).key(n)] = rawinput.lkeyboard[i].state[n];
}
}
//====
//Mice
//====
for(unsigned i = 0; i < min(rawinput.lmouse.size(), (unsigned)Mouse::Count); i++) {
table[mouse(i).axis(0)] = rawinput.lmouse[i].xDistance;
table[mouse(i).axis(1)] = rawinput.lmouse[i].yDistance;
table[mouse(i).axis(2)] = rawinput.lmouse[i].zDistance;
for(unsigned n = 0; n < min(5U, (unsigned)Mouse::Buttons); n++) {
table[mouse(i).button(n)] = (bool)(rawinput.lmouse[i].buttonState & (1 << n));
}
rawinput.lmouse[i].sync();
}
ReleaseMutex(rawinput.mutex);
unsigned joy = 0;
//==================
//XInput controllers
//==================
xinput.poll();
for(unsigned i = 0; i < xinput.lgamepad.size(); i++) {
if(joy >= Joypad::Count) break;
table[joypad(i).hat(0)] = xinput.lgamepad[i].hat;
for(unsigned axis = 0; axis < min(6U, (unsigned)Joypad::Axes); axis++) {
table[joypad(i).axis(axis)] = xinput.lgamepad[i].axis[axis];
}
for(unsigned button = 0; button < min(10U, (unsigned)Joypad::Buttons); button++) {
table[joypad(i).button(button)] = xinput.lgamepad[i].button[button];
}
}
//=======================
//DirectInput controllers
//=======================
dinput.poll();
for(unsigned i = 0; i < dinput.lgamepad.size(); i++) {
if(joy >= Joypad::Count) break;
for(unsigned hat = 0; hat < min(4U, (unsigned)Joypad::Hats); hat++) {
table[joypad(i).hat(hat)] = dinput.lgamepad[i].hat[hat];
}
for(unsigned axis = 0; axis < min(6U, (unsigned)Joypad::Axes); axis++) {
table[joypad(i).axis(axis)] = dinput.lgamepad[i].axis[axis];
}
for(unsigned button = 0; button < min(128U, (unsigned)Joypad::Buttons); button++) {
table[joypad(i).button(button)] = dinput.lgamepad[i].button[button];
}
}
return true;
}
bool init() {
//only spawn RawInput processing thread one time
if(rawinput.initialized == false) {
rawinput.initialized = true;
rawinput.mutex = CreateMutex(NULL, FALSE, NULL);
CreateThread(NULL, 0, RawInputThreadProc, 0, 0, NULL);
//RawInput device calibration needs to finish before initializing DirectInput;
//as it needs device GUIDs to distinguish XInput devices from ordinary joypads.
bool ready = false;
do {
Sleep(10);
WaitForSingleObject(rawinput.mutex, INFINITE);
ready = rawinput.ready;
ReleaseMutex(rawinput.mutex);
} while(ready == false);
}
xinput.init();
dinput.init(settings.handle);
acquire_mouse = false;
cursor_visible = true;
return true;
}
void term() {
unacquire();
dinput.term();
}
pInputRaw() {
}
};
DeclareInput(Raw)
};

230
ruby/input/sdl.cpp Executable file
View File

@@ -0,0 +1,230 @@
//================
//SDL input driver
//================
//Keyboard and mouse are controlled directly via Xlib,
//as SDL cannot capture input from windows it does not create itself.
//SDL is used only to handle joysticks / gamepads.
#include <SDL/SDL.h>
#include <sys/ipc.h>
#include <sys/shm.h>
namespace ruby {
struct pInputSDL {
#include "xlibkeys.hpp"
struct {
Display *display;
Window rootwindow;
Cursor InvisibleCursor;
SDL_Joystick *gamepad[Joypad::Count];
unsigned screenwidth, screenheight;
unsigned relativex, relativey;
bool mouseacquired;
//mouse device settings
int accel_numerator;
int accel_denominator;
int threshold;
} device;
struct {
uintptr_t handle;
} settings;
bool cap(const string& name) {
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;
}
any get(const string& name) {
if(name == Input::Handle) return (uintptr_t)settings.handle;
return false;
}
bool set(const string& name, const any &value) {
if(name == Input::Handle) {
settings.handle = any_cast<uintptr_t>(value);
return true;
}
return false;
}
bool acquire() {
if(acquired()) return true;
if(XGrabPointer(device.display, settings.handle, True, 0, GrabModeAsync, GrabModeAsync,
device.rootwindow, device.InvisibleCursor, CurrentTime) == GrabSuccess) {
//backup existing cursor acceleration settings
XGetPointerControl(device.display, &device.accel_numerator, &device.accel_denominator, &device.threshold);
//disable cursor acceleration
XChangePointerControl(device.display, True, False, 1, 1, 0);
//center cursor (so that first relative poll returns 0, 0 if mouse has not moved)
XWarpPointer(device.display, None, device.rootwindow, 0, 0, 0, 0, device.screenwidth / 2, device.screenheight / 2);
return device.mouseacquired = true;
} else {
return device.mouseacquired = false;
}
}
bool unacquire() {
if(acquired()) {
//restore cursor acceleration and release cursor
XChangePointerControl(device.display, True, True, device.accel_numerator, device.accel_denominator, device.threshold);
XUngrabPointer(device.display, CurrentTime);
device.mouseacquired = false;
}
return true;
}
bool acquired() {
return device.mouseacquired;
}
bool poll(int16_t *table) {
memset(table, 0, Scancode::Limit * sizeof(int16_t));
//========
//Keyboard
//========
x_poll(table);
//=====
//Mouse
//=====
Window root_return, child_return;
int root_x_return = 0, root_y_return = 0;
int win_x_return = 0, win_y_return = 0;
unsigned int mask_return = 0;
XQueryPointer(device.display, settings.handle,
&root_return, &child_return, &root_x_return, &root_y_return,
&win_x_return, &win_y_return, &mask_return);
if(acquired()) {
XWindowAttributes attributes;
XGetWindowAttributes(device.display, settings.handle, &attributes);
//absolute -> relative conversion
table[mouse(0).axis(0)] = (int16_t)(root_x_return - device.screenwidth / 2);
table[mouse(0).axis(1)] = (int16_t)(root_y_return - device.screenheight / 2);
if(table[mouse(0).axis(0)] != 0 || table[mouse(0).axis(1)] != 0) {
//if mouse movement occurred, re-center mouse for next poll
XWarpPointer(device.display, None, device.rootwindow, 0, 0, 0, 0, device.screenwidth / 2, device.screenheight / 2);
}
} else {
table[mouse(0).axis(0)] = (int16_t)(root_x_return - device.relativex);
table[mouse(0).axis(1)] = (int16_t)(root_y_return - device.relativey);
device.relativex = root_x_return;
device.relativey = root_y_return;
}
//manual device polling is limited to only five buttons ...
table[mouse(0).button(0)] = (bool)(mask_return & Button1Mask);
table[mouse(0).button(1)] = (bool)(mask_return & Button2Mask);
table[mouse(0).button(2)] = (bool)(mask_return & Button3Mask);
table[mouse(0).button(3)] = (bool)(mask_return & Button4Mask);
table[mouse(0).button(4)] = (bool)(mask_return & Button5Mask);
//=========
//Joypad(s)
//=========
SDL_JoystickUpdate();
for(unsigned i = 0; i < Joypad::Count; i++) {
if(!device.gamepad[i]) continue;
//POV hats
unsigned hats = min((unsigned)Joypad::Hats, SDL_JoystickNumHats(device.gamepad[i]));
for(unsigned hat = 0; hat < hats; hat++) {
uint8_t state = SDL_JoystickGetHat(device.gamepad[i], hat);
if(state & SDL_HAT_UP ) table[joypad(i).hat(hat)] |= Joypad::HatUp;
if(state & SDL_HAT_RIGHT) table[joypad(i).hat(hat)] |= Joypad::HatRight;
if(state & SDL_HAT_DOWN ) table[joypad(i).hat(hat)] |= Joypad::HatDown;
if(state & SDL_HAT_LEFT ) table[joypad(i).hat(hat)] |= Joypad::HatLeft;
}
//axes
unsigned axes = min((unsigned)Joypad::Axes, SDL_JoystickNumAxes(device.gamepad[i]));
for(unsigned axis = 0; axis < axes; axis++) {
table[joypad(i).axis(axis)] = (int16_t)SDL_JoystickGetAxis(device.gamepad[i], axis);
}
//buttons
for(unsigned button = 0; button < Joypad::Buttons; button++) {
table[joypad(i).button(button)] = (bool)SDL_JoystickGetButton(device.gamepad[i], button);
}
}
return true;
}
bool init() {
x_init();
SDL_InitSubSystem(SDL_INIT_JOYSTICK);
SDL_JoystickEventState(SDL_IGNORE);
device.display = XOpenDisplay(0);
device.rootwindow = DefaultRootWindow(device.display);
XWindowAttributes attributes;
XGetWindowAttributes(device.display, device.rootwindow, &attributes);
device.screenwidth = attributes.width;
device.screenheight = attributes.height;
//Xlib: "because XShowCursor(false) would be too easy."
//create a fully transparent cursor named InvisibleCursor,
//for use while acquire() / XGrabPointer() is active.
Pixmap pixmap;
XColor black, unused;
static char invisible_data[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
Colormap colormap = DefaultColormap(device.display, DefaultScreen(device.display));
XAllocNamedColor(device.display, colormap, "black", &black, &unused);
pixmap = XCreateBitmapFromData(device.display, settings.handle, invisible_data, 8, 8);
device.InvisibleCursor = XCreatePixmapCursor(device.display, pixmap, pixmap, &black, &black, 0, 0);
XFreePixmap(device.display, pixmap);
XFreeColors(device.display, colormap, &black.pixel, 1, 0);
device.mouseacquired = false;
device.relativex = 0;
device.relativey = 0;
unsigned joypads = min((unsigned)Joypad::Count, SDL_NumJoysticks());
for(unsigned i = 0; i < joypads; i++) device.gamepad[i] = SDL_JoystickOpen(i);
return true;
}
void term() {
unacquire();
XFreeCursor(device.display, device.InvisibleCursor);
for(unsigned i = 0; i < Joypad::Count; i++) {
if(device.gamepad[i]) SDL_JoystickClose(device.gamepad[i]);
device.gamepad[i] = 0;
}
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
XCloseDisplay(device.display);
}
pInputSDL() {
for(unsigned i = 0; i < Joypad::Count; i++) device.gamepad[i] = 0;
settings.handle = 0;
}
};
DeclareInput(SDL)
};

50
ruby/input/x.cpp Executable file
View File

@@ -0,0 +1,50 @@
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
namespace ruby {
class pInputX {
public:
Display *display;
#include "xlibkeys.hpp"
bool cap(const string& name) {
if(name == Input::KeyboardSupport) return true;
return false;
}
any get(const string& name) {
return false;
}
bool set(const string& name, const any &value) {
return false;
}
bool acquire() { return false; }
bool unacquire() { return false; }
bool acquired() { return false; }
bool poll(int16_t *table) {
memset(table, 0, Scancode::Limit * sizeof(int16_t));
x_poll(table);
return true;
}
bool init() {
x_init();
display = XOpenDisplay(0);
return true;
}
void term() {
XCloseDisplay(display);
}
};
DeclareInput(X)
};

264
ruby/input/xlibkeys.hpp Executable file
View File

@@ -0,0 +1,264 @@
uint8_t scancode[256];
enum XScancode {
Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
ScrollLock, Pause, Tilde,
Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Num0,
Dash, Equal, Backspace,
Insert, Delete, Home, End, PageUp, PageDown,
A, B, C, D, E, F, G, H, I, J, K, L, M,
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
LeftBracket, RightBracket, Backslash, Semicolon, Apostrophe, Comma, Period, Slash,
Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0,
Point, Enter, Add, Subtract, Multiply, Divide,
Up, Down, Left, Right,
Tab, Return, Spacebar, Menu,
LeftShift, RightShift, LeftControl, RightControl, LeftAlt, RightAlt, LeftSuper, RightSuper,
};
void x_poll(int16_t *table) {
char state[32];
Display *display = XOpenDisplay(0);
XQueryKeymap(display, state);
XCloseDisplay(display);
#define key(id) table[keyboard(0)[id]]
#define pressed(id) (bool)(state[scancode[id] >> 3] & (1 << (scancode[id] & 7)))
key(Keyboard::Escape) = pressed(Escape);
key(Keyboard::F1) = pressed(F1);
key(Keyboard::F2) = pressed(F2);
key(Keyboard::F3) = pressed(F3);
key(Keyboard::F4) = pressed(F4);
key(Keyboard::F5) = pressed(F5);
key(Keyboard::F6) = pressed(F6);
key(Keyboard::F7) = pressed(F7);
key(Keyboard::F8) = pressed(F8);
key(Keyboard::F9) = pressed(F9);
key(Keyboard::F10) = pressed(F10);
key(Keyboard::F11) = pressed(F11);
key(Keyboard::F12) = pressed(F12);
key(Keyboard::ScrollLock) = pressed(ScrollLock);
key(Keyboard::Pause) = pressed(Pause);
key(Keyboard::Tilde) = pressed(Tilde);
key(Keyboard::Num1) = pressed(Num1);
key(Keyboard::Num2) = pressed(Num2);
key(Keyboard::Num3) = pressed(Num3);
key(Keyboard::Num4) = pressed(Num4);
key(Keyboard::Num5) = pressed(Num5);
key(Keyboard::Num6) = pressed(Num6);
key(Keyboard::Num7) = pressed(Num7);
key(Keyboard::Num8) = pressed(Num8);
key(Keyboard::Num9) = pressed(Num9);
key(Keyboard::Num0) = pressed(Num0);
key(Keyboard::Dash) = pressed(Dash);
key(Keyboard::Equal) = pressed(Equal);
key(Keyboard::Backspace) = pressed(Backspace);
key(Keyboard::Insert) = pressed(Insert);
key(Keyboard::Delete) = pressed(Delete);
key(Keyboard::Home) = pressed(Home);
key(Keyboard::End) = pressed(End);
key(Keyboard::PageUp) = pressed(PageUp);
key(Keyboard::PageDown) = pressed(PageDown);
key(Keyboard::A) = pressed(A);
key(Keyboard::B) = pressed(B);
key(Keyboard::C) = pressed(C);
key(Keyboard::D) = pressed(D);
key(Keyboard::E) = pressed(E);
key(Keyboard::F) = pressed(F);
key(Keyboard::G) = pressed(G);
key(Keyboard::H) = pressed(H);
key(Keyboard::I) = pressed(I);
key(Keyboard::J) = pressed(J);
key(Keyboard::K) = pressed(K);
key(Keyboard::L) = pressed(L);
key(Keyboard::M) = pressed(M);
key(Keyboard::N) = pressed(N);
key(Keyboard::O) = pressed(O);
key(Keyboard::P) = pressed(P);
key(Keyboard::Q) = pressed(Q);
key(Keyboard::R) = pressed(R);
key(Keyboard::S) = pressed(S);
key(Keyboard::T) = pressed(T);
key(Keyboard::U) = pressed(U);
key(Keyboard::V) = pressed(V);
key(Keyboard::W) = pressed(W);
key(Keyboard::X) = pressed(X);
key(Keyboard::Y) = pressed(Y);
key(Keyboard::Z) = pressed(Z);
key(Keyboard::LeftBracket) = pressed(LeftBracket);
key(Keyboard::RightBracket) = pressed(RightBracket);
key(Keyboard::Backslash) = pressed(Backslash);
key(Keyboard::Semicolon) = pressed(Semicolon);
key(Keyboard::Apostrophe) = pressed(Apostrophe);
key(Keyboard::Comma) = pressed(Comma);
key(Keyboard::Period) = pressed(Period);
key(Keyboard::Slash) = pressed(Slash);
key(Keyboard::Keypad1) = pressed(Keypad1);
key(Keyboard::Keypad2) = pressed(Keypad2);
key(Keyboard::Keypad3) = pressed(Keypad3);
key(Keyboard::Keypad4) = pressed(Keypad4);
key(Keyboard::Keypad5) = pressed(Keypad5);
key(Keyboard::Keypad6) = pressed(Keypad6);
key(Keyboard::Keypad7) = pressed(Keypad7);
key(Keyboard::Keypad8) = pressed(Keypad8);
key(Keyboard::Keypad9) = pressed(Keypad9);
key(Keyboard::Keypad0) = pressed(Keypad0);
key(Keyboard::Point) = pressed(Point);
key(Keyboard::Enter) = pressed(Enter);
key(Keyboard::Add) = pressed(Add);
key(Keyboard::Subtract) = pressed(Subtract);
key(Keyboard::Multiply) = pressed(Multiply);
key(Keyboard::Divide) = pressed(Divide);
key(Keyboard::Up) = pressed(Up);
key(Keyboard::Down) = pressed(Down);
key(Keyboard::Left) = pressed(Left);
key(Keyboard::Right) = pressed(Right);
key(Keyboard::Tab) = pressed(Tab);
key(Keyboard::Return) = pressed(Return);
key(Keyboard::Spacebar) = pressed(Spacebar);
key(Keyboard::Menu) = pressed(Menu);
key(Keyboard::Shift) = pressed(LeftShift) || pressed(RightShift);
key(Keyboard::Control) = pressed(LeftControl) || pressed(RightControl);
key(Keyboard::Alt) = pressed(LeftAlt) || pressed(RightAlt);
key(Keyboard::Super) = pressed(LeftSuper) || pressed(RightSuper);
#undef key
#undef pressed
}
void x_init() {
Display *display = XOpenDisplay(0);
memset(&scancode, 0, sizeof scancode);
#define assign(x, y) scancode[x] = XKeysymToKeycode(display, y)
assign(Escape, XK_Escape);
assign(F1, XK_F1);
assign(F2, XK_F2);
assign(F3, XK_F3);
assign(F4, XK_F4);
assign(F5, XK_F5);
assign(F6, XK_F6);
assign(F7, XK_F7);
assign(F8, XK_F8);
assign(F9, XK_F9);
assign(F10, XK_F10);
assign(F11, XK_F11);
assign(F12, XK_F12);
assign(ScrollLock, XK_Scroll_Lock);
assign(Pause, XK_Pause);
assign(Tilde, XK_asciitilde);
assign(Num0, XK_0);
assign(Num1, XK_1);
assign(Num2, XK_2);
assign(Num3, XK_3);
assign(Num4, XK_4);
assign(Num5, XK_5);
assign(Num6, XK_6);
assign(Num7, XK_7);
assign(Num8, XK_8);
assign(Num9, XK_9);
assign(Dash, XK_minus);
assign(Equal, XK_equal);
assign(Backspace, XK_BackSpace);
assign(Insert, XK_Insert);
assign(Delete, XK_Delete);
assign(Home, XK_Home);
assign(End, XK_End);
assign(PageUp, XK_Prior);
assign(PageDown, XK_Next);
assign(A, XK_A);
assign(B, XK_B);
assign(C, XK_C);
assign(D, XK_D);
assign(E, XK_E);
assign(F, XK_F);
assign(G, XK_G);
assign(H, XK_H);
assign(I, XK_I);
assign(J, XK_J);
assign(K, XK_K);
assign(L, XK_L);
assign(M, XK_M);
assign(N, XK_N);
assign(O, XK_O);
assign(P, XK_P);
assign(Q, XK_Q);
assign(R, XK_R);
assign(S, XK_S);
assign(T, XK_T);
assign(U, XK_U);
assign(V, XK_V);
assign(W, XK_W);
assign(X, XK_X);
assign(Y, XK_Y);
assign(Z, XK_Z);
assign(LeftBracket, XK_bracketleft);
assign(RightBracket, XK_bracketright);
assign(Backslash, XK_backslash);
assign(Semicolon, XK_semicolon);
assign(Apostrophe, XK_apostrophe);
assign(Comma, XK_comma);
assign(Period, XK_period);
assign(Slash, XK_slash);
assign(Keypad0, XK_KP_0);
assign(Keypad1, XK_KP_1);
assign(Keypad2, XK_KP_2);
assign(Keypad3, XK_KP_3);
assign(Keypad4, XK_KP_4);
assign(Keypad5, XK_KP_5);
assign(Keypad6, XK_KP_6);
assign(Keypad7, XK_KP_7);
assign(Keypad8, XK_KP_8);
assign(Keypad9, XK_KP_9);
assign(Add, XK_KP_Add);
assign(Subtract, XK_KP_Subtract);
assign(Multiply, XK_KP_Multiply);
assign(Divide, XK_KP_Divide);
assign(Enter, XK_KP_Enter);
assign(Up, XK_Up);
assign(Down, XK_Down);
assign(Left, XK_Left);
assign(Right, XK_Right);
assign(Tab, XK_Tab);
assign(Return, XK_Return);
assign(Spacebar, XK_space);
assign(LeftControl, XK_Control_L);
assign(RightControl, XK_Control_R);
assign(LeftAlt, XK_Alt_L);
assign(RightAlt, XK_Alt_R);
assign(LeftShift, XK_Shift_L);
assign(RightShift, XK_Shift_R);
assign(LeftSuper, XK_Super_L);
assign(RightSuper, XK_Super_R);
assign(Menu, XK_Menu);
#undef assign
XCloseDisplay(display);
}

370
ruby/ruby.cpp Executable file
View File

@@ -0,0 +1,370 @@
#include <ruby/ruby.hpp>
using namespace nall;
#include <ruby/ruby_impl.cpp>
namespace ruby {
VideoInterface video;
AudioInterface audio;
InputInterface input;
/* VideoInterface */
const char *Video::Handle = "Handle";
const char *Video::Synchronize = "Synchronize";
const char *Video::Filter = "Filter";
const char *Video::FragmentShader = "FragmentShader";
const char *Video::VertexShader = "VertexShader";
void VideoInterface::driver(const char *driver) {
if(p) term();
if(!driver || !*driver) driver = default_driver();
if(0);
#ifdef VIDEO_DIRECT3D
else if(!strcmp(driver, "Direct3D")) p = new VideoD3D();
#endif
#ifdef VIDEO_DIRECTDRAW
else if(!strcmp(driver, "DirectDraw")) p = new VideoDD();
#endif
#ifdef VIDEO_GDI
else if(!strcmp(driver, "GDI")) p = new VideoGDI();
#endif
#ifdef VIDEO_GLX
else if(!strcmp(driver, "OpenGL")) p = new VideoGLX();
#endif
#ifdef VIDEO_QTOPENGL
else if(!strcmp(driver, "Qt-OpenGL")) p = new VideoQtOpenGL();
#endif
#ifdef VIDEO_QTRASTER
else if(!strcmp(driver, "Qt-Raster")) p = new VideoQtRaster();
#endif
#ifdef VIDEO_SDL
else if(!strcmp(driver, "SDL")) p = new VideoSDL();
#endif
#ifdef VIDEO_WGL
else if(!strcmp(driver, "OpenGL")) p = new VideoWGL();
#endif
#ifdef VIDEO_XV
else if(!strcmp(driver, "X-Video")) p = new VideoXv();
#endif
else p = new Video();
}
//select the *safest* available driver, not the fastest
const char* VideoInterface::default_driver() {
#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_QTOPENGL)
return "Qt-OpenGL";
#elif defined(VIDEO_QTRASTER)
return "Qt-Raster";
#elif defined(VIDEO_SDL)
return "SDL";
#elif defined(VIDEO_XV)
return "X-Video";
#elif defined(VIDEO_GLX)
return "OpenGL";
#else
return "None";
#endif
}
//returns list of available drivers, sorted from most to least optimal
const char* VideoInterface::driver_list() {
return
//Windows
#if defined(VIDEO_DIRECT3D)
"Direct3D;"
#endif
#if defined(VIDEO_WGL)
"OpenGL;"
#endif
#if defined(VIDEO_DIRECTDRAW)
"DirectDraw;"
#endif
#if defined(VIDEO_GDI)
"GDI;"
#endif
//Linux
#if defined(VIDEO_GLX)
"OpenGL;"
#endif
#if defined(VIDEO_QTOPENGL)
"Qt-OpenGL;"
#endif
#if defined(VIDEO_XV)
"X-Video;"
#endif
#if defined(VIDEO_QTRASTER)
"Qt-Raster;"
#endif
#if defined(VIDEO_SDL)
"SDL;"
#endif
"None";
}
bool VideoInterface::init() {
if(!p) driver();
return p->init();
}
void VideoInterface::term() {
if(p) {
delete p;
p = 0;
}
}
bool VideoInterface::cap(const string& name) { return p ? p->cap(name) : false; }
any VideoInterface::get(const string& name) { return p ? p->get(name) : false; }
bool VideoInterface::set(const string& name, const any& value) { return p ? p->set(name, value) : false; }
bool VideoInterface::lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { return p ? p->lock(data, pitch, width, height) : false; }
void VideoInterface::unlock() { if(p) p->unlock(); }
void VideoInterface::clear() { if(p) p->clear(); }
void VideoInterface::refresh() { if(p) p->refresh(); }
VideoInterface::VideoInterface() : p(0) {}
VideoInterface::~VideoInterface() { term(); }
/* AudioInterface */
void AudioInterface::driver(const char *driver) {
if(p) term();
if(!driver || !*driver) driver = default_driver();
if(0);
#ifdef AUDIO_ALSA
else if(!strcmp(driver, "ALSA")) p = new AudioALSA();
#endif
#ifdef AUDIO_AO
else if(!strcmp(driver, "libao")) p = new AudioAO();
#endif
#ifdef AUDIO_DIRECTSOUND
else if(!strcmp(driver, "DirectSound")) p = new AudioDS();
#endif
#ifdef AUDIO_OPENAL
else if(!strcmp(driver, "OpenAL")) p = new AudioOpenAL();
#endif
#ifdef AUDIO_OSS
else if(!strcmp(driver, "OSS")) p = new AudioOSS();
#endif
#ifdef AUDIO_PULSEAUDIO
else if(!strcmp(driver, "PulseAudio")) p = new AudioPulseAudio();
#endif
#ifdef AUDIO_PULSEAUDIOSIMPLE
else if(!strcmp(driver, "PulseAudioSimple")) p = new AudioPulseAudioSimple();
#endif
else p = new Audio();
}
//select the *safest* available driver, not the fastest
const char* AudioInterface::default_driver() {
#if defined(AUDIO_DIRECTSOUND)
return "DirectSound";
#elif defined(AUDIO_ALSA)
return "ALSA";
#elif defined(AUDIO_OPENAL)
return "OpenAL";
#elif defined(AUDIO_PULSEAUDIO)
return "PulseAudio";
#elif defined(AUDIO_PULSEAUDIOSIMPLE)
return "PulseAudioSimple";
#elif defined(AUDIO_AO)
return "libao";
#elif defined(AUDIO_OSS)
return "OSS";
#else
return "None";
#endif
}
//returns list of available drivers, sorted from most to least optimal
const char* AudioInterface::driver_list() {
return
//Windows
#if defined(AUDIO_DIRECTSOUND)
"DirectSound;"
#endif
//Linux
#if defined(AUDIO_ALSA)
"ALSA;"
#endif
#if defined(AUDIO_OPENAL)
"OpenAL;"
#endif
#if defined(AUDIO_OSS)
"OSS;"
#endif
#if defined(AUDIO_PULSEAUDIO)
"PulseAudio;"
#endif
#if defined(AUDIO_PULSEAUDIOSIMPLE)
"PulseAudioSimple;"
#endif
#if defined(AUDIO_AO)
"libao;"
#endif
"None";
}
#include "ruby_audio.cpp"
/* InputInterface */
const char *Input::Handle = "Handle";
const char *Input::KeyboardSupport = "KeyboardSupport";
const char *Input::MouseSupport = "MouseSupport";
const char *Input::JoypadSupport = "JoypadSupport";
void InputInterface::driver(const char *driver) {
if(p) term();
if(!driver || !*driver) driver = default_driver();
if(0);
#ifdef INPUT_DIRECTINPUT
else if(!strcmp(driver, "DirectInput")) p = new InputDI();
#endif
#ifdef INPUT_RAWINPUT
else if(!strcmp(driver, "RawInput")) p = new InputRaw();
#endif
#ifdef INPUT_SDL
else if(!strcmp(driver, "SDL")) p = new InputSDL();
#endif
#ifdef INPUT_X
else if(!strcmp(driver, "X-Windows")) p = new InputX();
#endif
#ifdef INPUT_CARBON
else if(!strcmp(driver, "Carbon")) p = new InputCarbon();
#endif
else p = new Input();
}
//select the *safest* available driver, not the fastest
const char* InputInterface::default_driver() {
#if defined(INPUT_RAWINPUT)
return "RawInput";
#elif defined(INPUT_DIRECTINPUT)
return "DirectInput";
#elif defined(INPUT_SDL)
return "SDL";
#elif defined(INPUT_X)
return "X-Windows";
#elif defined(INPUT_CARBON)
return "Carbon";
#else
return "none";
#endif
}
const char* InputInterface::driver_list() {
return
//Windows
#if defined(INPUT_RAWINPUT)
"RawInput;"
#endif
#if defined(INPUT_DIRECTINPUT)
"DirectInput;"
#endif
//Linux
#if defined(INPUT_SDL)
"SDL;"
#endif
#if defined(INPUT_X)
"X-Windows;"
#endif
//OS X
#if defined(INPUT_CARBON)
"Carbon;"
#endif
"None";
}
bool InputInterface::init() {
if(!p) driver();
return p->init();
}
void InputInterface::term() {
if(p) {
delete p;
p = 0;
}
}
bool InputInterface::cap(const string& name) { return p ? p->cap(name) : false; }
any InputInterface::get(const string& name) { return p ? p->get(name) : false; }
bool InputInterface::set(const string& name, const any& value) { return p ? p->set(name, value) : false; }
bool InputInterface::acquire() { return p ? p->acquire() : false; }
bool InputInterface::unacquire() { return p ? p->unacquire() : false; }
bool InputInterface::acquired() { return p ? p->acquired() : false; }
bool InputInterface::poll(int16_t *table) { return p ? p->poll(table) : false; }
InputInterface::InputInterface() : p(0) {}
InputInterface::~InputInterface() { term(); }
};

109
ruby/ruby.hpp Executable file
View File

@@ -0,0 +1,109 @@
/*
ruby
version: 0.06 (2009-05-22)
license: public domain
*/
#ifndef RUBY_H
#define RUBY_H
#include <nall/algorithm.hpp>
#include <nall/any.hpp>
#include <nall/array.hpp>
#include <nall/bit.hpp>
#include <nall/detect.hpp>
#include <nall/input.hpp>
#include <nall/sort.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
#include <nall/vector.hpp>
namespace ruby {
#include <ruby/video.hpp>
#include <ruby/audio.hpp>
#include <ruby/input.hpp>
class VideoInterface {
public:
void driver(const char *driver = "");
const char* default_driver();
const char* driver_list();
bool init();
void term();
bool cap(const nall::string& name);
nall::any get(const nall::string& name);
bool set(const nall::string& name, const nall::any& value);
bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height);
void unlock();
void clear();
void refresh();
VideoInterface();
~VideoInterface();
private:
Video *p;
};
class AudioInterface {
public:
void driver(const char *driver = "");
const char* default_driver();
const char* driver_list();
bool init();
void term();
bool cap(const nall::string& name);
nall::any get(const nall::string& name);
bool set(const nall::string& name, const nall::any& value);
void sample(uint16_t left, uint16_t right);
void clear();
AudioInterface();
~AudioInterface();
private:
Audio *p;
unsigned volume;
//resample unit
double hermite(double mu, double a, double b, double c, double d);
bool resample_enabled;
double r_step, r_frac;
int r_left[4], r_right[4];
};
class InputInterface {
public:
void driver(const char *driver = "");
const char* default_driver();
const char* driver_list();
bool init();
void term();
bool cap(const nall::string& name);
nall::any get(const nall::string& name);
bool set(const nall::string& name, const nall::any& value);
bool acquire();
bool unacquire();
bool acquired();
bool poll(int16_t *table);
InputInterface();
~InputInterface();
private:
Input *p;
};
extern VideoInterface video;
extern AudioInterface audio;
extern InputInterface input;
};
#endif

133
ruby/ruby_audio.cpp Executable file
View File

@@ -0,0 +1,133 @@
const char *Audio::Volume = "Volume";
const char *Audio::Resample = "Resample";
const char *Audio::ResampleRatio = "ResampleRatio";
const char *Audio::Handle = "Handle";
const char *Audio::Synchronize = "Synchronize";
const char *Audio::Frequency = "Frequency";
const char *Audio::Latency = "Latency";
bool AudioInterface::init() {
if(!p) driver();
return p->init();
}
void AudioInterface::term() {
if(p) {
delete p;
p = 0;
}
}
bool AudioInterface::cap(const string& name) {
if(name == Audio::Volume) return true;
if(name == Audio::Resample) return true;
if(name == Audio::ResampleRatio) return true;
return p ? p->cap(name) : false;
}
any AudioInterface::get(const string& name) {
if(name == Audio::Volume) return volume;
if(name == Audio::Resample) return resample_enabled;
if(name == Audio::ResampleRatio);
return p ? p->get(name) : false;
}
bool AudioInterface::set(const string& name, const any& value) {
if(name == Audio::Volume) {
volume = any_cast<unsigned>(value);
return true;
}
if(name == Audio::Resample) {
resample_enabled = any_cast<bool>(value);
return true;
}
if(name == Audio::ResampleRatio) {
r_step = any_cast<double>(value);
r_frac = 0;
return true;
}
return p ? p->set(name, value) : false;
}
//4-tap hermite interpolation
double AudioInterface::hermite(double mu1, double a, double b, double c, double d) {
const double tension = 0.0; //-1 = low, 0 = normal, 1 = high
const double bias = 0.0; //-1 = left, 0 = even, 1 = right
double mu2, mu3, m0, m1, a0, a1, a2, a3;
mu2 = mu1 * mu1;
mu3 = mu2 * mu1;
m0 = (b - a) * (1 + bias) * (1 - tension) / 2;
m0 += (c - b) * (1 - bias) * (1 - tension) / 2;
m1 = (c - b) * (1 + bias) * (1 - tension) / 2;
m1 += (d - c) * (1 - bias) * (1 - tension) / 2;
a0 = +2 * mu3 - 3 * mu2 + 1;
a1 = mu3 - 2 * mu2 + mu1;
a2 = mu3 - mu2;
a3 = -2 * mu3 + 3 * mu2;
return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
}
void AudioInterface::sample(uint16_t left, uint16_t right) {
int s_left = (int16_t)left;
int s_right = (int16_t)right;
if(volume != 100) {
s_left = sclamp<16>((double)s_left * (double)volume / 100.0);
s_right = sclamp<16>((double)s_right * (double)volume / 100.0);
}
r_left [0] = r_left [1];
r_left [1] = r_left [2];
r_left [2] = r_left [3];
r_left [3] = s_left;
r_right[0] = r_right[1];
r_right[1] = r_right[2];
r_right[2] = r_right[3];
r_right[3] = s_right;
if(resample_enabled == false) {
if(p) p->sample(left, right);
return;
}
while(r_frac <= 1.0) {
int output_left = sclamp<16>(hermite(r_frac, r_left [0], r_left [1], r_left [2], r_left [3]));
int output_right = sclamp<16>(hermite(r_frac, r_right[0], r_right[1], r_right[2], r_right[3]));
r_frac += r_step;
if(p) p->sample(output_left, output_right);
}
r_frac -= 1.0;
}
void AudioInterface::clear() {
r_frac = 0;
r_left [0] = r_left [1] = r_left [2] = r_left [3] = 0;
r_right[0] = r_right[1] = r_right[2] = r_right[3] = 0;
if(p) p->clear();
}
AudioInterface::AudioInterface() {
p = 0;
volume = 100;
resample_enabled = false;
r_step = r_frac = 0;
r_left [0] = r_left [1] = r_left [2] = r_left [3] = 0;
r_right[0] = r_right[1] = r_right[2] = r_right[3] = 0;
}
AudioInterface::~AudioInterface() {
term();
}

178
ruby/ruby_impl.cpp Executable file
View File

@@ -0,0 +1,178 @@
/* Global Headers */
#if defined(VIDEO_QTOPENGL) || defined(VIDEO_QTRASTER)
#include <QApplication>
#include <QtGui>
#endif
#if defined(VIDEO_QTOPENGL)
#include <QGLWidget>
#if defined(PLATFORM_WIN)
#include <GL/glext.h>
#endif
#endif
#if defined(PLATFORM_X)
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#elif defined(PLATFORM_OSX)
#define __INTEL_COMPILER
#include <Carbon/Carbon.h>
#elif defined(PLATFORM_WIN)
#define _WIN32_WINNT 0x0501
#include <windows.h>
#endif
/* Video */
#define DeclareVideo(Name) \
class Video##Name : public Video { \
public: \
bool cap(const string& name) { return p.cap(name); } \
any get(const string& name) { return p.get(name); } \
bool set(const string& name, const any& value) { return p.set(name, value); } \
\
bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { return p.lock(data, pitch, width, height); } \
void unlock() { p.unlock(); } \
\
void clear() { p.clear(); } \
void refresh() { p.refresh(); } \
bool init() { return p.init(); } \
void term() { p.term(); } \
\
Video##Name() : p(*new pVideo##Name) {} \
~Video##Name() { delete &p; } \
\
private: \
pVideo##Name &p; \
};
#ifdef VIDEO_DIRECT3D
#include <ruby/video/direct3d.cpp>
#endif
#ifdef VIDEO_DIRECTDRAW
#include <ruby/video/directdraw.cpp>
#endif
#ifdef VIDEO_GDI
#include <ruby/video/gdi.cpp>
#endif
#ifdef VIDEO_GLX
#include <ruby/video/glx.cpp>
#endif
#ifdef VIDEO_QTOPENGL
#include <ruby/video/qtopengl.cpp>
#endif
#ifdef VIDEO_QTRASTER
#include <ruby/video/qtraster.cpp>
#endif
#ifdef VIDEO_SDL
#include <ruby/video/sdl.cpp>
#endif
#ifdef VIDEO_WGL
#include <ruby/video/wgl.cpp>
#endif
#ifdef VIDEO_XV
#include <ruby/video/xv.cpp>
#endif
/* Audio */
#define DeclareAudio(Name) \
class Audio##Name : public Audio { \
public: \
bool cap(const string& name) { return p.cap(name); } \
any get(const string& name) { return p.get(name); } \
bool set(const string& name, const any& value) { return p.set(name, value); } \
\
void sample(uint16_t left, uint16_t right) { p.sample(left, right); } \
void clear() { p.clear(); } \
bool init() { return p.init(); } \
void term() { p.term(); } \
\
Audio##Name() : p(*new pAudio##Name) {} \
~Audio##Name() { delete &p; } \
\
private: \
pAudio##Name &p; \
};
#ifdef AUDIO_ALSA
#include <ruby/audio/alsa.cpp>
#endif
#ifdef AUDIO_AO
#include <ruby/audio/ao.cpp>
#endif
#ifdef AUDIO_DIRECTSOUND
#include <ruby/audio/directsound.cpp>
#endif
#ifdef AUDIO_OPENAL
#include <ruby/audio/openal.cpp>
#endif
#ifdef AUDIO_OSS
#include <ruby/audio/oss.cpp>
#endif
#ifdef AUDIO_PULSEAUDIO
#include <ruby/audio/pulseaudio.cpp>
#endif
#ifdef AUDIO_PULSEAUDIOSIMPLE
#include <ruby/audio/pulseaudiosimple.cpp>
#endif
/* Input */
#define DeclareInput(Name) \
class Input##Name : public Input { \
public: \
bool cap(const string& name) { return p.cap(name); } \
any get(const string& name) { return p.get(name); } \
bool set(const string& name, const any& value) { return p.set(name, value); } \
\
bool acquire() { return p.acquire(); } \
bool unacquire() { return p.unacquire(); } \
bool acquired() { return p.acquired(); } \
\
bool poll(int16_t *table) { return p.poll(table); } \
bool init() { return p.init(); } \
void term() { p.term(); } \
\
Input##Name() : p(*new pInput##Name) {} \
~Input##Name() { delete &p; } \
\
private: \
pInput##Name &p; \
};
#ifdef INPUT_DIRECTINPUT
#include <ruby/input/directinput.cpp>
#endif
#ifdef INPUT_RAWINPUT
#include <ruby/input/rawinput.cpp>
#endif
#ifdef INPUT_SDL
#include <ruby/input/sdl.cpp>
#endif
#ifdef INPUT_X
#include <ruby/input/x.cpp>
#endif
#ifdef INPUT_CARBON
#include <ruby/input/carbon.cpp>
#endif

28
ruby/video.hpp Executable file
View File

@@ -0,0 +1,28 @@
class Video {
public:
static const char *Handle;
static const char *Synchronize;
static const char *Filter;
static const char *FragmentShader;
static const char *VertexShader;
enum Filter {
FilterPoint,
FilterLinear,
};
virtual bool cap(const nall::string& name) { return false; }
virtual nall::any get(const nall::string& name) { return false; }
virtual bool set(const nall::string& name, const nall::any& value) { return false; }
virtual bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { return false; }
virtual void unlock() {}
virtual void clear() {}
virtual void refresh() {}
virtual bool init() { return true; }
virtual void term() {}
Video() {}
virtual ~Video() {}
};

453
ruby/video/direct3d.cpp Executable file
View File

@@ -0,0 +1,453 @@
#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*);
namespace ruby {
class pVideoD3D {
public:
LPDIRECT3D9 lpd3d;
LPDIRECT3DDEVICE9 device;
LPDIRECT3DVERTEXBUFFER9 vertex_buffer, *vertex_ptr;
D3DPRESENT_PARAMETERS presentation;
D3DSURFACE_DESC d3dsd;
D3DLOCKED_RECT d3dlr;
D3DRASTER_STATUS d3drs;
D3DCAPS9 d3dcaps;
LPDIRECT3DTEXTURE9 texture;
LPDIRECT3DSURFACE9 surface;
LPD3DXEFFECT effect;
string shaderSource;
bool lost;
unsigned iwidth, 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;
bool synchronize;
unsigned filter;
unsigned width;
unsigned height;
} settings;
struct {
unsigned width;
unsigned height;
} state;
bool cap(const string& name) {
if(name == Video::Handle) return true;
if(name == Video::Synchronize) return true;
if(name == Video::Filter) return true;
if(name == Video::FragmentShader) return true;
return false;
}
any get(const string& name) {
if(name == Video::Handle) return (uintptr_t)settings.handle;
if(name == Video::Synchronize) return settings.synchronize;
if(name == Video::Filter) return settings.filter;
return false;
}
bool set(const string& name, const any& value) {
if(name == Video::Handle) {
settings.handle = (HWND)any_cast<uintptr_t>(value);
return true;
}
if(name == Video::Synchronize) {
settings.synchronize = any_cast<bool>(value);
return true;
}
if(name == Video::Filter) {
settings.filter = any_cast<unsigned>(value);
if(lpd3d) update_filter();
return true;
}
if(name == Video::FragmentShader) {
set_fragment_shader(any_cast<const char*>(value));
return true;
}
return false;
}
bool recover() {
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;
}
unsigned rounded_power_of_two(unsigned n) {
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
return n + 1;
}
void resize(unsigned width, unsigned height) {
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);
}
void update_filter() {
if(!device) return;
if(lost && !recover()) return;
switch(settings.filter) { default:
case Video::FilterPoint: flags.filter = D3DTEXF_POINT; break;
case Video::FilterLinear: flags.filter = D3DTEXF_LINEAR; break;
}
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)
void 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
) {
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));
}
void clear() {
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();
}
//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);
}
}
bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) {
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;
}
void unlock() {
surface->UnlockRect();
surface->Release();
}
void refresh() {
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_fragment_shader(shaderSource);
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) {
while(true) {
D3DRASTER_STATUS status;
device->GetRasterStatus(0, &status);
if(status.InVBlank == true) break;
}
}
if(device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) lost = true;
}
void set_fragment_shader(const char *source) {
if(!caps.shader) return;
if(effect) {
effect->Release();
effect = NULL;
}
if(!source || !*source) {
shaderSource = "";
return;
}
shaderSource = source;
HMODULE d3dx;
for(unsigned i = 0; i < 256; i++) {
char t[256];
sprintf(t, "d3dx9_%u.dll", i);
d3dx = LoadLibrary(t);
if(d3dx) break;
}
if(!d3dx) d3dx = LoadLibrary("d3dx9.dll");
if(!d3dx) return;
EffectProc effectProc = (EffectProc)GetProcAddress(d3dx, "D3DXCreateEffect");
TextureProc textureProc = (TextureProc)GetProcAddress(d3dx, "D3DXCreateTextureFromFileA");
LPD3DXBUFFER pBufferErrors = NULL;
effectProc(device, shaderSource, lstrlen(source), NULL, NULL, NULL, NULL, &effect, &pBufferErrors);
D3DXHANDLE hTech;
effect->FindNextValidTechnique(NULL, &hTech);
effect->SetTechnique(hTech);
}
bool init() {
term();
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_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;
}
void release_resources() {
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; }
}
void term() {
release_resources();
if(device) { device->Release(); device = 0; }
if(lpd3d) { lpd3d->Release(); lpd3d = 0; }
}
pVideoD3D() {
effect = 0;
vertex_buffer = 0;
surface = 0;
texture = 0;
device = 0;
lpd3d = 0;
lost = true;
settings.handle = 0;
settings.synchronize = false;
settings.filter = Video::FilterLinear;
}
};
DeclareVideo(D3D)
};
#undef D3DVERTEX

186
ruby/video/directdraw.cpp Executable file
View File

@@ -0,0 +1,186 @@
#include <ddraw.h>
namespace ruby {
class pVideoDD {
public:
LPDIRECTDRAW lpdd;
LPDIRECTDRAW7 lpdd7;
LPDIRECTDRAWSURFACE7 screen, raster;
LPDIRECTDRAWCLIPPER clipper;
DDSURFACEDESC2 ddsd;
DDSCAPS2 ddscaps;
unsigned iwidth, iheight;
struct {
HWND handle;
bool synchronize;
unsigned width;
unsigned height;
} settings;
bool cap(const string& name) {
if(name == Video::Handle) return true;
if(name == Video::Synchronize) return true;
return false;
}
any get(const string& name) {
if(name == Video::Handle) return (uintptr_t)settings.handle;
if(name == Video::Synchronize) return settings.synchronize;
return false;
}
bool set(const string& name, const any& value) {
if(name == Video::Handle) {
settings.handle = (HWND)any_cast<uintptr_t>(value);
return true;
}
if(name == Video::Synchronize) {
settings.synchronize = any_cast<bool>(value);
return true;
}
return false;
}
void resize(unsigned width, unsigned height) {
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();
}
void clear() {
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);
}
bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) {
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;
}
void unlock() {
raster->Unlock(0);
}
void refresh() {
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();
}
}
bool init() {
term();
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;
}
void term() {
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; }
}
pVideoDD() {
lpdd = 0;
lpdd7 = 0;
screen = 0;
raster = 0;
clipper = 0;
settings.handle = 0;
}
};
DeclareVideo(DD)
};

100
ruby/video/gdi.cpp Executable file
View File

@@ -0,0 +1,100 @@
#include <assert.h>
namespace ruby {
class pVideoGDI {
public:
uint32_t *buffer;
HBITMAP bitmap;
HDC bitmapdc;
BITMAPINFO bmi;
struct {
HWND handle;
unsigned width;
unsigned height;
} settings;
bool cap(const string& name) {
if(name == Video::Handle) return true;
return false;
}
any get(const string& name) {
if(name == Video::Handle) return (uintptr_t)settings.handle;
return false;
}
bool set(const string& name, const any& value) {
if(name == Video::Handle) {
settings.handle = (HWND)any_cast<uintptr_t>(value);
return true;
}
return false;
}
bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) {
settings.width = width;
settings.height = height;
pitch = 1024 * 4;
return data = buffer;
}
void unlock() {}
void clear() {}
void refresh() {
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);
}
bool init() {
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;
}
void term() {
DeleteObject(bitmap);
DeleteDC(bitmapdc);
}
pVideoGDI() {
buffer = (uint32_t*)malloc(1024 * 1024 * sizeof(uint32_t));
settings.handle = 0;
}
~pVideoGDI() {
if(buffer) free(buffer);
}
};
DeclareVideo(GDI)
};

231
ruby/video/glx.cpp Executable file
View File

@@ -0,0 +1,231 @@
/*
video.glx
author: byuu
license: public domain
last updated: 2010-01-05
Design notes:
SGI's GLX is the X11/Xlib interface to OpenGL.
At the time of this writing, there are three relevant versions of the API: versions 1.2, 1.3 and 1.4.
Version 1.2 was released on March 4th, 1997.
Version 1.3 was released on October 19th, 1998.
Version 1.4 was released on December 16th, 2005.
Despite version 1.3 being roughly ten years old at this time, there are still many modern X11 GLX drivers
that lack full support for the specification. Most notable would be the official video drivers from ATI.
Given this, 1.4 support is pretty much hopeless to target.
Luckily, each version has been designed to be backwards compatible with the previous version. As well,
version 1.2 is wholly sufficient, albeit less convenient, to implement this video module.
Therefore, for the purpose of compatibility, this driver only uses GLX 1.2 or earlier API commands.
As well, it only uses raw Xlib API commands, so that it is compatible with any toolkit.
*/
#include "opengl.hpp"
namespace ruby {
//returns true once window is mapped (created and displayed onscreen)
static Bool glx_wait_for_map_notify(Display *d, XEvent *e, char *arg) {
return (e->type == MapNotify) && (e->xmap.window == (Window)arg);
}
class pVideoGLX : public OpenGL {
public:
int (*glSwapInterval)(int);
Display *display;
int screen;
Window xwindow;
Colormap colormap;
GLXContext glxcontext;
GLXWindow glxwindow;
struct {
int version_major, version_minor;
bool double_buffer;
bool is_direct;
} glx;
struct {
Window handle;
bool synchronize;
unsigned filter;
unsigned width;
unsigned height;
} settings;
bool cap(const string& name) {
if(name == Video::Handle) return true;
if(name == Video::Synchronize) return true;
if(name == Video::Filter) return true;
if(name == Video::FragmentShader) return true;
if(name == Video::VertexShader) return true;
return false;
}
any get(const string& name) {
if(name == Video::Handle) return (uintptr_t)settings.handle;
if(name == Video::Synchronize) return settings.synchronize;
if(name == Video::Filter) return settings.filter;
return false;
}
bool set(const string& name, const any& value) {
if(name == Video::Handle) {
settings.handle = any_cast<uintptr_t>(value);
return true;
}
if(name == Video::Synchronize) {
if(settings.synchronize != any_cast<bool>(value)) {
settings.synchronize = any_cast<bool>(value);
if(glSwapInterval) glSwapInterval(settings.synchronize);
return true;
}
}
if(name == Video::Filter) {
settings.filter = any_cast<unsigned>(value);
return true;
}
if(name == Video::FragmentShader) {
OpenGL::set_fragment_shader(any_cast<const char*>(value));
return true;
}
if(name == Video::VertexShader) {
OpenGL::set_vertex_shader(any_cast<const char*>(value));
return true;
}
return false;
}
bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) {
resize(width, height);
settings.width = width;
settings.height = height;
return OpenGL::lock(data, pitch);
}
void unlock() {
}
void clear() {
OpenGL::clear();
if(glx.double_buffer) glXSwapBuffers(display, glxwindow);
}
void refresh() {
//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);
}
OpenGL::refresh(settings.filter == Video::FilterLinear,
settings.width, settings.height, parent.width, parent.height);
if(glx.double_buffer) glXSwapBuffers(display, glxwindow);
}
bool init() {
term();
display = XOpenDisplay(0);
screen = DefaultScreen(display);
glXQueryVersion(display, &glx.version_major, &glx.version_minor);
//require GLX 1.2+ API
if(glx.version_major < 1 || (glx.version_major == 1 && glx.version_minor < 2)) return false;
XWindowAttributes window_attributes;
XGetWindowAttributes(display, settings.handle, &window_attributes);
//let GLX determine the best Visual to use for GL output; provide a few hints
//note: some video drivers will override double buffering attribute
int attributelist[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None };
XVisualInfo *vi = glXChooseVisual(display, screen, attributelist);
//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;
attributes.event_mask = StructureNotifyMask;
xwindow = XCreateWindow(display, /* parent = */ settings.handle,
/* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height,
/* border_width = */ 0, vi->depth, InputOutput, vi->visual,
CWColormap | CWBorderPixel | CWEventMask, &attributes);
XSetWindowBackground(display, xwindow, /* color = */ 0);
XMapWindow(display, xwindow);
XEvent event;
//window must be realized (appear onscreen) before we make the context current
XIfEvent(display, &event, glx_wait_for_map_notify, (char*)xwindow);
glxcontext = glXCreateContext(display, vi, /* sharelist = */ 0, /* direct = */ GL_TRUE);
glXMakeCurrent(display, glxwindow = xwindow, glxcontext);
//read attributes of frame buffer for later use, as requested attributes from above are not always granted
int value = 0;
glXGetConfig(display, vi, GLX_DOUBLEBUFFER, &value);
glx.double_buffer = value;
glx.is_direct = glXIsDirect(display, glxcontext);
OpenGL::init();
settings.width = 256;
settings.height = 256;
//vertical synchronization
if(!glSwapInterval) glSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalSGI");
if(!glSwapInterval) glSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalMESA");
if( glSwapInterval) glSwapInterval(settings.synchronize);
return true;
}
void term() {
OpenGL::term();
if(glxcontext) {
glXDestroyContext(display, glxcontext);
glxcontext = 0;
}
if(xwindow) {
XUnmapWindow(display, xwindow);
xwindow = 0;
}
if(colormap) {
XFreeColormap(display, colormap);
colormap = 0;
}
}
pVideoGLX() : glSwapInterval(0) {
settings.handle = 0;
settings.synchronize = false;
xwindow = 0;
colormap = 0;
glxcontext = 0;
glxwindow = 0;
}
~pVideoGLX() { term(); }
};
DeclareVideo(GLX)
};

225
ruby/video/opengl.hpp Executable file
View File

@@ -0,0 +1,225 @@
#include <GL/gl.h>
#if defined(PLATFORM_X)
#include <GL/glx.h>
#define glGetProcAddress(name) (*glXGetProcAddress)((const GLubyte*)(name))
#elif defined(PLATFORM_WIN)
#include <GL/glext.h>
#define glGetProcAddress(name) wglGetProcAddress(name)
#else
#error "ruby::OpenGL: unsupported platform"
#endif
PFNGLCREATEPROGRAMPROC glCreateProgram = 0;
PFNGLUSEPROGRAMPROC glUseProgram = 0;
PFNGLCREATESHADERPROC glCreateShader = 0;
PFNGLDELETESHADERPROC glDeleteShader = 0;
PFNGLSHADERSOURCEPROC glShaderSource = 0;
PFNGLCOMPILESHADERPROC glCompileShader = 0;
PFNGLATTACHSHADERPROC glAttachShader = 0;
PFNGLDETACHSHADERPROC glDetachShader = 0;
PFNGLLINKPROGRAMPROC glLinkProgram = 0;
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = 0;
PFNGLUNIFORM1IPROC glUniform1i = 0;
PFNGLUNIFORM2FVPROC glUniform2fv = 0;
PFNGLUNIFORM4FVPROC glUniform4fv = 0;
class OpenGL {
public:
GLuint gltexture;
GLuint glprogram;
GLuint fragmentshader;
GLuint vertexshader;
bool shader_support;
uint32_t *buffer;
unsigned iwidth, iheight;
void resize(unsigned width, unsigned height) {
if(iwidth >= width && iheight >= height) return;
if(gltexture) glDeleteTextures(1, &gltexture);
iwidth = max(width, iwidth );
iheight = max(height, iheight);
if(buffer) delete[] buffer;
buffer = new uint32_t[iwidth * iheight];
glGenTextures(1, &gltexture);
glBindTexture(GL_TEXTURE_2D, gltexture);
glPixelStorei(GL_UNPACK_ROW_LENGTH, iwidth);
glTexImage2D(GL_TEXTURE_2D,
/* mip-map level = */ 0, /* internal format = */ GL_RGB,
iwidth, iheight, /* border = */ 0, /* format = */ GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
}
bool lock(uint32_t *&data, unsigned &pitch) {
pitch = iwidth * sizeof(uint32_t);
return data = buffer;
}
void clear() {
memset(buffer, 0, iwidth * iheight * sizeof(uint32_t));
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}
void refresh(bool smooth, unsigned inwidth, unsigned inheight, unsigned outwidth, unsigned outheight) {
if(shader_support) {
glUseProgram(glprogram);
GLint location;
float inputSize[2] = { (float)inwidth, (float)inheight };
location = glGetUniformLocation(glprogram, "rubyInputSize");
glUniform2fv(location, 1, inputSize);
float outputSize[2] = { (float)outwidth, (float)outheight };
location = glGetUniformLocation(glprogram, "rubyOutputSize");
glUniform2fv(location, 1, outputSize);
float textureSize[2] = { (float)iwidth, (float)iheight };
location = glGetUniformLocation(glprogram, "rubyTextureSize");
glUniform2fv(location, 1, textureSize);
}
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, smooth ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, smooth ? GL_LINEAR : GL_NEAREST);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, outwidth, 0, outheight, -1.0, 1.0);
glViewport(0, 0, outwidth, outheight);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glPixelStorei(GL_UNPACK_ROW_LENGTH, iwidth);
glTexSubImage2D(GL_TEXTURE_2D,
/* mip-map level = */ 0, /* x = */ 0, /* y = */ 0,
inwidth, inheight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
//OpenGL projection sets 0,0 as *bottom-left* of screen.
//therefore, below vertices flip image to support top-left source.
//texture range = x1:0.0, y1:0.0, x2:1.0, y2:1.0
//vertex range = x1:0, y1:0, x2:width, y2:height
double w = double(inwidth) / double(iwidth);
double h = double(inheight) / double(iheight);
int u = outwidth;
int v = outheight;
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(shader_support) {
glUseProgram(0);
}
}
void set_fragment_shader(const char *source) {
if(!shader_support) return;
if(fragmentshader) {
glDetachShader(glprogram, fragmentshader);
glDeleteShader(fragmentshader);
fragmentshader = 0;
}
if(source) {
fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentshader, 1, &source, 0);
glCompileShader(fragmentshader);
glAttachShader(glprogram, fragmentshader);
}
glLinkProgram(glprogram);
}
void set_vertex_shader(const char *source) {
if(!shader_support) return;
if(vertexshader) {
glDetachShader(glprogram, vertexshader);
glDeleteShader(vertexshader);
vertexshader = 0;
}
if(source) {
vertexshader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexshader, 1, &source, 0);
glCompileShader(vertexshader);
glAttachShader(glprogram, vertexshader);
}
glLinkProgram(glprogram);
}
void init() {
//disable unused features
glDisable(GL_ALPHA_TEST);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDisable(GL_POLYGON_SMOOTH);
glDisable(GL_STENCIL_TEST);
//enable useful and required features
glEnable(GL_DITHER);
glEnable(GL_TEXTURE_2D);
//bind shader functions
glCreateProgram = (PFNGLCREATEPROGRAMPROC)glGetProcAddress("glCreateProgram");
glUseProgram = (PFNGLUSEPROGRAMPROC)glGetProcAddress("glUseProgram");
glCreateShader = (PFNGLCREATESHADERPROC)glGetProcAddress("glCreateShader");
glDeleteShader = (PFNGLDELETESHADERPROC)glGetProcAddress("glDeleteShader");
glShaderSource = (PFNGLSHADERSOURCEPROC)glGetProcAddress("glShaderSource");
glCompileShader = (PFNGLCOMPILESHADERPROC)glGetProcAddress("glCompileShader");
glAttachShader = (PFNGLATTACHSHADERPROC)glGetProcAddress("glAttachShader");
glDetachShader = (PFNGLDETACHSHADERPROC)glGetProcAddress("glDetachShader");
glLinkProgram = (PFNGLLINKPROGRAMPROC)glGetProcAddress("glLinkProgram");
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)glGetProcAddress("glGetUniformLocation");
glUniform1i = (PFNGLUNIFORM1IPROC)glGetProcAddress("glUniform1i");
glUniform2fv = (PFNGLUNIFORM2FVPROC)glGetProcAddress("glUniform2fv");
glUniform4fv = (PFNGLUNIFORM4FVPROC)glGetProcAddress("glUniform4fv");
shader_support = glCreateProgram && glUseProgram && glCreateShader
&& glDeleteShader && glShaderSource && glCompileShader && glAttachShader
&& glDetachShader && glLinkProgram && glGetUniformLocation
&& glUniform1i && glUniform2fv && glUniform4fv;
if(shader_support) glprogram = glCreateProgram();
//create surface texture
resize(256, 256);
}
void term() {
if(gltexture) {
glDeleteTextures(1, &gltexture);
gltexture = 0;
}
if(buffer) {
delete[] buffer;
buffer = 0;
iwidth = 0;
iheight = 0;
}
}
OpenGL() {
gltexture = 0;
glprogram = 0;
fragmentshader = 0;
vertexshader = 0;
buffer = 0;
iwidth = 0;
iheight = 0;
}
};

174
ruby/video/qtopengl.cpp Executable file
View File

@@ -0,0 +1,174 @@
#ifdef __APPLE__
#include <OpenGL/OpenGL.h>
#endif
namespace ruby {
class pVideoQtOpenGL {
public:
QWidget *parent;
QVBoxLayout *layout;
class RubyGLWidget : public QGLWidget {
public:
GLuint texture;
unsigned textureWidth, textureHeight;
uint32_t *buffer;
unsigned rasterWidth, rasterHeight;
bool synchronize;
unsigned filter;
void resize(unsigned width, unsigned height) {
if(width > textureWidth || height > textureHeight) {
textureWidth = max(width, textureWidth);
textureHeight = max(height, textureHeight);
if(buffer) {
delete[] buffer;
glDeleteTextures(1, &texture);
}
buffer = new uint32_t[textureWidth * textureHeight];
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStorei(GL_UNPACK_ROW_LENGTH, textureWidth);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, textureHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
}
}
void updateSynchronization() {
#ifdef __APPLE__
makeCurrent();
CGLContextObj context = CGLGetCurrentContext();
GLint value = synchronize; //0 = draw immediately (no vsync), 1 = draw once per frame (vsync)
CGLSetParameter(context, kCGLCPSwapInterval, &value);
#endif
}
void paintGL() {
unsigned outputWidth = width();
unsigned outputHeight = height();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, outputWidth, 0, outputHeight, -1.0, 1.0);
glViewport(0, 0, outputWidth, outputHeight);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter == Video::FilterPoint) ? GL_NEAREST : GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter == Video::FilterPoint) ? GL_NEAREST : GL_LINEAR);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rasterWidth, rasterHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
double w = (double)rasterWidth / (double)textureWidth;
double h = (double)rasterHeight / (double)textureHeight;
unsigned u = outputWidth;
unsigned v = outputHeight;
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();
}
void initializeGL() {
format().setDoubleBuffer(true);
texture = 0;
textureWidth = 0;
textureHeight = 0;
buffer = 0;
resize(rasterWidth = 256, rasterHeight = 256);
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);
glClearColor(0.0, 0.0, 0.0, 0.0);
}
} *widget;
bool cap(const string& name) {
if(name == Video::Synchronize) return true;
if(name == Video::Filter) return true;
if(name == "QWidget") return true;
return false;
}
any get(const string& name) {
if(name == Video::Synchronize) return widget->synchronize;
if(name == Video::Filter) return widget->filter;
if(name == "QWidget") return parent;
return false;
}
bool set(const string& name, const any& value) {
if(name == Video::Synchronize) {
widget->synchronize = any_cast<bool>(value);
widget->updateSynchronization();
return true;
}
if(name == Video::Filter) {
widget->filter = any_cast<unsigned>(value);
return true;
}
if(name == "QWidget") {
parent = any_cast<QWidget*>(value);
return true;
}
return false;
}
bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) {
widget->resize(width, height);
widget->rasterWidth = width;
widget->rasterHeight = height;
pitch = widget->textureWidth * sizeof(uint32_t);
return data = widget->buffer;
}
void unlock() {
}
void clear() {
memset(widget->buffer, 0, widget->textureWidth * widget->textureHeight * sizeof(uint32_t));
widget->updateGL();
}
void refresh() {
widget->updateGL();
}
bool init() {
layout = new QVBoxLayout;
layout->setMargin(0);
widget = new RubyGLWidget;
widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
layout->addWidget(widget);
parent->setLayout(layout);
return true;
}
void term() {
}
};
DeclareVideo(QtOpenGL)
};

126
ruby/video/qtraster.cpp Executable file
View File

@@ -0,0 +1,126 @@
namespace ruby {
struct VideoQtRasterContext {
QImage *image;
unsigned width, height;
unsigned filter;
} context;
class pVideoQtRaster {
public:
QWidget *parent;
QVBoxLayout *layout;
struct QtImage : public QWidget {
VideoQtRasterContext &context;
void paintEvent(QPaintEvent*) {
if(context.image == 0) return;
QPainter painter(this);
if(size().width() == context.width && size().height() == context.height) {
painter.drawImage(0, 0, *context.image);
} else {
Qt::TransformationMode mode = Qt::FastTransformation;
if(context.filter == Video::FilterLinear) mode = Qt::SmoothTransformation;
painter.drawImage(0, 0, context.image->scaled(size(), Qt::IgnoreAspectRatio, mode));
}
}
QtImage(QWidget *parent, VideoQtRasterContext &context_) : QWidget(parent), context(context_) {}
} *widget;
bool cap(const string& name) {
if(name == Video::Filter) return true;
if(name == "QWidget") return true;
return false;
}
any get(const string& name) {
if(name == Video::Filter) return context.filter;
if(name == "QWidget") return parent;
return false;
}
bool set(const string& name, const any& value) {
if(name == Video::Filter) {
context.filter = any_cast<unsigned>(value);
return true;
}
if(name == "QWidget") {
parent = any_cast<QWidget*>(value);
return true;
}
return false;
}
void resize(unsigned width, unsigned height) {
if(context.width != width || context.height != height) {
if(context.image) delete context.image;
context.image = new QImage(context.width = width, context.height = height, QImage::Format_RGB32);
}
}
bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) {
//if image size has changed since last lock(), re-allocate buffer to match new size
if(width != context.width || height != context.height) resize(width, height);
pitch = width * sizeof(uint32_t);
return data = (uint32_t*)context.image->bits();
}
void unlock() {
}
void clear() {
context.image->fill(0);
widget->update();
}
void refresh() {
widget->update();
}
bool init() {
term();
layout = new QVBoxLayout;
layout->setMargin(0);
context.image = 0;
context.width = 0;
context.height = 0;
context.filter = Video::FilterPoint;
resize(256, 256);
widget = new QtImage(parent, context);
widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
layout->addWidget(widget);
parent->setLayout(layout);
clear();
return true;
}
void term() {
if(context.image) delete context.image;
if(widget) delete widget;
if(layout) delete layout;
context.image = 0;
widget = 0;
layout = 0;
}
pVideoQtRaster() {
context.image = 0;
widget = 0;
layout = 0;
}
};
DeclareVideo(QtRaster)
};

140
ruby/video/sdl.cpp Executable file
View File

@@ -0,0 +1,140 @@
#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>
namespace ruby {
class pVideoSDL {
public:
Display *display;
SDL_Surface *screen, *buffer;
unsigned iwidth, iheight;
struct {
uintptr_t handle;
unsigned width;
unsigned height;
} settings;
bool cap(const string& name) {
if(name == Video::Handle) return true;
return false;
}
any get(const string& name) {
if(name == Video::Handle) return settings.handle;
return false;
}
bool set(const string& name, const any& value) {
if(name == Video::Handle) {
settings.handle = any_cast<uintptr_t>(value);
return true;
}
return false;
}
void resize(unsigned width, unsigned height) {
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
);
}
bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) {
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;
}
void unlock() {
if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer);
}
void clear() {
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();
}
void refresh() {
//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);
}
bool init() {
display = XOpenDisplay(0);
char env[512];
sprintf(env, "SDL_WINDOWID=%ld", (long int)settings.handle);
putenv(env);
SDL_InitSubSystem(SDL_INIT_VIDEO);
screen = SDL_SetVideoMode(2560, 1600, 32, SDL_HWSURFACE);
buffer = 0;
iwidth = 0;
iheight = 0;
resize(settings.width = 256, settings.height = 256);
return true;
}
void term() {
XCloseDisplay(display);
SDL_FreeSurface(buffer);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
pVideoSDL() {
settings.handle = 0;
}
};
DeclareVideo(SDL)
};

154
ruby/video/wgl.cpp Executable file
View File

@@ -0,0 +1,154 @@
/*
video.wgl
authors: byuu, krom
*/
#include "opengl.hpp"
namespace ruby {
class pVideoWGL : public OpenGL {
public:
BOOL (APIENTRY *glSwapInterval)(int);
HDC display;
HGLRC wglcontext;
HWND window;
HINSTANCE glwindow;
struct {
HWND handle;
bool synchronize;
unsigned filter;
unsigned width;
unsigned height;
} settings;
bool cap(const string& name) {
if(name == Video::Handle) return true;
if(name == Video::Synchronize) return true;
if(name == Video::Filter) return true;
if(name == Video::FragmentShader) return true;
if(name == Video::VertexShader) return true;
return false;
}
any get(const string& name) {
if(name == Video::Handle) return (uintptr_t)settings.handle;
if(name == Video::Synchronize) return settings.synchronize;
if(name == Video::Filter) return settings.filter;
return false;
}
bool set(const string& name, const any& value) {
if(name == Video::Handle) {
settings.handle = (HWND)any_cast<uintptr_t>(value);
return true;
}
if(name == Video::Synchronize) {
if(settings.synchronize != any_cast<bool>(value)) {
settings.synchronize = any_cast<bool>(value);
if(wglcontext) init();
}
}
if(name == Video::Filter) {
settings.filter = any_cast<unsigned>(value);
return true;
}
if(name == Video::FragmentShader) {
OpenGL::set_fragment_shader(any_cast<const char*>(value));
return true;
}
if(name == Video::VertexShader) {
OpenGL::set_vertex_shader(any_cast<const char*>(value));
return true;
}
return false;
}
bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) {
resize(width, height);
settings.width = width;
settings.height = height;
return OpenGL::lock(data, pitch);
}
void unlock() {
}
void clear() {
OpenGL::clear();
SwapBuffers(display);
}
void refresh() {
RECT rc;
GetClientRect(settings.handle, &rc);
OpenGL::refresh(settings.filter == Video::FilterLinear,
settings.width, settings.height,
rc.right - rc.left, rc.bottom - rc.top);
SwapBuffers(display);
}
bool init() {
term();
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);
OpenGL::init();
settings.width = 256;
settings.height = 256;
//vertical synchronization
if(!glSwapInterval) glSwapInterval = (BOOL (APIENTRY*)(int))glGetProcAddress("wglSwapIntervalEXT");
if( glSwapInterval) glSwapInterval(settings.synchronize);
return true;
}
void term() {
OpenGL::term();
if(wglcontext) {
wglDeleteContext(wglcontext);
wglcontext = 0;
}
}
pVideoWGL() : glSwapInterval(0) {
settings.handle = 0;
settings.synchronize = false;
settings.filter = 0;
window = 0;
wglcontext = 0;
glwindow = 0;
}
~pVideoWGL() { term(); }
};
DeclareVideo(WGL)
};

498
ruby/video/xv.cpp Executable file
View File

@@ -0,0 +1,498 @@
#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" XvImage* XvShmCreateImage(Display*, XvPortID, int, char*, int, int, XShmSegmentInfo*);
namespace ruby {
class pVideoXv {
public:
uint32_t *buffer;
uint8_t *ytable, *utable, *vtable;
enum XvFormat {
XvFormatRGB32,
XvFormatRGB24,
XvFormatRGB16,
XvFormatRGB15,
XvFormatYUY2,
XvFormatUYVY,
XvFormatUnknown
};
struct {
Display *display;
GC gc;
Window window;
Colormap colormap;
XShmSegmentInfo shminfo;
int port;
int depth;
int visualid;
XvImage *image;
XvFormat format;
uint32_t fourcc;
unsigned width;
unsigned height;
} device;
struct {
Window handle;
bool synchronize;
unsigned width;
unsigned height;
} settings;
bool cap(const string& name) {
if(name == Video::Handle) return true;
if(name == Video::Synchronize) {
return XInternAtom(XOpenDisplay(0), "XV_SYNC_TO_VBLANK", true) != None;
}
return false;
}
any get(const string& name) {
if(name == Video::Handle) return settings.handle;
if(name == Video::Synchronize) return settings.synchronize;
return false;
}
bool set(const string& name, const any& value) {
if(name == Video::Handle) {
settings.handle = any_cast<uintptr_t>(value);
return true;
}
if(name == Video::Synchronize) {
Display *display = XOpenDisplay(0);
Atom atom = XInternAtom(display, "XV_SYNC_TO_VBLANK", true);
if(atom != None && device.port >= 0) {
settings.synchronize = any_cast<bool>(value);
XvSetPortAttribute(display, device.port, atom, settings.synchronize);
return true;
}
return false;
}
return false;
}
void resize(unsigned width, unsigned height) {
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, NULL);
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];
}
bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) {
if(width != settings.width || height != settings.height) {
resize(settings.width = width, settings.height = height);
}
pitch = device.width * 4;
return data = buffer;
}
void unlock() {
}
void clear() {
memset(buffer, 0, device.width * device.height * sizeof(uint32_t));
//clear twice in case video is double buffered ...
refresh();
refresh();
}
void refresh() {
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: render_rgb32(width, height); break;
case XvFormatRGB24: render_rgb24(width, height); break;
case XvFormatRGB16: render_rgb16(width, height); break;
case XvFormatRGB15: render_rgb15(width, height); break;
case XvFormatYUY2: render_yuy2 (width, height); break;
case XvFormatUYVY: render_uyvy (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);
}
bool init() {
device.display = XOpenDisplay(0);
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;
int 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;
init_yuv_tables();
clear();
return true;
}
void term() {
XShmDetach(device.display, &device.shminfo);
shmdt(device.shminfo.shmaddr);
shmctl(device.shminfo.shmid, IPC_RMID, NULL);
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(buffer) { delete[] buffer; buffer = 0; }
if(ytable) { delete[] ytable; ytable = 0; }
if(utable) { delete[] utable; utable = 0; }
if(vtable) { delete[] vtable; vtable = 0; }
}
void render_rgb32(unsigned width, unsigned height) {
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;
}
}
void render_rgb24(unsigned width, unsigned height) {
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;
}
}
void render_rgb16(unsigned width, unsigned height) {
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;
}
}
void render_rgb15(unsigned width, unsigned height) {
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;
}
}
void render_yuy2(unsigned width, unsigned height) {
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;
}
}
void render_uyvy(unsigned width, unsigned height) {
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;
}
}
void init_yuv_tables() {
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;
}
}
pVideoXv() {
device.window = 0;
device.colormap = 0;
device.port = -1;
ytable = 0;
utable = 0;
vtable = 0;
settings.handle = 0;
settings.synchronize = false;
}
~pVideoXv() {
term();
}
};
DeclareVideo(Xv)
};