From: Ralf Jung Date: Sun, 31 Mar 2013 12:40:31 +0000 (+0200) Subject: Initial commit X-Git-Url: https://git.ralfj.de/gltest.git/commitdiff_plain/aa4575e1aa768daff8ca10c7a8a06aa0603e998f Initial commit --- aa4575e1aa768daff8ca10c7a8a06aa0603e998f 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; +};