From 284e4c043e091f7f201c4c53df96df49c58cdfad Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 20 Jul 2017 21:52:47 +1000 Subject: [PATCH] Update to v103r18 release. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 :| --- higan/emulator/emulator.hpp | 2 +- higan/target-tomoko/GNUmakefile | 4 +- higan/target-tomoko/settings/audio.cpp | 66 ++- higan/target-tomoko/settings/settings.hpp | 2 +- ruby/audio/wasapi.cpp | 88 +++- ruby/video/direct3d.cpp | 1 + ruby/video/glx2.cpp | 272 ++++++----- ruby/video/opengl/opengl.hpp | 10 +- ruby/video/sdl.cpp | 6 +- ruby/video/xv.cpp | 556 +++++++++++----------- 10 files changed, 526 insertions(+), 481 deletions(-) diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 61656efd..6c109a6a 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -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/"; diff --git a/higan/target-tomoko/GNUmakefile b/higan/target-tomoko/GNUmakefile index 91e86567..7eca2faa 100644 --- a/higan/target-tomoko/GNUmakefile +++ b/higan/target-tomoko/GNUmakefile @@ -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 diff --git a/higan/target-tomoko/settings/audio.cpp b/higan/target-tomoko/settings/audio.cpp index 476a01d9..5e122c38 100644 --- a/higan/target-tomoko/settings/audio.cpp +++ b/higan/target-tomoko/settings/audio.cpp @@ -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(); - } - } -} diff --git a/higan/target-tomoko/settings/settings.hpp b/higan/target-tomoko/settings/settings.hpp index fe9636f3..ae9fc436 100644 --- a/higan/target-tomoko/settings/settings.hpp +++ b/higan/target-tomoko/settings/settings.hpp @@ -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 { diff --git a/ruby/audio/wasapi.cpp b/ruby/audio/wasapi.cpp index 50932fe0..10fde467 100644 --- a/ruby/audio/wasapi.cpp +++ b/ruby/audio/wasapi.cpp @@ -4,6 +4,7 @@ #include #include #include +#include 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 _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; diff --git a/ruby/video/direct3d.cpp b/ruby/video/direct3d.cpp index 60bf3ba2..9aa514ec 100644 --- a/ruby/video/direct3d.cpp +++ b/ruby/video/direct3d.cpp @@ -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; } diff --git a/ruby/video/glx2.cpp b/ruby/video/glx2.cpp index d9e3b410..cdd411b3 100644 --- a/ruby/video/glx2.cpp +++ b/ruby/video/glx2.cpp @@ -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 + #include + #ifndef glGetProcAddress + #define glGetProcAddress(name) (*glXGetProcAddress)((const GLubyte*)(name)) + #endif +#elif defined(DISPLAY_QUARTZ) + #include +#elif defined(DISPLAY_WINDOWS) + #include + #include + #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()) { - settings.handle = value.get(); - return true; - } + auto setSmooth(bool smooth) -> bool { + if(_smooth == smooth) return true; + _smooth = smooth; + return true; + } - if(name == Video::Synchronize && value.is()) { - if(settings.synchronize != value.get()) { - settings.synchronize = value.get(); - if(glXSwapInterval) glXSwapInterval(settings.synchronize); - return true; - } - } - - if(name == Video::Filter && value.is()) { - settings.filter = value.get(); - 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; }; diff --git a/ruby/video/opengl/opengl.hpp b/ruby/video/opengl/opengl.hpp index 797f3fa7..d917f860 100644 --- a/ruby/video/opengl/opengl.hpp +++ b/ruby/video/opengl/opengl.hpp @@ -1,15 +1,19 @@ #if defined(DISPLAY_XORG) #include #include - #define glGetProcAddress(name) (*glXGetProcAddress)((const GLubyte*)(name)) + #ifndef glGetProcAddress + #define glGetProcAddress(name) (*glXGetProcAddress)((const GLubyte*)(name)) + #endif #elif defined(DISPLAY_QUARTZ) #include #elif defined(DISPLAY_WINDOWS) #include #include - #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" diff --git a/ruby/video/sdl.cpp b/ruby/video/sdl.cpp index 710a28b4..598e3c5b 100644 --- a/ruby/video/sdl.cpp +++ b/ruby/video/sdl.cpp @@ -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 #include #include @@ -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; diff --git a/ruby/video/xv.cpp b/ruby/video/xv.cpp index 723ecf03..75f1cc9c 100644 --- a/ruby/video/xv.cpp +++ b/ruby/video/xv.cpp @@ -4,153 +4,78 @@ #include #include -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()) { - settings.handle = value.get(); - return true; - } - - if(name == Video::Synchronize && value.is()) { - 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(); - 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; };