From aa4575e1aa768daff8ca10c7a8a06aa0603e998f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 31 Mar 2013 14:40:31 +0200 Subject: [PATCH 1/1] Initial commit --- Makefile | 15 ++++ eglbackend.cpp | 104 +++++++++++++++++++++++++++ eglbackend.h | 30 ++++++++ gltest.cpp | 185 +++++++++++++++++++++++++++++++++++++++++++++++++ glwindow.cpp | 136 ++++++++++++++++++++++++++++++++++++ glwindow.h | 62 +++++++++++++++++ glxbackend.cpp | 85 +++++++++++++++++++++++ glxbackend.h | 33 +++++++++ 8 files changed, 650 insertions(+) create mode 100644 Makefile create mode 100644 eglbackend.cpp create mode 100644 eglbackend.h create mode 100644 gltest.cpp create mode 100644 glwindow.cpp create mode 100644 glwindow.h create mode 100644 glxbackend.cpp create mode 100644 glxbackend.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3fc8800 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +FLAGS := -Wall + +COMMON_SRC = gltest.cpp glwindow.cpp +COMMON_HDR = glwindow.h + +all: glxtest egltest + +glxtest: $(COMMON_SRC) $(COMMON_HDR) glxbackend.cpp glxbackend.h + g++ $(FLAGS) -DUSE_GLX $(COMMON_SRC) glxbackend.cpp -lGL -lX11 -o glxtest + +egltest: $(COMMON_SRC) $(COMMON_HDR) eglbackend.cpp eglbackend.h + g++ $(FLAGS) -DUSE_EGL $(COMMON_SRC) eglbackend.cpp -lEGL -lGL -lX11 -o egltest + +clean: + rm -f glxtest egltest diff --git a/eglbackend.cpp b/eglbackend.cpp new file mode 100644 index 0000000..359d54a --- /dev/null +++ b/eglbackend.cpp @@ -0,0 +1,104 @@ +#include "eglbackend.h" + +#include +#include +#include + +#include + +static const char *eglErrorToString(EGLint e) +{ +#define CASE(name) case name: return #name + switch (e) { + CASE(EGL_SUCCESS); + CASE(EGL_NOT_INITIALIZED); + CASE(EGL_BAD_ACCESS); + CASE(EGL_BAD_ALLOC); + CASE(EGL_BAD_ATTRIBUTE); + CASE(EGL_BAD_CONTEXT); + CASE(EGL_BAD_CONFIG); + CASE(EGL_BAD_CURRENT_SURFACE); + CASE(EGL_BAD_DISPLAY); + default: return ""; + } +#undef CASE +} + +static void exitEglError(const char *what) +{ + EGLint e = eglGetError(); + fprintf(stderr, "EGL error %d (%s): %s\n", e, eglErrorToString(e), what); + exit(1); +} + +/* attributes for a double buffered visual in RGBA format with at least +* 4 bits per color */ +static const EGLint config_attribs[] = { + EGL_RED_SIZE, 4, + EGL_GREEN_SIZE, 4, + EGL_BLUE_SIZE, 4, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_NONE, +}; + +VisualID EGLBackend::initialize(Display *xDisplay) +{ + if (display == EGL_NO_DISPLAY) { // this implies that the context is also unitialized + // get connection and bind API + EGLint eglMajor, eglMinor; + display = eglGetDisplay(xDisplay); + if (display == EGL_NO_DISPLAY) + exitEglError("Failed to get EGL display"); + if (eglInitialize(display, &eglMajor, &eglMinor) == EGL_FALSE) + exitEglError("Failed to initialize EGL"); + printf("Using EGL version %d.%d\n", eglMajor, eglMinor); + eglBindAPI(EGL_OPENGL_API); + // get an appropriate config + EGLConfig configs[1]; + EGLint count; + if (eglChooseConfig(display, config_attribs, configs, 1, &count) == EGL_FALSE || count == 0) + exitEglError("Failed to choose config"); + config = configs[0]; + } + // return visual ID + EGLint val; + eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &val); + return (VisualID)val; +} + +void EGLBackend::createContext(Window window) +{ + assert(display != EGL_NO_DISPLAY && context == EGL_NO_CONTEXT); + surface = eglCreateWindowSurface(display, config, window, NULL); + /* create an EGL context and use it with the surface */ + context = eglCreateContext(display, config, EGL_NO_CONTEXT, NULL); + if (context == EGL_NO_CONTEXT) + exitEglError("Failed to create context"); + if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) + exitEglError("Failed to make context current"); +} + +EGLBackend::~EGLBackend() +{ + if (display != EGL_NO_DISPLAY) { + if (context != EGL_NO_CONTEXT) { + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(display, context); + eglDestroySurface(display, surface); + } + eglTerminate(display); + eglReleaseThread(); + } +} + +void EGLBackend::swapBuffers() const +{ + assert(context != EGL_NO_CONTEXT); // this implies the display is also initialized + eglSwapBuffers(display, surface); +} + +void EGLBackend::setSwapInterval(int i) const +{ + assert(context != EGL_NO_CONTEXT); + eglSwapInterval(display, i); +} diff --git a/eglbackend.h b/eglbackend.h new file mode 100644 index 0000000..17c3bcf --- /dev/null +++ b/eglbackend.h @@ -0,0 +1,30 @@ +#include "glwindow.h" + +#include + + +/** Make an X window fit for GL. You have to manage the X window yourself! */ +class EGLBackend : public GLBackend { +public: + /** Create a GL window class and find an appropriate visual */ + EGLBackend() : display(EGL_NO_DISPLAY), context(EGL_NO_CONTEXT) {} + /** Fre all resources */ + virtual ~EGLBackend(); + + /** Initialize GL backend, choose visual configuration and return the ID */ + virtual VisualID initialize(Display *display); + + /** create a GL context for the given window */ + virtual void createContext(Window window); + + /** Swap back and front buffers */ + virtual void swapBuffers() const; + + /** Set the swap interval */ + virtual void setSwapInterval(int i) const; +private: + EGLDisplay display; + EGLConfig config; + EGLSurface surface; + EGLContext context; +}; diff --git a/gltest.cpp b/gltest.cpp new file mode 100644 index 0000000..2b6105c --- /dev/null +++ b/gltest.cpp @@ -0,0 +1,185 @@ +// stdlib includes +#include +#include +#include +#include +#include +#include +#include +#include + +// include proper GL connector +#include "glwindow.h" +#if defined(USE_GLX) +#include "glxbackend.h" +GLBackend *createGLBackend() +{ + return new GLXBackend(); +} +#elif defined(USE_EGL) +#include "eglbackend.h" +GLBackend *createGLBackend() +{ + return new EGLBackend(); +} +#else +#error "No GL window type selected" +#endif + +// configuration +const GLfloat boxWidth = 0.045f; +const GLfloat boxSpeed = 1.25f; // per second + +// profiler +const int numProfilerStates = 5; +const char *profilerStateNames[numProfilerStates] = { "Pre-Render", "Drawing", "Swapping", "Post-Render", "Outside renderer"}; + +// utility functions +static double getTime() +{ + struct timespec tp; + clock_gettime(CLOCK_MONOTONIC, &tp); + return tp.tv_sec + 1e-9 * tp.tv_nsec; +} + +static void drawRect(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) +{ + glVertex2f(x1, y1); glVertex2f(x2, y1); glVertex2f(x2, y2); glVertex2f(x1, y2); +} + +// the window +class TearTestWindow : public GLWindow { +public: + TearTestWindow() : GLWindow(XOpenDisplay(0), createGLBackend()), boxPos(0), boxDirection(1) + {} + +protected: + virtual void initGL() + { + getBackend()->setSwapInterval(1); + // initialize GL proper + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glDisable(GL_DEPTH_TEST); + // initialize clocks + lastFrame = getTime(); + // initailize profiler + framect = 0; + memset(stateTime, 0, sizeof(stateTime)); + curState = -1; + lastDisplay = lastProfile = getTime(); + } + + virtual void resizeGL(unsigned int width, unsigned int height) + { + /* prevent divide-by-zero */ + if (height == 0) + height = 1; + glViewport(0, 0, width, height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho (0, 1, 1, 0, 0, 1); + glMatrixMode(GL_MODELVIEW); + glClear(GL_COLOR_BUFFER_BIT); + glFlush(); + } + + void profilerTick(int nextState) + { + assert (nextState >= 0 && nextState < numProfilerStates); + double time = getTime(); + if (curState >= 0) + stateTime[curState] += time-lastProfile; + curState = nextState; + lastProfile = time; + // display? + const double elapsed = time-lastDisplay; + if (elapsed >= 3) { + printf("%.1f fps, time spent: ", framect/elapsed); + for (int i = 0; i < numProfilerStates; ++i) { + if (i != 0) printf(", "); + printf("%s %.1f%%", profilerStateNames[i], stateTime[i]/elapsed*100); + } + printf("\n"); + lastDisplay = time; + framect = 0; + memset(stateTime, 0, sizeof(stateTime)); + } + } + + void renderGL() + { + profilerTick(0); + double time = getTime(); + // anim + double passedTime = time-lastFrame; + boxPos += boxSpeed*passedTime*boxDirection; + while (boxPos < 0 || boxPos+boxWidth > 1) { // wrapover + if (boxPos < 0) { + boxPos = -boxPos; + boxDirection = -boxDirection; + } + else { + boxPos = 1.0-boxWidth-(boxPos+boxWidth-1.0); + boxDirection = -boxDirection; + } + } + lastFrame = time; + // draw + //glFlush(); + profilerTick(1); + //glClear(GL_COLOR_BUFFER_BIT); + glBegin(GL_QUADS); + // clear manually + glColor3f(0.0f, 0.0f, 0.0f); + drawRect(0, 0, 1, 1); + glColor3f(0.8f, 1.0f, 0.75f); + drawRect(boxPos, 0, boxPos+boxWidth, 1); + glEnd(); + usleep(20*1000); + profilerTick(2); + getBackend()->swapBuffers(); +// glDrawBuffer(GL_FRONT); +// int xpos = 0; +// int ypos = 0; + //foreach (const QRect &r, region.rects()) { + // convert to OpenGL coordinates + //int y = displayHeight() - 0 - r.height(); +// glBitmap(0, 0, 0, 0, 0 - xpos, 0 - ypos, NULL); // not glRasterPos2f, see glxbackend.cpp +// xpos = 0; +// ypos = 0; +// glCopyPixels(0, 0, getWidth(), getHeight(), GL_COLOR); + //} +// glBitmap(0, 0, 0, 0, -xpos, -ypos, NULL); // move position back to 0,0 +// glDrawBuffer(GL_BACK); + profilerTick(3); + glFlush(); + ++framect; + profilerTick(4); + } + + virtual void handleKeyPress(KeySym key) + { + switch (key) { + case XK_Escape: close(); break; + case XK_F1: setFullscreen(!getFullscreen()); break; + default: break; + } + } + +private: + double lastFrame; + GLfloat boxPos, boxDirection; + // FPS + double lastDisplay, lastProfile; + int framect, curState; + double stateTime[numProfilerStates]; +}; + + + +int main(int argc, char ** argv) +{ + TearTestWindow w; + w.open(800, 600); + w.exec(); +} diff --git a/glwindow.cpp b/glwindow.cpp new file mode 100644 index 0000000..67e9b9a --- /dev/null +++ b/glwindow.cpp @@ -0,0 +1,136 @@ +#include "glwindow.h" + +#include +#include +#include +#include + +GLWindow::~GLWindow() +{ + delete backend; + if (window) + XDestroyWindow(display, window); + XCloseDisplay(display); +} + +void GLWindow::create(unsigned int width, unsigned int height) +{ + assert(!window); + // get visual from backend + XVisualInfo vinfo_template; + vinfo_template.visualid = backend->initialize(display); + int num_vinfo; + XVisualInfo *vi = XGetVisualInfo(display, VisualIDMask, &vinfo_template, &num_vinfo); + assert(num_vinfo == 1 && vi != NULL); + // set winattrs + XSetWindowAttributes winAttr; + winAttr.colormap = XCreateColormap(display, RootWindow(display, vi->screen), vi->visual, AllocNone); + winAttr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask; + winAttr.border_pixel = 0; + // create window + /*if (fullscreen) { + // get root window size + width = DisplayWidth(display, vi->screen); + height = DisplayHeight(display, vi->screen); + printf("Display size: %dx%d\n", width, height); + // set window attributes + winAttr.override_redirect = True; + window = XCreateWindow(display, RootWindow(display, vi->screen), + 0, 0, width, height, 0, vi->depth, InputOutput, vi->visual, + CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect, &winAttr); + XWarpPointer(display, None, window, 0, 0, 0, 0, 0, 0); + XMapRaised(display, window); + XGrabKeyboard(display, window, True, GrabModeAsync, GrabModeAsync, CurrentTime); + XGrabPointer(display, window, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, window, None, CurrentTime); + } + else {*/ + printf("Initial window size: %dx%d\n", width, height); + // create a window in window mode + window = XCreateWindow(display, RootWindow(display, vi->screen), + 0, 0, width, height, 0, vi->depth, InputOutput, vi->visual, + CWBorderPixel | CWColormap | CWEventMask, &winAttr); + /* handle wm_delete_events */ + Atom wmDelete = XInternAtom(display, "WM_DELETE_WINDOW", True); + XMapRaised(display, window); + XSetWMProtocols(display, window, &wmDelete, 1); + // done + this->fullscreen = false; + this->width = width; + this->height = height; + backend->createContext(window); + initGL(); + resizeGL(width, height); +} + +void GLWindow::close() +{ + if (!window) return; + XEvent ev; + memset(&ev, 0, sizeof (ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = window; + ev.xclient.message_type = XInternAtom(display, "WM_PROTOCOLS", true); + ev.xclient.format = 32; + ev.xclient.data.l[0] = XInternAtom(display, "WM_DELETE_WINDOW", false); + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(display, window, False, NoEventMask, &ev); +} + +void GLWindow::setFullscreen(bool fullscreen) +{ + assert(window); + // set fullscreen property + XEvent e; + e.xclient.type = ClientMessage; + e.xclient.window = window; + e.xclient.message_type = XInternAtom(display, "_NET_WM_STATE", true); + e.xclient.format = 32; + e.xclient.data.l[0] = 2; // _NET_WM_STATE_TOGGLE + e.xclient.data.l[1] = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", fullscreen); + e.xclient.data.l[2] = 0; // no second property to toggle + e.xclient.data.l[3] = 1; + e.xclient.data.l[4] = 0; + XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &e); +} + +void GLWindow::exec() +{ + while (true) + { + /* handle the events in the queue */ + while (XPending(display) > 0) + { + XEvent event; + XNextEvent(display, &event); + switch (event.type) + { + case Expose: + renderGL(); + break; + case ConfigureNotify: + if (event.xconfigure.width != this->width || event.xconfigure.height != this->height) { + printf("Window resized to: %dx%d\n", event.xconfigure.width, event.xconfigure.height); + this->width = event.xconfigure.width; + this->height = event.xconfigure.height; + resizeGL(this->width, this->height); + } + break; + case KeyPress: + handleKeyPress(XLookupKeysym(&event.xkey, 0)); + break; + case ClientMessage: + if (strcmp(XGetAtomName(display, event.xclient.message_type), "WM_PROTOCOLS") == 0 && + strcmp(XGetAtomName(display, event.xclient.data.l[0]), "WM_DELETE_WINDOW") == 0) + { + XDestroyWindow(display, window); + window = 0; + return; + } + break; + default: + break; + } + } + renderGL(); + } +} diff --git a/glwindow.h b/glwindow.h new file mode 100644 index 0000000..edd7b1f --- /dev/null +++ b/glwindow.h @@ -0,0 +1,62 @@ +#ifndef GL_WINDOW_H +#define GL_WINDOW_H + +#include + +/** Abstracts the GL binding API away */ +class GLBackend { +public: + /** Create a GL window class, but does not do anything */ + GLBackend() {} + /** Fre all resources */ + virtual ~GLBackend() {} + + /** Initialize GL backend, choose visual configuration and return the ID */ + virtual VisualID initialize(Display *display) = 0; + + /** create a GL context for the given window */ + virtual void createContext(Window window) = 0; + + /** Swap back and front buffers */ + virtual void swapBuffers() const = 0; + + /** Set the swap interval */ + virtual void setSwapInterval(int i) const = 0; +}; + +/** A window to render GL stuff in */ +class GLWindow { +public: + GLWindow(Display *display, GLBackend *backend) //!< Create the window class, but do not open it. Taks ownership of the backend and the X connection. + : display(display), backend(backend), window(0) {} + virtual ~GLWindow(); + + void open(unsigned int width, unsigned int height) { if (!window) create(width, height); } + void close(); + void setFullscreen(bool fullscreen); + bool getFullscreen(void) { return fullscreen; } + + int getWidth() { return width; } + int getHeight() { return height; } + + void exec(); + +protected: + const GLBackend * getBackend() { return backend; } + + virtual void initGL() = 0; + virtual void resizeGL(unsigned int width, unsigned int height) = 0; + virtual void renderGL() = 0; + virtual void handleKeyPress(KeySym key) = 0; + +private: + void create(unsigned int width, unsigned int height); + + Display *display; + GLBackend *backend; + Window window; + int width, height; + bool fullscreen; +}; + +#endif diff --git a/glxbackend.cpp b/glxbackend.cpp new file mode 100644 index 0000000..ff126eb --- /dev/null +++ b/glxbackend.cpp @@ -0,0 +1,85 @@ +#include "glxbackend.h" + +#include +#include +#include +#include + +/* attributes for a double buffered visual in RGBA format with at least +* 4 bits per color */ +static int attrList[] = +{ + GLX_RGBA, GLX_DOUBLEBUFFER, + GLX_RED_SIZE, 4, + GLX_GREEN_SIZE, 4, + GLX_BLUE_SIZE, 4, + None +}; + +VisualID GLXBackend::initialize(Display *display) +{ + if (this->display == NULL) { // this implies that the context is also unitialized + this->display = display; + // debugging: show version information + int glxMajor, glxMinor; + glXQueryVersion(display, &glxMajor, &glxMinor); + printf("Using GLX version %d.%d\n", glxMajor, glxMinor); + // check for swap control functions + const char *extensions = glXQueryExtensionsString(display, DefaultScreen(display)); + if (std::string(extensions).find("GLX_MESA_swap_control") != std::string::npos) { + funSwapIntervalMesa = (PFNGLXSWAPINTERVALMESAPROC)glXGetProcAddress((const GLubyte*)"glXSwapIntervalMESA"); + if (funSwapIntervalMesa) printf("glXSwapIntervalMESA is present\n"); + } + else { + funSwapIntervalMesa = 0; + } + if (std::string(extensions).find("GLX_EXT_swap_control") != std::string::npos) { + funSwapIntervalExt = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress((const GLubyte*)"glXSwapIntervalEXT"); + if (funSwapIntervalExt) printf("glXSwapIntervalEXT is present\n"); + } + else { + funSwapIntervalExt = 0; + } + // get the visual + vi = glXChooseVisual(display, DefaultScreen(display), attrList); + } + return vi->visualid; +} + +void GLXBackend::createContext(Window window) +{ + assert(display != NULL && context == None); + this->window = window; + // create context with that window + context = glXCreateContext(display, vi, 0, GL_TRUE); + glXMakeCurrent(display, window, context); + assert(glXIsDirect(display, context)); +} + +GLXBackend::~GLXBackend() +{ + if (display != NULL) { + if (context != None) { + glXMakeCurrent(display, None, NULL); + glXDestroyContext(display, context); + } + XFree(vi); + } +} + +void GLXBackend::swapBuffers() const +{ + assert(context != None); // this implies the display is also initialized + glXSwapBuffers(display, window); +} + +void GLXBackend::setSwapInterval(int i) const +{ + assert(context != None); + if (funSwapIntervalExt) + funSwapIntervalExt(display, window, i); + else if (funSwapIntervalMesa) + funSwapIntervalMesa(i); + else + assert(false && "At least one of glXSwapIntervalMESA, glXSwapIntervalEXT must be provided by the system"); +} diff --git a/glxbackend.h b/glxbackend.h new file mode 100644 index 0000000..3f24d48 --- /dev/null +++ b/glxbackend.h @@ -0,0 +1,33 @@ +#include "glwindow.h" + +#include + + +/** Make an X window fit for GL. You have to manage the X window yourself! */ +class GLXBackend : public GLBackend { +public: + /** Create a GL window class and find an appropriate visual */ + GLXBackend() : display(NULL), context(None) {} + /** Fre all resources */ + virtual ~GLXBackend(); + + /** Initialize GL backend, choose visual configuration and return the ID */ + virtual VisualID initialize(Display *display); + + /** create a GL context for the given window */ + virtual void createContext(Window window); + + /** Swap back and front buffers */ + virtual void swapBuffers() const; + + /** Set the swap interval */ + virtual void setSwapInterval(int i) const; +private: + Display *display; + XVisualInfo *vi; + GLXContext context; + Window window; + + PFNGLXSWAPINTERVALMESAPROC funSwapIntervalMesa; + PFNGLXSWAPINTERVALEXTPROC funSwapIntervalExt; +}; -- 2.30.2