mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-04-02 10:42:14 -04:00
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.)
235 lines
7.7 KiB
C++
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;
|
|
}
|
|
}
|
|
};
|