blob: a2dbdb354abb9eeb154159e7188b55cf5db1b03f [file] [log] [blame]
/**************************************************************************
*
* Copyright 2011 Jose Fonseca
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
**************************************************************************/
#include <assert.h>
#include <stdlib.h>
#include <limits.h>
#include <iostream>
#include "glproc.hpp"
#include "glws.hpp"
#include "glws_xlib.hpp"
namespace glws {
static bool has_GLX_VERSION_1_3 = false;
static bool has_GLX_ARB_create_context = false;
static bool has_GLX_ARB_create_context_profile = false;
static bool has_GLX_EXT_create_context_es_profile = false;
static bool has_GLX_EXT_create_context_es2_profile = false;
static bool has_GLX_EXT_swap_control = false;
static bool has_GLX_MESA_swap_control = false;
static bool has_GLX_OML_swap_method = false;
class GlxVisual : public Visual
{
public:
GLXFBConfig fbconfig;
XVisualInfo *visinfo;
GlxVisual(Profile prof) :
Visual(prof),
fbconfig(0),
visinfo(0)
{}
~GlxVisual() {
XFree(visinfo);
}
};
class GlxDrawable : public Drawable
{
public:
Window window = 0;
GLXDrawable drawable = 0;
GlxDrawable(const Visual *vis, int w, int h,
const glws::pbuffer_info *pbInfo) :
Drawable(vis, w, h, pbInfo ? true : false)
{
const GlxVisual *glxvisual = static_cast<const GlxVisual *>(visual);
XVisualInfo *visinfo = glxvisual->visinfo;
const char *name = "glretrace";
if (pbInfo) {
drawable = createPbuffer(display, glxvisual, pbInfo, w, h);
}
else {
window = createWindow(visinfo, name, width, height);
if (has_GLX_VERSION_1_3) {
drawable = glXCreateWindow(display, glxvisual->fbconfig, window, NULL);
if (has_GLX_EXT_swap_control) {
glXSwapIntervalEXT(display, drawable, 0);
}
} else {
drawable = window;
}
}
glXWaitX();
}
~GlxDrawable() {
if (pbuffer) {
glXDestroyPbuffer(display, drawable);
} else {
if (has_GLX_VERSION_1_3) {
glXDestroyWindow(display, drawable);
}
XDestroyWindow(display, window);
}
}
void
resize(int w, int h) override {
if (!window) {
return;
}
if (w == width && h == height) {
return;
}
glXWaitGL();
Drawable::resize(w, h);
resizeWindow(window, w, h);
glXWaitX();
}
void show(void) override {
if (!window ||
visible) {
return;
}
glXWaitGL();
showWindow(window);
glXWaitX();
Drawable::show();
}
void setName(const char *name) override {
if (!window) {
return;
}
setWindowName(window, name);
}
void copySubBuffer(int x, int y, int width, int height) override {
glXCopySubBufferMESA(display, drawable, x, y, width, height);
if (window) {
processKeys(window);
}
}
void swapBuffers(void) override {
assert(!pbuffer);
if (window &&
!has_GLX_EXT_swap_control &&
has_GLX_MESA_swap_control) {
glXSwapIntervalMESA(0);
}
glXSwapBuffers(display, drawable);
if (window) {
processKeys(window);
}
}
private:
Window createPbuffer(Display *dpy, const GlxVisual *visinfo,
const glws::pbuffer_info *pbInfo, int w, int h);
};
class GlxContext : public Context
{
public:
GLXContext context;
GlxContext(const Visual *vis, GLXContext ctx) :
Context(vis),
context(ctx)
{}
~GlxContext() {
glXDestroyContext(display, context);
}
};
#ifndef GLXBadFBConfig
#define GLXBadFBConfig 9
#endif
static int errorBase = INT_MIN;
static int eventBase = INT_MIN;
static int (*oldErrorHandler)(Display *, XErrorEvent *) = NULL;
static int
errorHandler(Display *dpy, XErrorEvent *error)
{
if (error->error_code == errorBase + GLXBadFBConfig) {
// Ignore, as we handle these.
return 0;
}
return oldErrorHandler(dpy, error);
}
void
init(void) {
initX();
int major = 0, minor = 0;
if (!glXQueryVersion(display, &major, &minor)) {
std::cerr << "error: failed to obtain GLX version\n";
exit(1);
}
const int requiredMajor = 1, requiredMinor = 2;
if (major < requiredMajor ||
(major == requiredMajor && minor < requiredMinor)) {
std::cerr << "error: GLX version " << requiredMajor << "." << requiredMinor << " required, but got version " << major << "." << minor << "\n";
exit(1);
}
has_GLX_VERSION_1_3 = major > 1 || (major == 1 && minor >= 3);
glXQueryExtension(display, &errorBase, &eventBase);
oldErrorHandler = XSetErrorHandler(errorHandler);
const char *extensions = glXQueryExtensionsString(display, screen);
#define CHECK_EXTENSION(name) \
has_##name = checkExtension(#name, extensions)
CHECK_EXTENSION(GLX_ARB_create_context);
CHECK_EXTENSION(GLX_ARB_create_context_profile);
CHECK_EXTENSION(GLX_EXT_create_context_es_profile);
CHECK_EXTENSION(GLX_EXT_create_context_es2_profile);
CHECK_EXTENSION(GLX_EXT_swap_control);
CHECK_EXTENSION(GLX_MESA_swap_control);
CHECK_EXTENSION(GLX_OML_swap_method);
#undef CHECK_EXTENSION
}
void
cleanup(void) {
XSetErrorHandler(oldErrorHandler);
oldErrorHandler = NULL;
cleanupX();
}
Visual *
createVisual(bool doubleBuffer, unsigned samples, Profile profile) {
GlxVisual *visual = new GlxVisual(profile);
Attributes<int> attribs;
attribs.add(GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT);
attribs.add(GLX_RENDER_TYPE, GLX_RGBA_BIT);
attribs.add(GLX_RED_SIZE, 8);
attribs.add(GLX_GREEN_SIZE, 8);
attribs.add(GLX_BLUE_SIZE, 8);
attribs.add(GLX_ALPHA_SIZE, 8);
attribs.add(GLX_DOUBLEBUFFER, doubleBuffer ? GL_TRUE : GL_FALSE);
attribs.add(GLX_DEPTH_SIZE, 1);
attribs.add(GLX_STENCIL_SIZE, 1);
if (samples > 1) {
attribs.add(GLX_SAMPLE_BUFFERS, 1);
attribs.add(GLX_SAMPLES_ARB, samples);
}
attribs.end();
int num_configs = 0;
GLXFBConfig * fbconfigs;
fbconfigs = glXChooseFBConfig(display, screen,
attribs,
&num_configs);
if (!num_configs || !fbconfigs) {
if (fbconfigs) {
XFree(fbconfigs);
}
return NULL;
}
visual->fbconfig = fbconfigs[0];
visual->visinfo = glXGetVisualFromFBConfig(display, visual->fbconfig);
if (has_GLX_OML_swap_method) {
XVisualInfo *visinfo;
int ret;
for (int i = 0; i < num_configs; ++i) {
if (glXGetFBConfigAttrib(display, fbconfigs[i], GLX_SWAP_METHOD_OML,
&ret) ||
ret != GLX_SWAP_COPY_OML) {
continue;
}
visinfo = glXGetVisualFromFBConfig(display, fbconfigs[i]);
/* We don't want the X 32-bit composite visual. */
if (visinfo->depth != 32) {
visual->fbconfig = fbconfigs[i];
visual->visinfo = visinfo;
break;
}
XFree(visinfo);
}
}
assert(visual->fbconfig);
assert(visual->visinfo);
return visual;
}
Drawable *
createDrawable(const Visual *visual, int width, int height,
const glws::pbuffer_info *pbInfo)
{
return new GlxDrawable(visual, width, height, pbInfo);
}
Context *
createContext(const Visual *_visual, Context *shareContext, bool debug)
{
const GlxVisual *visual = static_cast<const GlxVisual *>(_visual);
Profile profile = visual->profile;
GLXContext share_context = NULL;
GLXContext context;
if (shareContext) {
share_context = static_cast<GlxContext*>(shareContext)->context;
}
if (has_GLX_ARB_create_context) {
Attributes<int> attribs;
attribs.add(GLX_RENDER_TYPE, GLX_RGBA_TYPE);
int contextFlags = 0;
if (profile.api == glfeatures::API_GL) {
attribs.add(GLX_CONTEXT_MAJOR_VERSION_ARB, profile.major);
attribs.add(GLX_CONTEXT_MINOR_VERSION_ARB, profile.minor);
if (profile.versionGreaterOrEqual(3, 2)) {
if (!has_GLX_ARB_create_context_profile) {
std::cerr << "error: GLX_ARB_create_context_profile not supported\n";
return NULL;
}
int profileMask = profile.core ? GLX_CONTEXT_CORE_PROFILE_BIT_ARB : GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
attribs.add(GLX_CONTEXT_PROFILE_MASK_ARB, profileMask);
if (profile.forwardCompatible) {
contextFlags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
}
}
} else if (profile.api == glfeatures::API_GLES) {
if (has_GLX_EXT_create_context_es_profile) {
attribs.add(GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_ES_PROFILE_BIT_EXT);
attribs.add(GLX_CONTEXT_MAJOR_VERSION_ARB, profile.major);
attribs.add(GLX_CONTEXT_MINOR_VERSION_ARB, profile.minor);
} else if (profile.major < 2) {
std::cerr << "error: " << profile << " requested but GLX_EXT_create_context_es_profile not supported\n";
return NULL;
} else if (has_GLX_EXT_create_context_es2_profile) {
assert(profile.major >= 2);
if (profile.major != 2 || profile.minor != 0) {
// We might still get a ES 3.0 context later (in particular Mesa does this)
std::cerr << "warning: " << profile << " requested but GLX_EXT_create_context_es_profile not supported\n";
}
attribs.add(GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_ES2_PROFILE_BIT_EXT);
attribs.add(GLX_CONTEXT_MAJOR_VERSION_ARB, 2);
attribs.add(GLX_CONTEXT_MINOR_VERSION_ARB, 0);
} else {
std::cerr << "warning: " << profile << " requested but GLX_EXT_create_context_es_profile or GLX_EXT_create_context_es2_profile not supported\n";
}
} else {
assert(0);
}
if (debug) {
contextFlags |= GLX_CONTEXT_DEBUG_BIT_ARB;
}
if (contextFlags) {
attribs.add(GLX_CONTEXT_FLAGS_ARB, contextFlags);
}
attribs.end();
context = glXCreateContextAttribsARB(display, visual->fbconfig, share_context, True, attribs);
if (!context && debug) {
// XXX: Mesa has problems with GLX_CONTEXT_DEBUG_BIT_ARB with
// OpenGL ES contexts, so retry without it
return createContext(_visual, shareContext, false);
}
} else {
if (profile.api != glfeatures::API_GL ||
profile.core) {
return NULL;
}
context = glXCreateNewContext(display, visual->fbconfig, GLX_RGBA_TYPE, share_context, True);
}
if (!context) {
return NULL;
}
return new GlxContext(visual, context);
}
bool
makeCurrentInternal(Drawable *drawable, Drawable *readable, Context *context)
{
Window draw = None;
if (drawable) {
GlxDrawable *glxDrawable = static_cast<GlxDrawable *>(drawable);
draw = glxDrawable->drawable;
}
Window read = None;
if (readable) {
GlxDrawable *glxReadable = static_cast<GlxDrawable *>(readable);
read = glxReadable->drawable;
}
GLXContext ctx = nullptr;
if (context) {
GlxContext *glxContext = static_cast<GlxContext *>(context);
ctx = glxContext->context;
}
// We assume all GLX implementations support GLX 1.3 and the
// glXMakeContextCurrent() function. But only call it when needed.
if (read != draw)
return glXMakeContextCurrent(display, draw, read, ctx);
else
return glXMakeCurrent(display, draw, ctx);
}
Window
GlxDrawable::createPbuffer(Display *dpy, const GlxVisual *visinfo,
const glws::pbuffer_info *pbInfo, int w, int h)
{
int samples = 0;
int doubleBuffer = 0;
// XXX ideally, we'd populate these attributes according to the Visual info
Attributes<int> attribs;
attribs.add(GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT);
attribs.add(GLX_RENDER_TYPE, GLX_RGBA_BIT);
attribs.add(GLX_RED_SIZE, 1);
attribs.add(GLX_GREEN_SIZE, 1);
attribs.add(GLX_BLUE_SIZE, 1);
attribs.add(GLX_ALPHA_SIZE, 1);
glXGetFBConfigAttrib(dpy, visinfo->fbconfig, GLX_DOUBLEBUFFER,
&doubleBuffer);
attribs.add(GLX_DOUBLEBUFFER, doubleBuffer ? GL_TRUE : GL_FALSE);
attribs.add(GLX_DEPTH_SIZE, 1);
attribs.add(GLX_STENCIL_SIZE, 1);
if (samples > 1) {
attribs.add(GLX_SAMPLE_BUFFERS, 1);
attribs.add(GLX_SAMPLES_ARB, samples);
}
attribs.end();
int num_configs = 0;
GLXFBConfig *fbconfigs;
fbconfigs = glXChooseFBConfig(dpy, screen, attribs, &num_configs);
if (!num_configs || !fbconfigs) {
std::cerr << "error: glXChooseFBConfig for pbuffer failed.\n";
exit(1);
}
Attributes<int> pbAttribs;
pbAttribs.add(GLX_PBUFFER_WIDTH, w);
pbAttribs.add(GLX_PBUFFER_HEIGHT, h);
pbAttribs.add(GLX_PRESERVED_CONTENTS, True);
pbAttribs.end();
GLXDrawable pbuffer = glXCreatePbuffer(dpy, fbconfigs[0], pbAttribs);
if (!pbuffer) {
std::cerr << "error: glXCreatePbuffer() failed\n";
exit(1);
}
return pbuffer;
}
// For GLX, we implement wglBindTexARB() as a copy operation.
// We copy the pbuffer image to the currently bound texture.
// If there's any rendering to the pbuffer before the wglReleaseTexImage()
// call, the results are undefined (and permitted by the extension spec).
//
// The spec says that glTexImage and glCopyTexImage calls which effect
// the pbuffer/texture should not be allowed, but we do not enforce that.
//
// The spec says that when a pbuffer is released from the texture that
// the contents do not have to be preserved. But that's what will happen
// since we're copying here.
bool
bindTexImage(Drawable *pBuffer, int iBuffer) {
GLint readBufSave;
GLint width, height;
assert(pBuffer->pbuffer);
// Save the current drawing surface and bind the pbuffer surface
GLXDrawable prevDrawable = glXGetCurrentDrawable();
GLXContext prevContext = glXGetCurrentContext();
GlxDrawable *glxPBuffer = static_cast<GlxDrawable *>(pBuffer);
glXMakeCurrent(display, glxPBuffer->drawable, prevContext);
glGetIntegerv(GL_READ_BUFFER, &readBufSave);
assert(iBuffer == GL_FRONT_LEFT ||
iBuffer == GL_BACK_LEFT ||
iBuffer == GL_FRONT_RIGHT ||
iBuffer == GL_BACK_RIGHT ||
iBuffer == GL_AUX0);
// set src buffer
glReadBuffer(iBuffer);
// Just copy image from pbuffer to texture
switch (pBuffer->pbInfo.texTarget) {
case GL_TEXTURE_1D:
glGetTexLevelParameteriv(GL_TEXTURE_1D, pBuffer->mipmapLevel,
GL_TEXTURE_WIDTH, &width);
if (width == pBuffer->width) {
// replace existing texture
glCopyTexSubImage1D(GL_TEXTURE_1D,
pBuffer->mipmapLevel,
0, // xoffset
0, 0, // x, y
pBuffer->width);
} else {
// define initial texture
glCopyTexImage1D(GL_TEXTURE_1D,
pBuffer->mipmapLevel,
pBuffer->pbInfo.texFormat,
0, 0, // x, y
pBuffer->width, 0);
}
break;
case GL_TEXTURE_2D:
glGetTexLevelParameteriv(GL_TEXTURE_2D, pBuffer->mipmapLevel,
GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, pBuffer->mipmapLevel,
GL_TEXTURE_HEIGHT, &height);
if (width == pBuffer->width && height == pBuffer->height) {
// replace existing texture
glCopyTexSubImage2D(GL_TEXTURE_2D,
pBuffer->mipmapLevel,
0, 0, // xoffset, yoffset
0, 0, // x, y
pBuffer->width, pBuffer->height);
} else {
// define initial texture
glCopyTexImage2D(GL_TEXTURE_2D,
pBuffer->mipmapLevel,
pBuffer->pbInfo.texFormat,
0, 0, // x, y
pBuffer->width, pBuffer->height, 0);
}
break;
case GL_TEXTURE_CUBE_MAP:
{
const GLenum target =
GL_TEXTURE_CUBE_MAP_POSITIVE_X + pBuffer->cubeFace;
glGetTexLevelParameteriv(target, pBuffer->mipmapLevel,
GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(target, pBuffer->mipmapLevel,
GL_TEXTURE_HEIGHT, &height);
if (width == pBuffer->width && height == pBuffer->height) {
// replace existing texture
glCopyTexSubImage2D(target,
pBuffer->mipmapLevel,
0, 0, // xoffset, yoffset
0, 0, // x, y
pBuffer->width, pBuffer->height);
} else {
// define new texture
glCopyTexImage2D(target,
pBuffer->mipmapLevel,
pBuffer->pbInfo.texFormat,
0, 0, // x, y
pBuffer->width, pBuffer->height, 0);
}
}
break;
default:
; // no op
}
// restore
glReadBuffer(readBufSave);
// rebind previous drawing surface
glXMakeCurrent(display, prevDrawable, prevContext);
return true;
}
bool
releaseTexImage(Drawable *pBuffer, int iBuffer) {
assert(pBuffer->pbuffer);
// nothing to do here.
return true;
}
bool
setPbufferAttrib(Drawable *pBuffer, const int *attribList) {
assert(pBuffer->pbuffer);
// Nothing to do here. retrace_wglSetPbufferAttribARB() will have parsed
// and saved the mipmap/cubeface info in the Drawable.
std::cout << "Calling GLX setPbufferAttrib\n";
return true;
}
} /* namespace glws */