bsnes/ruby/video/glx.cpp
Tim Allen e0815b55b9 Update to v094r28 release.
byuu says:

This WIP substantially restructures the ruby API for the first time
since that project started.

It is my hope that with this restructuring, destruction of the ruby
objects should now be deterministic, which should fix the crashing on
closing the emulator on Linux. We'll see I guess ... either way, it
removed two layers of wrappers from ruby, so it's a pretty nice code
cleanup.

It won't compile on Windows due to a few issues I didn't see until
uploading the WIP, too lazy to upload another. But I fixed all the
compilation issues locally, so it'll work on Windows again with the next
WIP (unless I break something else.)

(Kind of annoying that Linux defines glActiveTexture but Windows
doesn't.)
2015-06-20 15:44:05 +10:00

235 lines
7.7 KiB
C++

#include "opengl/opengl.hpp"
#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
struct VideoGLX : Video, OpenGL {
~VideoGLX() { term(); }
auto (*glXCreateContextAttribs)(Display*, GLXFBConfig, GLXContext, signed, const signed*) -> GLXContext = nullptr;
auto (*glXSwapInterval)(signed) -> signed = nullptr;
Display* display = nullptr;
signed screen = 0;
Window xwindow = 0;
Colormap colormap = 0;
GLXContext glxcontext = nullptr;
GLXWindow glxwindow = 0;
struct {
signed version_major = 0;
signed version_minor = 0;
bool doubleBuffer = false;
bool isDirect = false;
} glx;
struct {
Window handle = 0;
bool synchronize = false;
unsigned depth = 24;
unsigned filter = 1; //linear
string shader;
} settings;
auto cap(const string& name) -> bool {
if(name == Video::Handle) return true;
if(name == Video::Synchronize) return true;
if(name == Video::Depth) return true;
if(name == Video::Filter) return true;
if(name == Video::Shader) return true;
return false;
}
auto get(const string& name) -> any {
if(name == Video::Handle) return (uintptr_t)settings.handle;
if(name == Video::Synchronize) return settings.synchronize;
if(name == Video::Depth) return settings.depth;
if(name == Video::Filter) return settings.filter;
if(name == Video::Shader) return settings.shader;
return {};
}
auto set(const string& name, const any& value) -> bool {
if(name == Video::Handle && value.is<uintptr_t>()) {
settings.handle = value.get<uintptr_t>();
return true;
}
if(name == Video::Synchronize && value.is<bool>()) {
if(settings.synchronize != value.get<bool>()) {
settings.synchronize = value.get<bool>();
if(glXSwapInterval) glXSwapInterval(settings.synchronize);
return true;
}
}
if(name == Video::Depth && value.is<unsigned>()) {
unsigned depth = value.get<unsigned>();
if(depth > DefaultDepth(display, screen)) return false;
switch(depth) {
case 24: inputFormat = GL_RGBA8; break;
case 30: inputFormat = GL_RGB10_A2; break;
default: return false;
}
settings.depth = depth;
return true;
}
if(name == Video::Filter && value.is<unsigned>()) {
settings.filter = value.get<unsigned>();
if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST;
return true;
}
if(name == Video::Shader && value.is<string>()) {
settings.shader = value.get<string>();
OpenGL::shader(settings.shader);
if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST;
return true;
}
return false;
}
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
OpenGL::size(width, height);
return OpenGL::lock(data, pitch);
}
auto unlock() -> void {
}
auto clear() -> void {
OpenGL::clear();
if(glx.doubleBuffer) glXSwapBuffers(display, glxwindow);
}
auto refresh() -> void {
//we must ensure that the child window is the same size as the parent window.
//unfortunately, we cannot hook the parent window resize event notification,
//as we did not create the parent window, nor have any knowledge of the toolkit used.
//therefore, inelegant as it may be, we query each window size and resize as needed.
XWindowAttributes parent, child;
XGetWindowAttributes(display, settings.handle, &parent);
XGetWindowAttributes(display, xwindow, &child);
if(child.width != parent.width || child.height != parent.height) {
XResizeWindow(display, xwindow, parent.width, parent.height);
}
outputWidth = parent.width, outputHeight = parent.height;
OpenGL::refresh();
if(glx.doubleBuffer) glXSwapBuffers(display, glxwindow);
}
auto init() -> bool {
display = XOpenDisplay(0);
screen = DefaultScreen(display);
glXQueryVersion(display, &glx.version_major, &glx.version_minor);
//require GLX 1.2+ API
if(glx.version_major < 1 || (glx.version_major == 1 && glx.version_minor < 2)) return false;
XWindowAttributes window_attributes;
XGetWindowAttributes(display, settings.handle, &window_attributes);
//let GLX determine the best Visual to use for GL output; provide a few hints
//note: some video drivers will override double buffering attribute
signed attributeList[] = {
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DOUBLEBUFFER, True,
GLX_RED_SIZE, (signed)(settings.depth / 3),
GLX_GREEN_SIZE, (signed)(settings.depth / 3) + (signed)(settings.depth % 3),
GLX_BLUE_SIZE, (signed)(settings.depth / 3),
None
};
signed fbCount;
GLXFBConfig* fbConfig = glXChooseFBConfig(display, screen, attributeList, &fbCount);
if(fbCount == 0) return false;
XVisualInfo* vi = glXGetVisualFromFBConfig(display, fbConfig[0]);
//Window settings.handle has already been realized, most likely with DefaultVisual.
//GLX requires that the GL output window has the same Visual as the GLX context.
//it is not possible to change the Visual of an already realized (created) window.
//therefore a new child window, using the same GLX Visual, must be created and binded to settings.handle.
colormap = XCreateColormap(display, RootWindow(display, vi->screen), vi->visual, AllocNone);
XSetWindowAttributes attributes;
attributes.colormap = colormap;
attributes.border_pixel = 0;
xwindow = XCreateWindow(display, /* parent = */ settings.handle,
/* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height,
/* border_width = */ 0, vi->depth, InputOutput, vi->visual,
CWColormap | CWBorderPixel, &attributes);
XSetWindowBackground(display, xwindow, /* color = */ 0);
XMapWindow(display, xwindow);
XFlush(display);
//window must be realized (appear onscreen) before we make the context current
while(XPending(display)) {
XEvent event;
XNextEvent(display, &event);
}
glxcontext = glXCreateContext(display, vi, /* sharelist = */ 0, /* direct = */ GL_TRUE);
glXMakeCurrent(display, glxwindow = xwindow, glxcontext);
glXCreateContextAttribs = (GLXContext (*)(Display*, GLXFBConfig, GLXContext, signed, const signed*))glGetProcAddress("glXCreateContextAttribsARB");
glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalSGI");
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalMESA");
if(glXCreateContextAttribs) {
signed attributes[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 2,
None
};
GLXContext context = glXCreateContextAttribs(display, fbConfig[0], nullptr, true, attributes);
if(context) {
glXMakeCurrent(display, 0, nullptr);
glXDestroyContext(display, glxcontext);
glXMakeCurrent(display, glxwindow, glxcontext = context);
}
}
if(glXSwapInterval) {
glXSwapInterval(settings.synchronize);
}
//read attributes of frame buffer for later use, as requested attributes from above are not always granted
signed value = 0;
glXGetConfig(display, vi, GLX_DOUBLEBUFFER, &value);
glx.doubleBuffer = value;
glx.isDirect = glXIsDirect(display, glxcontext);
OpenGL::init();
return true;
}
auto term() -> void {
OpenGL::term();
if(glxcontext) {
glXDestroyContext(display, glxcontext);
glxcontext = nullptr;
}
if(xwindow) {
XUnmapWindow(display, xwindow);
xwindow = 0;
}
if(colormap) {
XFreeColormap(display, colormap);
colormap = 0;
}
if(display) {
XCloseDisplay(display);
display = nullptr;
}
}
};