Update to v103r18 release.

byuu says:

Changelog:

  - tomoko: improved handling of changing audio devices on the audio
    settings panel
  - ruby/audio/wasapi: added device enumeration and selection support¹
  - ruby/audio/wasapi: release property store handle from audio device
  - ruby/audio/wasapi: fix exclusive mode buffer filling
  - ruby/video/glx2: ported to new API -- tested and confirmed working
    great²
  - ruby/video/sdl: fixed initialization -- tested and confirmed working
    on FreeBSD now³
  - ruby/video/xv: ported to new API -- tested and mostly working great,
    sans fullscreen mode⁴

Errata:

  - accidentally changed "Driver Settings" label to "Driver" on the
    audio settings tab because I deleted the line and forgot the
    "Settings" part
  - need to use "return initialize();" from setDevice() in the WASAPI
    driver, instead of "return true;", so device selection is currently
    not functioning in this WIP for said driver

¹: for now, this will likely end up selecting the first available
endpoint device, which is probably wrong. I need to come up with a
system to expose good 'default values' when selecting new audio drivers,
or changing audio device settings.

²: glx2 is a fallback driver for system with only OpenGL 2.0 and no
OpenGL 3.2 drivers, such as FreeBSD 10.1 with AMD graphics cards.

³: although I really should track down why InputManager::poll() is
crashing the emulator when Video::ready() returns false ...

⁴: really bizarrely, when entering fullscreen mode, it looks like the
image was a triangle strip, and the bottom right triange is missing, and
the top left triangle skews the entire image into it. I'm suspecting
this is a Radeon driver bug when trying to create a 2560x1600 X-Video
surface. The glitch persists when exiting fullscreen, too.

If anyone can test the X-Video driver on their Linux/BSD system, it'd be
appreciated. If it's just my video card, I'll ignore it. If not,
hopefully someone can find the cause of the issue :|
This commit is contained in:
Tim Allen 2017-07-20 21:52:47 +10:00
parent 0b4e7fb5a5
commit 284e4c043e
10 changed files with 526 additions and 481 deletions

View File

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

View File

@ -26,11 +26,11 @@ else ifeq ($(platform),macosx)
ruby += audio.openal
ruby += #input.quartz input.carbon
else ifeq ($(platform),linux)
ruby += video.xshm video.sdl #video.glx video.xv
ruby += video.glx2 video.xv video.xshm video.sdl #video.glx
ruby += audio.oss audio.openal #audio.alsa audio.pulseaudio audio.pulseaudiosimple audio.ao
ruby += input.sdl input.xlib #input.udev
else ifeq ($(platform),bsd)
ruby += video.xshm #video.glx video.xv
ruby += video.glx2 video.xv video.xshm video.sdl #video.glx
ruby += audio.oss audio.openal
ruby += input.sdl input.xlib
endif

View File

@ -4,10 +4,19 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) {
layout.setMargin(5);
driverLabel.setFont(Font().setBold()).setText("Driver Settings");
driverLabel.setFont(Font().setBold()).setText("Driver");
deviceLabel.setText("Device:");
deviceList.onChange([&] { updateDriver(); updateDriverLists(); });
deviceList.onChange([&] { updateDevice(); updateDriver(); });
//the device list never changes once a driver is activated;
//however, the available frequencies and latencies may change when the active device is changed
for(auto& device : audio->information().devices) {
deviceList.append(ComboButtonItem().setText(device));
if(device == settings["Audio/Device"].text()) {
deviceList.item(deviceList.itemCount() - 1).setSelected();
}
}
frequencyLabel.setText("Frequency:");
frequencyList.onChange([&] { updateDriver(); });
@ -30,14 +39,29 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) {
reverbEnable.setText("Reverb").setChecked(settings["Audio/Reverb/Enable"].boolean()).onToggle([&] { updateEffects(); });
updateDriverLists();
updateDevice();
updateDriver(true);
updateEffects(true);
}
//when changing audio drivers, device/frequency/latency values may no longer be valid for new driver
//updateDriverLists() will try to select a match if one is found
//otherwise, this function will force now-invalid settings to the first setting in each list
auto AudioSettings::updateDevice() -> void {
frequencyList.reset();
for(auto& frequency : audio->information().frequencies) {
frequencyList.append(ComboButtonItem().setText(frequency));
if(frequency == settings["Audio/Frequency"].real()) {
frequencyList.item(frequencyList.itemCount() - 1).setSelected();
}
}
latencyList.reset();
for(auto& latency : audio->information().latencies) {
latencyList.append(ComboButtonItem().setText(latency));
if(latency == settings["Audio/Latency"].natural()) {
latencyList.item(latencyList.itemCount() - 1).setSelected();
}
}
}
auto AudioSettings::updateDriver(bool initializing) -> void {
settings["Audio/Device"].setValue(deviceList.selected().text());
settings["Audio/Frequency"].setValue(frequencyList.selected().text());
@ -58,33 +82,3 @@ auto AudioSettings::updateEffects(bool initializing) -> void {
if(!initializing) program->updateAudioEffects();
}
//called during initialization, and after changing audio device
//each audio device may have separately supported frequencies and/or latencies
auto AudioSettings::updateDriverLists() -> void {
auto information = audio->information();
deviceList.reset();
for(auto& device : information.devices) {
deviceList.append(ComboButtonItem().setText(device));
if(device == settings["Audio/Device"].text()) {
deviceList.item(deviceList.itemCount() - 1).setSelected();
}
}
frequencyList.reset();
for(auto& frequency : information.frequencies) {
frequencyList.append(ComboButtonItem().setText(frequency));
if(frequency == settings["Audio/Frequency"].real()) {
frequencyList.item(frequencyList.itemCount() - 1).setSelected();
}
}
latencyList.reset();
for(auto& latency : information.latencies) {
latencyList.append(ComboButtonItem().setText(latency));
if(latency == settings["Audio/Latency"].natural()) {
latencyList.item(latencyList.itemCount() - 1).setSelected();
}
}
}

View File

@ -63,9 +63,9 @@ struct AudioSettings : TabFrameItem {
HorizontalSlider balanceSlider{&balanceLayout, Size{~0, 0}};
CheckLabel reverbEnable{&layout, Size{~0, 0}};
auto updateDevice() -> void;
auto updateDriver(bool initializing = false) -> void;
auto updateEffects(bool initializing = false) -> void;
auto updateDriverLists() -> void;
};
struct InputSettings : TabFrameItem {

View File

@ -4,6 +4,7 @@
#include <audiopolicy.h>
#include <devicetopology.h>
#include <endpointvolume.h>
#include <functiondiscoverykeys_devpkey.h>
struct AudioWASAPI : Audio {
AudioWASAPI() { initialize(); }
@ -13,14 +14,15 @@ struct AudioWASAPI : Audio {
auto information() -> Information {
Information information;
information.devices = {"Default"};
for(auto& device : _devices) information.devices.append(device);
information.channels = {2};
information.frequencies = {(double)_frequency};
information.latencies = {20, 40, 60, 80, 100};
information.latencies = {0, 20, 40, 60, 80, 100};
return information;
}
auto exclusive() -> bool { return _exclusive; }
auto device() -> string { return _device; }
auto blocking() -> bool { return _blocking; }
auto channels() -> uint { return _channels; }
auto frequency() -> double { return (double)_frequency; }
@ -32,6 +34,12 @@ struct AudioWASAPI : Audio {
return initialize();
}
auto setDevice(string device) -> bool {
if(_device == device) return true;
_device = device;
return true;
}
auto setBlocking(bool blocking) -> bool {
if(_blocking == blocking) return true;
_blocking = blocking;
@ -77,31 +85,65 @@ private:
terminate();
if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&_enumerator) != S_OK) return false;
if(_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &_audioDevice) != S_OK) return false;
//enumerate all audio endpoint devices, and select the first to match the device() name
IMMDeviceCollection* deviceCollection = nullptr;
if(_enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection) != S_OK) return false;
uint deviceCount = 0;
if(deviceCollection->GetCount(&deviceCount) != S_OK) return false;
for(uint deviceIndex : range(deviceCount)) {
IMMDevice* device = nullptr;
if(deviceCollection->Item(deviceIndex, &device) != S_OK) return false;
IPropertyStore* propertyStore = nullptr;
device->OpenPropertyStore(STGM_READ, &propertyStore);
PROPVARIANT propVariant;
propertyStore->GetValue(PKEY_Device_FriendlyName, &propVariant);
_devices.append((const char*)utf8_t(propVariant.pwszVal));
propertyStore->Release();
if(!_audioDevice && _devices.right() == _device) {
_audioDevice = device;
} else {
device->Release();
}
}
deviceCollection->Release();
//if no match is found, choose the default audio endpoint for the device()
if(!_audioDevice) {
if(_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &_audioDevice) != S_OK) return false;
}
if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false;
WAVEFORMATEXTENSIBLE waveFormat = {};
if(_exclusive) {
if(_audioDevice->OpenPropertyStore(STGM_READ, &_propertyStore) != S_OK) return false;
if(_propertyStore->GetValue(PKEY_AudioEngine_DeviceFormat, &_propVariant) != S_OK) return false;
_waveFormat = (WAVEFORMATEX*)_propVariant.blob.pBlobData;
IPropertyStore* propertyStore = nullptr;
if(_audioDevice->OpenPropertyStore(STGM_READ, &propertyStore) != S_OK) return false;
PROPVARIANT propVariant;
if(propertyStore->GetValue(PKEY_AudioEngine_DeviceFormat, &propVariant) != S_OK) return false;
waveFormat = *(WAVEFORMATEXTENSIBLE*)propVariant.blob.pBlobData;
propertyStore->Release();
if(_audioClient->GetDevicePeriod(nullptr, &_devicePeriod) != S_OK) return false;
auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units
auto result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, _waveFormat, nullptr);
auto result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, &waveFormat.Format, nullptr);
if(result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false;
_audioClient->Release();
latency = (REFERENCE_TIME)(10'000 * 1'000 * _bufferSize / _waveFormat->nSamplesPerSec);
latency = (REFERENCE_TIME)(10'000 * 1'000 * _bufferSize / waveFormat.Format.nSamplesPerSec);
if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false;
result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, _waveFormat, nullptr);
result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, &waveFormat.Format, nullptr);
}
if(result != S_OK) return false;
DWORD taskIndex = 0;
_taskHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskIndex);
} else {
if(_audioClient->GetMixFormat(&_waveFormat) != S_OK) return false;
WAVEFORMATEX* waveFormatEx = nullptr;
if(_audioClient->GetMixFormat(&waveFormatEx) != S_OK) return false;
waveFormat = *(WAVEFORMATEXTENSIBLE*)waveFormatEx;
CoTaskMemFree(waveFormatEx);
if(_audioClient->GetDevicePeriod(&_devicePeriod, nullptr)) return false;
auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units
if(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, 0, _waveFormat, nullptr) != S_OK) return false;
if(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, 0, &waveFormat.Format, nullptr) != S_OK) return false;
}
_eventHandle = CreateEvent(nullptr, false, false, nullptr);
@ -109,19 +151,19 @@ private:
if(_audioClient->GetService(IID_IAudioRenderClient, (void**)&_renderClient) != S_OK) return false;
if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false;
_channels = _waveFormat->nChannels;
_frequency = _waveFormat->nSamplesPerSec;
_mode = ((WAVEFORMATEXTENSIBLE*)_waveFormat)->SubFormat.Data1;
_precision = _waveFormat->wBitsPerSample;
_channels = waveFormat.Format.nChannels;
_frequency = waveFormat.Format.nSamplesPerSec;
_mode = waveFormat.SubFormat.Data1;
_precision = waveFormat.Format.wBitsPerSample;
clear();
return _ready = true;
}
auto terminate() -> void {
_devices.reset();
if(_audioClient) _audioClient->Stop();
if(_renderClient) _renderClient->Release(), _renderClient = nullptr;
if(_waveFormat) CoTaskMemFree(_waveFormat), _waveFormat = nullptr;
if(_audioClient) _audioClient->Release(), _audioClient = nullptr;
if(_audioDevice) _audioDevice->Release(), _audioDevice = nullptr;
if(_eventHandle) CloseHandle(_eventHandle), _eventHandle = nullptr;
@ -129,9 +171,12 @@ private:
}
auto write() -> void {
uint32_t padding = 0;
_audioClient->GetCurrentPadding(&padding);
uint32_t available = _bufferSize - padding;
uint32_t available = _bufferSize;
if(!_exclusive) {
uint32_t padding = 0;
_audioClient->GetCurrentPadding(&padding);
available = _bufferSize - padding;
}
uint8_t* buffer = nullptr;
if(_renderClient->GetBuffer(available, &buffer) == S_OK) {
@ -171,6 +216,7 @@ private:
bool _ready = false;
bool _exclusive = false;
string _device;
bool _blocking = true;
uint _channels = 2;
uint _frequency = 48000;
@ -187,12 +233,10 @@ private:
} _queue;
IMMDeviceEnumerator* _enumerator = nullptr;
vector<string> _devices;
IMMDevice* _audioDevice = nullptr;
IPropertyStore* _propertyStore = nullptr;
IAudioClient* _audioClient = nullptr;
IAudioRenderClient* _renderClient = nullptr;
WAVEFORMATEX* _waveFormat = nullptr;
PROPVARIANT _propVariant;
HANDLE _eventHandle = nullptr;
HANDLE _taskHandle = nullptr;
REFERENCE_TIME _devicePeriod = 0;

View File

@ -311,6 +311,7 @@ private:
}
auto terminate() -> void {
_ready = false;
if(_vertexBuffer) { _vertexBuffer->Release(); _vertexBuffer = nullptr; }
if(_surface) { _surface->Release(); _surface = nullptr; }
if(_texture) { _texture->Release(); _texture = nullptr; }

View File

@ -3,94 +3,82 @@
//note: this is a fallback driver for use when OpenGL 3.2 is not available.
//see glx.cpp for comments on how this driver operates (they are very similar.)
#if defined(DISPLAY_XORG)
#include <GL/gl.h>
#include <GL/glx.h>
#ifndef glGetProcAddress
#define glGetProcAddress(name) (*glXGetProcAddress)((const GLubyte*)(name))
#endif
#elif defined(DISPLAY_QUARTZ)
#include <OpenGL/gl3.h>
#elif defined(DISPLAY_WINDOWS)
#include <GL/gl.h>
#include <GL/glext.h>
#ifndef glGetProcAddress
#define glGetProcAddress(name) wglGetProcAddress(name)
#endif
#else
#error "ruby::OpenGL2: unsupported platform"
#endif
struct VideoGLX2 : Video {
~VideoGLX2() { term(); }
VideoGLX2() { initialize(); }
~VideoGLX2() { terminate(); }
auto (*glXSwapInterval)(int) -> int = nullptr;
Display* display = nullptr;
int screen = 0;
Window xwindow = 0;
Colormap colormap = 0;
GLXContext glxcontext = nullptr;
GLXWindow glxwindow = 0;
auto ready() -> bool { return _ready; }
struct {
Window handle = 0;
bool synchronize = false;
uint filter = Video::FilterLinear;
auto context() -> uintptr { return _context; }
auto blocking() -> bool { return _blocking; }
auto smooth() -> bool { return _smooth; }
uint width = 256;
uint height = 256;
bool isDoubleBuffered = false;
bool isDirect = false;
} settings;
auto cap(const string& name) -> bool {
if(name == Video::Handle) return true;
if(name == Video::Synchronize) return true;
if(name == Video::Filter) return true;
return false;
auto setContext(uintptr context) -> bool {
if(_context == context) return true;
_context = context;
return initialize();
}
auto get(const string& name) -> any {
if(name == Video::Handle) return (uintptr)settings.handle;
if(name == Video::Synchronize) return settings.synchronize;
if(name == Video::Filter) return settings.filter;
return {};
auto setBlocking(bool blocking) -> bool {
if(_blocking == blocking) return true;
_blocking = blocking;
if(_ready && glXSwapInterval) glXSwapInterval(_blocking);
return true;
}
auto set(const string& name, const any& value) -> bool {
if(name == Video::Handle && value.is<uintptr>()) {
settings.handle = value.get<uintptr>();
return true;
}
auto setSmooth(bool smooth) -> bool {
if(_smooth == smooth) return true;
_smooth = smooth;
return true;
}
if(name == Video::Synchronize && value.is<bool>()) {
if(settings.synchronize != value.get<bool>()) {
settings.synchronize = value.get<bool>();
if(glXSwapInterval) glXSwapInterval(settings.synchronize);
return true;
}
}
if(name == Video::Filter && value.is<uint>()) {
settings.filter = value.get<uint>();
return true;
}
return false;
auto clear() -> void {
memory::fill(_glBuffer, _glWidth * _glHeight * sizeof(uint32_t));
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
if(_isDoubleBuffered) glXSwapBuffers(_display, _glXWindow);
}
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
if(width != settings.width || height != settings.height) resize(width, height);
pitch = glwidth * sizeof(uint32_t);
return data = glbuffer;
if(width != _width || height != _height) resize(width, height);
pitch = _glWidth * sizeof(uint32_t);
return data = _glBuffer;
}
auto unlock() -> void {
}
auto clear() -> void {
memory::fill(glbuffer, glwidth * glheight * sizeof(uint32_t));
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
if(settings.isDoubleBuffered) glXSwapBuffers(display, glxwindow);
}
auto refresh() -> void {
auto output() -> void {
XWindowAttributes parent, child;
XGetWindowAttributes(display, settings.handle, &parent);
XGetWindowAttributes(display, xwindow, &child);
XGetWindowAttributes(_display, (Window)_context, &parent);
XGetWindowAttributes(_display, _window, &child);
if(child.width != parent.width || child.height != parent.height) {
XResizeWindow(display, xwindow, parent.width, parent.height);
XResizeWindow(_display, _window, parent.width, parent.height);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, settings.filter ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, settings.filter ? GL_LINEAR : GL_NEAREST);
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();
@ -99,12 +87,11 @@ struct VideoGLX2 : Video {
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glPixelStorei(GL_UNPACK_ROW_LENGTH, glwidth);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, settings.width, settings.height,
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, glbuffer);
glPixelStorei(GL_UNPACK_ROW_LENGTH, _glWidth);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _width, _height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, _glBuffer);
double w = (double)settings.width / (double)glwidth;
double h = (double)settings.height / (double)glheight;
double w = (double)_width / (double)_glWidth;
double h = (double)_height / (double)_glHeight;
int u = parent.width;
int v = parent.height;
@ -116,19 +103,23 @@ struct VideoGLX2 : Video {
glEnd();
glFlush();
if(settings.isDoubleBuffered) glXSwapBuffers(display, glxwindow);
if(_isDoubleBuffered) glXSwapBuffers(_display, _glXWindow);
}
auto init() -> bool {
display = XOpenDisplay(0);
screen = DefaultScreen(display);
private:
auto initialize() -> bool {
terminate();
if(!_context) return false;
_display = XOpenDisplay(0);
_screen = DefaultScreen(_display);
int versionMajor = 0, versionMinor = 0;
glXQueryVersion(display, &versionMajor, &versionMinor);
glXQueryVersion(_display, &versionMajor, &versionMinor);
if(versionMajor < 1 || (versionMajor == 1 && versionMinor < 2)) return false;
XWindowAttributes windowAttributes;
XGetWindowAttributes(display, settings.handle, &windowAttributes);
XGetWindowAttributes(_display, (Window)_context, &windowAttributes);
int attributeList[] = {
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
@ -141,37 +132,37 @@ struct VideoGLX2 : Video {
};
int fbCount = 0;
auto fbConfig = glXChooseFBConfig(display, screen, attributeList, &fbCount);
auto fbConfig = glXChooseFBConfig(_display, _screen, attributeList, &fbCount);
if(fbCount == 0) return false;
auto vi = glXGetVisualFromFBConfig(display, fbConfig[0]);
colormap = XCreateColormap(display, RootWindow(display, vi->screen), vi->visual, AllocNone);
auto vi = glXGetVisualFromFBConfig(_display, fbConfig[0]);
_colormap = XCreateColormap(_display, RootWindow(_display, vi->screen), vi->visual, AllocNone);
XSetWindowAttributes attributes;
attributes.colormap = colormap;
attributes.colormap = _colormap;
attributes.border_pixel = 0;
xwindow = XCreateWindow(display, settings.handle, 0, 0, windowAttributes.width, windowAttributes.height,
_window = XCreateWindow(_display, (Window)_context, 0, 0, windowAttributes.width, windowAttributes.height,
0, vi->depth, InputOutput, vi->visual, CWColormap | CWBorderPixel, &attributes);
XSetWindowBackground(display, xwindow, 0);
XMapWindow(display, xwindow);
XFlush(display);
XSetWindowBackground(_display, _window, 0);
XMapWindow(_display, _window);
XFlush(_display);
while(XPending(display)) {
while(XPending(_display)) {
XEvent event;
XNextEvent(display, &event);
XNextEvent(_display, &event);
}
glxcontext = glXCreateContext(display, vi, 0, GL_TRUE);
glXMakeCurrent(display, glxwindow = xwindow, glxcontext);
_glXContext = glXCreateContext(_display, vi, 0, GL_TRUE);
glXMakeCurrent(_display, _glXWindow = _window, _glXContext);
if(!glXSwapInterval) glXSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalMESA");
if(!glXSwapInterval) glXSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalSGI");
if(glXSwapInterval) glXSwapInterval(settings.synchronize);
if(glXSwapInterval) glXSwapInterval(_blocking);
int value = 0;
glXGetConfig(display, vi, GLX_DOUBLEBUFFER, &value);
settings.isDoubleBuffered = value;
settings.isDirect = glXIsDirect(display, glxcontext);
glXGetConfig(_display, vi, GLX_DOUBLEBUFFER, &value);
_isDoubleBuffered = value;
_isDirect = glXIsDirect(_display, _glXContext);
glDisable(GL_ALPHA_TEST);
glDisable(GL_BLEND);
@ -183,62 +174,83 @@ struct VideoGLX2 : Video {
glEnable(GL_TEXTURE_2D);
resize(256, 256);
return true;
return _ready = true;
}
auto term() -> void {
if(gltexture) {
glDeleteTextures(1, &gltexture);
gltexture = 0;
auto terminate() -> void {
_ready = false;
if(_glTexture) {
glDeleteTextures(1, &_glTexture);
_glTexture = 0;
}
if(glbuffer) {
delete[] glbuffer;
glbuffer = 0;
if(_glBuffer) {
delete[] _glBuffer;
_glBuffer = nullptr;
}
glwidth = 0;
glheight = 0;
_glWidth = 0;
_glHeight = 0;
if(glxcontext) {
glXDestroyContext(display, glxcontext);
glxcontext = nullptr;
if(_glXContext) {
glXDestroyContext(_display, _glXContext);
_glXContext = nullptr;
}
if(xwindow) {
XUnmapWindow(display, xwindow);
xwindow = 0;
if(_window) {
XUnmapWindow(_display, _window);
_window = 0;
}
if(colormap) {
XFreeColormap(display, colormap);
colormap = 0;
if(_colormap) {
XFreeColormap(_display, _colormap);
_colormap = 0;
}
if(display) {
XCloseDisplay(display);
display = nullptr;
if(_display) {
XCloseDisplay(_display);
_display = nullptr;
}
}
private:
GLuint gltexture = 0;
uint32_t* glbuffer = nullptr;
uint glwidth = 0;
uint glheight = 0;
auto resize(uint width, uint height) -> void {
settings.width = width;
settings.height = height;
_width = width;
_height = height;
if(gltexture == 0) glGenTextures(1, &gltexture);
glwidth = max(glwidth, width);
glheight = max(glheight, height);
if(glbuffer) delete[] glbuffer;
glbuffer = new uint32_t[glwidth * glheight]();
if(_glTexture == 0) glGenTextures(1, &_glTexture);
_glWidth = max(_glWidth, width);
_glHeight = max(_glHeight, height);
delete[] _glBuffer;
_glBuffer = new uint32_t[_glWidth * _glHeight]();
glBindTexture(GL_TEXTURE_2D, gltexture);
glPixelStorei(GL_UNPACK_ROW_LENGTH, glwidth);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, glwidth, glheight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, glbuffer);
glBindTexture(GL_TEXTURE_2D, _glTexture);
glPixelStorei(GL_UNPACK_ROW_LENGTH, _glWidth);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _glWidth, _glHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, _glBuffer);
}
auto (*glXSwapInterval)(int) -> int = nullptr;
bool _ready = false;
uintptr _context = 0;
bool _blocking = false;
bool _smooth = true;
Display* _display = nullptr;
int _screen = 0;
Window _window = 0;
Colormap _colormap = 0;
GLXContext _glXContext = nullptr;
GLXWindow _glXWindow = 0;
bool _isDoubleBuffered = false;
bool _isDirect = false;
uint _width = 256;
uint _height = 256;
GLuint _glTexture = 0;
uint32_t* _glBuffer = nullptr;
uint _glWidth = 0;
uint _glHeight = 0;
};

View File

@ -1,15 +1,19 @@
#if defined(DISPLAY_XORG)
#include <GL/gl.h>
#include <GL/glx.h>
#define glGetProcAddress(name) (*glXGetProcAddress)((const GLubyte*)(name))
#ifndef glGetProcAddress
#define glGetProcAddress(name) (*glXGetProcAddress)((const GLubyte*)(name))
#endif
#elif defined(DISPLAY_QUARTZ)
#include <OpenGL/gl3.h>
#elif defined(DISPLAY_WINDOWS)
#include <GL/gl.h>
#include <GL/glext.h>
#define glGetProcAddress(name) wglGetProcAddress(name)
#ifndef glGetProcAddress
#define glGetProcAddress(name) wglGetProcAddress(name)
#endif
#else
#error "ruby::OpenGL: unsupported platform"
#error "ruby::OpenGL3: unsupported platform"
#endif
#include "bind.hpp"

View File

@ -1,6 +1,3 @@
//note: this driver works under Linux, but crashes with SIGSEGV under FreeBSD
//exact reason is unknown; but I suspect it's a bug in FreeBSD's SDL 1.2 package
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/Xv.h>
@ -97,10 +94,11 @@ private:
_bufferHeight = 0;
resize(_width = 256, _height = 256);
return true;
return _ready = true;
}
auto terminate() -> void {
_ready = false;
if(_buffer) SDL_FreeSurface(_buffer), _buffer = nullptr;
if(_screen) SDL_QuitSubSystem(SDL_INIT_VIDEO), _screen = nullptr;
if(_display) XCloseDisplay(_display), _display = nullptr;

View File

@ -4,153 +4,78 @@
#include <X11/extensions/Xv.h>
#include <X11/extensions/Xvlib.h>
extern "C" auto XvShmCreateImage(Display*, XvPortID, signed, char*, signed, signed, XShmSegmentInfo*) -> XvImage*;
extern "C" auto XvShmCreateImage(Display*, XvPortID, int, char*, int, int, XShmSegmentInfo*) -> XvImage*;
struct VideoXv : Video {
~VideoXv() { term(); }
VideoXv() { initialize(); }
~VideoXv() { terminate(); }
uint32_t* buffer = nullptr;
uint8_t* ytable = nullptr;
uint8_t* utable = nullptr;
uint8_t* vtable = nullptr;
auto ready() -> bool { return _ready; }
enum XvFormat : unsigned {
XvFormatRGB32,
XvFormatRGB24,
XvFormatRGB16,
XvFormatRGB15,
XvFormatYUY2,
XvFormatUYVY,
XvFormatUnknown,
};
auto context() -> uintptr { return _context; }
auto blocking() -> bool { return _blocking; }
struct {
Display* display = nullptr;
GC gc = 0;
Window window = 0;
Colormap colormap = 0;
XShmSegmentInfo shminfo;
signed port = -1;
signed depth = 0;
signed visualid = 0;
XvImage* image = nullptr;
XvFormat format = XvFormatUnknown;
uint32_t fourcc = 0;
unsigned width = 0;
unsigned height = 0;
} device;
struct {
Window handle = 0;
bool synchronize = false;
unsigned width = 0;
unsigned height = 0;
} settings;
auto cap(const string& name) -> bool {
if(name == Video::Handle) return true;
if(name == Video::Synchronize) {
Display* display = XOpenDisplay(nullptr);
bool result = XInternAtom(display, "XV_SYNC_TO_VBLANK", true) != None;
XCloseDisplay(display);
return result;
}
return false;
auto setContext(uintptr context) -> bool {
if(_context == context) return true;
_context = context;
return initialize();
}
auto get(const string& name) -> any {
if(name == Video::Handle) return settings.handle;
if(name == Video::Synchronize) return settings.synchronize;
return {};
auto setBlocking(bool blocking) -> bool {
if(_blocking == blocking) return true;
_blocking = blocking;
bool result = false;
Display* display = XOpenDisplay(nullptr);
Atom atom = XInternAtom(display, "XV_SYNC_TO_VBLANK", true);
if(atom != None && _port >= 0) {
XvSetPortAttribute(display, _port, atom, _blocking);
result = true;
}
XCloseDisplay(display);
return result;
}
auto set(const string& name, const any& value) -> bool {
if(name == Video::Handle && value.is<uintptr_t>()) {
settings.handle = value.get<uintptr_t>();
return true;
}
if(name == Video::Synchronize && value.is<bool>()) {
bool result = false;
Display* display = XOpenDisplay(nullptr);
Atom atom = XInternAtom(display, "XV_SYNC_TO_VBLANK", true);
if(atom != None && device.port >= 0) {
settings.synchronize = value.get<bool>();
XvSetPortAttribute(display, device.port, atom, settings.synchronize);
result = true;
}
XCloseDisplay(display);
return result;
}
return false;
auto clear() -> void {
memory::fill(_buffer, _bufferWidth * _bufferHeight * sizeof(uint32_t));
//clear twice in case video is double buffered ...
output();
output();
}
auto resize(unsigned width, unsigned height) -> void {
if(device.width >= width && device.height >= height) return;
device.width = max(width, device.width);
device.height = max(height, device.height);
XShmDetach(device.display, &device.shminfo);
shmdt(device.shminfo.shmaddr);
shmctl(device.shminfo.shmid, IPC_RMID, nullptr);
XFree(device.image);
delete[] buffer;
device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, device.width, device.height, &device.shminfo);
device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777);
device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0);
device.shminfo.readOnly = false;
XShmAttach(device.display, &device.shminfo);
buffer = new uint32_t[device.width * device.height];
}
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
if(width != settings.width || height != settings.height) {
resize(settings.width = width, settings.height = height);
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
if(width != _width || height != _height) {
resize(_width = width, _height = height);
}
pitch = device.width * 4;
return data = buffer;
pitch = _bufferWidth * 4;
return data = _buffer;
}
auto unlock() -> void {
}
auto clear() -> void {
memory::fill(buffer, device.width * device.height * sizeof(uint32_t));
//clear twice in case video is double buffered ...
refresh();
refresh();
}
auto refresh() -> void {
unsigned width = settings.width;
unsigned height = settings.height;
auto output() -> void {
uint width = _width;
uint height = _height;
XWindowAttributes target;
XGetWindowAttributes(device.display, device.window, &target);
XGetWindowAttributes(_display, _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);
XGetWindowAttributes(_display, (Window)_context, &parent);
if(target.width != parent.width || target.height != parent.height) {
XResizeWindow(device.display, device.window, parent.width, parent.height);
XResizeWindow(_display, _window, parent.width, parent.height);
}
//update target width and height attributes
XGetWindowAttributes(device.display, device.window, &target);
XGetWindowAttributes(_display, _window, &target);
switch(device.format) {
switch(_format) {
case XvFormatRGB32: renderRGB32(width, height); break;
case XvFormatRGB24: renderRGB24(width, height); break;
case XvFormatRGB16: renderRGB16(width, height); break;
@ -159,39 +84,42 @@ struct VideoXv : Video {
case XvFormatUYVY: renderUYVY (width, height); break;
}
XvShmPutImage(device.display, device.port, device.window, device.gc, device.image,
XvShmPutImage(_display, _port, _window, _gc, _image,
0, 0, width, height,
0, 0, target.width, target.height,
true);
}
auto init() -> bool {
device.display = XOpenDisplay(nullptr);
auto initialize() -> bool {
terminate();
if(!_context) return false;
if(!XShmQueryExtension(device.display)) {
fprintf(stderr, "VideoXv: XShm extension not found.\n");
_display = XOpenDisplay(nullptr);
if(!XShmQueryExtension(_display)) {
print("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++) {
_port = -1;
XvAdaptorInfo* adaptorInfo = nullptr;
uint adaptorCount = 0;
XvQueryAdaptors(_display, DefaultRootWindow(_display), &adaptorCount, &adaptorInfo);
for(uint n : range(adaptorCount)) {
//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;
if(adaptorInfo[n].num_formats < 1) continue;
if(!(adaptorInfo[n].type & XvInputMask)) continue;
if(!(adaptorInfo[n].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;
_port = adaptorInfo[n].base_id;
_depth = adaptorInfo[n].formats->depth;
_visualID = adaptorInfo[n].formats->visual_id;
break;
}
XvFreeAdaptorInfo(adaptor_info);
if(device.port < 0) {
fprintf(stderr, "VideoXv: failed to find valid XvPort.\n");
XvFreeAdaptorInfo(adaptorInfo);
if(_port < 0) {
print("VideoXv: failed to find valid XvPort.\n");
return false;
}
@ -199,272 +127,297 @@ struct VideoXv : Video {
//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);
XGetWindowAttributes(_display, (Window)_context, &window_attributes);
XVisualInfo visualtemplate;
visualtemplate.visualid = device.visualid;
visualtemplate.screen = DefaultScreen(device.display);
visualtemplate.depth = device.depth;
visualtemplate.visual = 0;
signed visualmatches = 0;
XVisualInfo* visualinfo = XGetVisualInfo(device.display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualtemplate, &visualmatches);
if(visualmatches < 1 || !visualinfo->visual) {
if(visualinfo) XFree(visualinfo);
fprintf(stderr, "VideoXv: unable to find Xv-compatible visual.\n");
XVisualInfo visualTemplate;
visualTemplate.visualid = _visualID;
visualTemplate.screen = DefaultScreen(_display);
visualTemplate.depth = _depth;
visualTemplate.visual = 0;
int visualMatches = 0;
XVisualInfo* visualInfo = XGetVisualInfo(_display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualTemplate, &visualMatches);
if(visualMatches < 1 || !visualInfo->visual) {
if(visualInfo) XFree(visualInfo);
print("VideoXv: unable to find Xv-compatible visual.\n");
return false;
}
device.colormap = XCreateColormap(device.display, settings.handle, visualinfo->visual, AllocNone);
_colormap = XCreateColormap(_display, (Window)_context, visualInfo->visual, AllocNone);
XSetWindowAttributes attributes;
attributes.colormap = device.colormap;
attributes.colormap = _colormap;
attributes.border_pixel = 0;
attributes.event_mask = StructureNotifyMask;
device.window = XCreateWindow(device.display, /* parent = */ settings.handle,
_window = XCreateWindow(_display, /* parent = */ (Window)_context,
/* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height,
/* border_width = */ 0, device.depth, InputOutput, visualinfo->visual,
/* border_width = */ 0, _depth, InputOutput, visualInfo->visual,
CWColormap | CWBorderPixel | CWEventMask, &attributes);
XFree(visualinfo);
XSetWindowBackground(device.display, device.window, /* color = */ 0);
XMapWindow(device.display, device.window);
XFree(visualInfo);
XSetWindowBackground(_display, _window, /* color = */ 0);
XMapWindow(_display, _window);
device.gc = XCreateGC(device.display, device.window, 0, 0);
_gc = XCreateGC(_display, _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);
Atom atom = XInternAtom(_display, "XV_AUTOPAINT_COLORKEY", true);
if(atom != None) XvSetPortAttribute(_display, _port, atom, 1);
//find optimal rendering format
device.format = XvFormatUnknown;
signed format_count;
XvImageFormatValues* format = XvListImageFormats(device.display, device.port, &format_count);
_format = XvFormatUnknown;
int formatCount = 0;
XvImageFormatValues* format = XvListImageFormats(_display, _port, &formatCount);
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;
if(_format == XvFormatUnknown) for(auto n : range(formatCount)) {
if(format[n].type == XvRGB && format[n].bits_per_pixel == 32) {
_format = XvFormatRGB32;
_fourCC = format[n].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;
if(_format == XvFormatUnknown) for(auto n : range(formatCount)) {
if(format[n].type == XvRGB && format[n].bits_per_pixel == 24) {
_format = XvFormatRGB24;
_fourCC = format[n].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;
if(_format == XvFormatUnknown) for(auto n : range(formatCount)) {
if(format[n].type == XvRGB && format[n].bits_per_pixel <= 16 && format[n].red_mask == 0xf800) {
_format = XvFormatRGB16;
_fourCC = format[n].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;
if(_format == XvFormatUnknown) for(auto n : range(formatCount)) {
if(format[n].type == XvRGB && format[n].bits_per_pixel <= 16 && format[n].red_mask == 0x7c00) {
_format = XvFormatRGB15;
_fourCC = format[n].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'
if(_format == XvFormatUnknown) for(auto n : range(formatCount)) {
if(format[n].type == XvYUV && format[n].bits_per_pixel == 16 && format[n].format == XvPacked) {
if(format[n].component_order[0] == 'Y' && format[n].component_order[1] == 'U'
&& format[n].component_order[2] == 'Y' && format[n].component_order[3] == 'V'
) {
device.format = XvFormatYUY2;
device.fourcc = format[i].id;
_format = XvFormatYUY2;
_fourCC = format[n].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'
if(_format == XvFormatUnknown) for(auto n : range(formatCount)) {
if(format[n].type == XvYUV && format[n].bits_per_pixel == 16 && format[n].format == XvPacked) {
if(format[n].component_order[0] == 'U' && format[n].component_order[1] == 'Y'
&& format[n].component_order[2] == 'V' && format[n].component_order[3] == 'Y'
) {
device.format = XvFormatUYVY;
device.fourcc = format[i].id;
_format = XvFormatUYVY;
_fourCC = format[n].id;
break;
}
}
}
free(format);
if(device.format == XvFormatUnknown) {
fprintf(stderr, "VideoXv: unable to find a supported image format.\n");
if(_format == XvFormatUnknown) {
print("VideoXv: unable to find a supported image format.\n");
return false;
}
device.width = 256;
device.height = 256;
_bufferWidth = 256;
_bufferHeight = 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");
_image = XvShmCreateImage(_display, _port, _fourCC, 0, _bufferWidth, _bufferHeight, &_shmInfo);
if(!_image) {
print("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");
_shmInfo.shmid = shmget(IPC_PRIVATE, _image->data_size, IPC_CREAT | 0777);
_shmInfo.shmaddr = _image->data = (char*)shmat(_shmInfo.shmid, 0, 0);
_shmInfo.readOnly = false;
if(!XShmAttach(_display, &_shmInfo)) {
print("VideoXv: XShmAttach failed.\n");
return false;
}
buffer = new uint32_t[device.width * device.height];
settings.width = 256;
settings.height = 256;
initTables();
_buffer = new uint32_t[_bufferWidth * _bufferHeight];
_width = 256;
_height = 256;
initializeTables();
clear();
return true;
return _ready = true;
}
auto term() -> void {
XShmDetach(device.display, &device.shminfo);
shmdt(device.shminfo.shmaddr);
shmctl(device.shminfo.shmid, IPC_RMID, nullptr);
XFree(device.image);
auto terminate() -> void {
_ready = false;
if(device.window) {
XUnmapWindow(device.display, device.window);
device.window = 0;
if(_image) {
XShmDetach(_display, &_shmInfo);
shmdt(_shmInfo.shmaddr);
shmctl(_shmInfo.shmid, IPC_RMID, nullptr);
XFree(_image);
_image = nullptr;
}
if(device.colormap) {
XFreeColormap(device.display, device.colormap);
device.colormap = 0;
if(_window) {
XUnmapWindow(_display, _window);
_window = 0;
}
if(device.display) {
XCloseDisplay(device.display);
device.display = nullptr;
if(_colormap) {
XFreeColormap(_display, _colormap);
_colormap = 0;
}
if(buffer) { delete[] buffer; buffer = nullptr; }
if(ytable) { delete[] ytable; ytable = nullptr; }
if(utable) { delete[] utable; utable = nullptr; }
if(vtable) { delete[] vtable; vtable = nullptr; }
if(_display) {
XCloseDisplay(_display);
_display = nullptr;
}
delete[] _buffer, _buffer = nullptr;
delete[] _ytable, _ytable = nullptr;
delete[] _utable, _utable = nullptr;
delete[] _vtable, _vtable = nullptr;
}
private:
auto renderRGB32(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint32_t* output = (uint32_t*)device.image->data;
auto resize(uint width, uint height) -> void {
if(_bufferWidth >= width && _bufferHeight >= height) return;
_bufferWidth = max(width, _bufferWidth);
_bufferHeight = max(height, _bufferHeight);
for(unsigned y = 0; y < height; y++) {
memcpy(output, input, width * 4);
input += device.width;
output += device.width;
XShmDetach(_display, &_shmInfo);
shmdt(_shmInfo.shmaddr);
shmctl(_shmInfo.shmid, IPC_RMID, nullptr);
XFree(_image);
_image = XvShmCreateImage(_display, _port, _fourCC, 0, _bufferWidth, _bufferHeight, &_shmInfo);
_shmInfo.shmid = shmget(IPC_PRIVATE, _image->data_size, IPC_CREAT | 0777);
_shmInfo.shmaddr = _image->data = (char*)shmat(_shmInfo.shmid, 0, 0);
_shmInfo.readOnly = false;
XShmAttach(_display, &_shmInfo);
delete[] _buffer;
_buffer = new uint32_t[_bufferWidth * _bufferHeight];
}
auto renderRGB32(uint width, uint height) -> void {
uint32_t* input = (uint32_t*)_buffer;
uint32_t* output = (uint32_t*)_image->data;
for(uint y : range(height)) {
memory::copy(output, input, width * 4);
input += _bufferWidth;
output += _bufferWidth;
}
}
auto renderRGB24(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint8_t* output = (uint8_t*)device.image->data;
auto renderRGB24(uint width, uint height) -> void {
uint32_t* input = (uint32_t*)_buffer;
uint8_t* output = (uint8_t*)_image->data;
for(unsigned y = 0; y < height; y++) {
for(unsigned x = 0; x < width; x++) {
for(uint y : range(height)) {
for(uint x : range(width)) {
uint32_t p = *input++;
*output++ = p;
*output++ = p >> 8;
*output++ = p >> 16;
}
input += (device.width - width);
output += (device.width - width) * 3;
input += (_bufferWidth - width);
output += (_bufferWidth - width) * 3;
}
}
auto renderRGB16(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint16_t* output = (uint16_t*)device.image->data;
auto renderRGB16(uint width, uint height) -> void {
uint32_t* input = (uint32_t*)_buffer;
uint16_t* output = (uint16_t*)_image->data;
for(unsigned y = 0; y < height; y++) {
for(unsigned x = 0; x < width; x++) {
for(uint y : range(height)) {
for(uint x : range(width)) {
uint32_t p = *input++;
*output++ = ((p >> 8) & 0xf800) | ((p >> 5) & 0x07e0) | ((p >> 3) & 0x001f); //RGB32->RGB16
}
input += device.width - width;
output += device.width - width;
input += _bufferWidth - width;
output += _bufferWidth - width;
}
}
auto renderRGB15(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint16_t* output = (uint16_t*)device.image->data;
auto renderRGB15(uint width, uint height) -> void {
uint32_t* input = (uint32_t*)_buffer;
uint16_t* output = (uint16_t*)_image->data;
for(unsigned y = 0; y < height; y++) {
for(unsigned x = 0; x < width; x++) {
for(uint y : range(height)) {
for(uint x : range(width)) {
uint32_t p = *input++;
*output++ = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x001f); //RGB32->RGB15
}
input += device.width - width;
output += device.width - width;
input += _bufferWidth - width;
output += _bufferWidth - width;
}
}
auto renderYUY2(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint16_t* output = (uint16_t*)device.image->data;
auto renderYUY2(uint width, uint height) -> void {
uint32_t* input = (uint32_t*)_buffer;
uint16_t* output = (uint16_t*)_image->data;
for(unsigned y = 0; y < height; y++) {
for(unsigned x = 0; x < width >> 1; x++) {
for(uint y : range(height)) {
for(uint x : range(width >> 1)) {
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;
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];
*output++ = (u << 8) | _ytable[p0];
*output++ = (v << 8) | _ytable[p1];
}
input += device.width - width;
output += device.width - width;
input += _bufferWidth - width;
output += _bufferWidth - width;
}
}
auto renderUYVY(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint16_t* output = (uint16_t*)device.image->data;
auto renderUYVY(uint width, uint height) -> void {
uint32_t* input = (uint32_t*)_buffer;
uint16_t* output = (uint16_t*)_image->data;
for(unsigned y = 0; y < height; y++) {
for(unsigned x = 0; x < width >> 1; x++) {
for(uint y : range(height)) {
for(uint x : range(width >> 1)) {
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;
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;
*output++ = (_ytable[p0] << 8) | u;
*output++ = (_ytable[p1] << 8) | v;
}
input += device.width - width;
output += device.width - width;
input += _bufferWidth - width;
output += _bufferWidth - width;
}
}
auto initTables() -> void {
ytable = new uint8_t[65536];
utable = new uint8_t[65536];
vtable = new uint8_t[65536];
auto initializeTables() -> void {
_ytable = new uint8_t[65536];
_utable = new uint8_t[65536];
_vtable = new uint8_t[65536];
for(unsigned i = 0; i < 65536; i++) {
for(uint n : range(65536)) {
//extract RGB565 color data from i
uint8_t r = (i >> 11) & 31, g = (i >> 5) & 63, b = (i) & 31;
uint8_t r = (n >> 11) & 31, g = (n >> 5) & 63, b = (n) & 31;
r = (r << 3) | (r >> 2); //R5->R8
g = (g << 2) | (g >> 4); //G6->G8
b = (b << 3) | (b >> 2); //B5->B8
@ -481,9 +434,48 @@ private:
//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;
_ytable[n] = y < 0 ? 0 : y > 255 ? 255 : y;
_utable[n] = u < 0 ? 0 : u > 255 ? 255 : u;
_vtable[n] = v < 0 ? 0 : v > 255 ? 255 : v;
}
}
bool _ready = false;
uintptr _context = 0;
bool _blocking = false;
uint _width = 0;
uint _height = 0;
uint32_t* _buffer = nullptr;
uint _bufferWidth = 0;
uint _bufferHeight = 0;
uint8_t* _ytable = nullptr;
uint8_t* _utable = nullptr;
uint8_t* _vtable = nullptr;
enum XvFormat : uint {
XvFormatRGB32,
XvFormatRGB24,
XvFormatRGB16,
XvFormatRGB15,
XvFormatYUY2,
XvFormatUYVY,
XvFormatUnknown,
};
Display* _display = nullptr;
GC _gc = 0;
Window _window = 0;
Colormap _colormap = 0;
XShmSegmentInfo _shmInfo;
int _port = -1;
int _depth = 0;
int _visualID = 0;
XvImage* _image = nullptr;
XvFormat _format = XvFormatUnknown;
uint32_t _fourCC = 0;
};