From 43083a0ca80a876286b6a6ef39ec6f10c9b6f9bf Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 28 Apr 2012 23:34:25 +0000 Subject: Initial import. --- pugl.pc.in | 10 +++ pugl/pugl.h | 154 ++++++++++++++++++++++++++++++++ pugl/pugl_internal.h | 92 ++++++++++++++++++++ pugl/pugl_x11.c | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++ pugl_test.c | 88 +++++++++++++++++++ waf | Bin 0 -> 92192 bytes wscript | 126 +++++++++++++++++++++++++++ 7 files changed, 712 insertions(+) create mode 100644 pugl.pc.in create mode 100644 pugl/pugl.h create mode 100644 pugl/pugl_internal.h create mode 100644 pugl/pugl_x11.c create mode 100644 pugl_test.c create mode 100755 waf create mode 100644 wscript diff --git a/pugl.pc.in b/pugl.pc.in new file mode 100644 index 0000000..d3606cd --- /dev/null +++ b/pugl.pc.in @@ -0,0 +1,10 @@ +prefix=@PREFIX@ +exec_prefix=@EXEC_PREFIX@ +libdir=@LIBDIR@ +includedir=@INCLUDEDIR@ + +Name: Pugl +Version: @PUGL_VERSION@ +Description: Lightweight portable OpenGL API +Libs: -L${libdir} -l@LIB_PUGL@ +Cflags: -I${includedir}/pugl-@PUGL_MAJOR_VERSION@ diff --git a/pugl/pugl.h b/pugl/pugl.h new file mode 100644 index 0000000..853a754 --- /dev/null +++ b/pugl/pugl.h @@ -0,0 +1,154 @@ +/* + Copyright 2012 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file pugl.h API for Pugl, a portable micro-framework for GL UIs. +*/ + +#ifndef PUGL_H_INCLUDED +#define PUGL_H_INCLUDED + +#include + +#ifdef __cplusplus +extern "C" { +#else +# include +#endif + +typedef struct PuglWindowImpl PuglWindow; + +/** + A native window handle. + + For X11, this is a Window. +*/ +typedef intptr_t PuglNativeWindow; + +typedef enum { + PUGL_SUCCESS = 0 +} PuglStatus; + +/** + Handle for opaque user data. +*/ +typedef void* PuglHandle; + +typedef void (*PuglCloseFunc)(PuglWindow* handle); +typedef void (*PuglDisplayFunc)(PuglWindow* handle); +typedef void (*PuglKeyboardFunc)(PuglWindow* handle, bool press, uint32_t key); +typedef void (*PuglMotionFunc)(PuglWindow* handle, int x, int y); +typedef void (*PuglMouseFunc)(PuglWindow* handle, + int button, int state, + int x, int y); +typedef void (*PuglReshapeFunc)(PuglWindow* handle, int width, int height); + +/** + Create a new GL window. + @param parent Parent window, or 0 for top level. + @param title Window title, or NULL. + @param width Window width in pixels. + @param height Window height in pixels. +*/ +PuglWindow* +puglCreate(PuglNativeWindow parent, const char* title, int width, int height); + +/** + Set the handle to be passed to all callbacks. + + This is generally a pointer to a struct which contains all necessary state. + Everything needed in callbacks should be here, not in static variables. + + Note the lack of this facility makes GLUT unsuitable for plugins or + non-trivial programs; this mistake is largely why Pugl exists. +*/ +void +puglSetHandle(PuglWindow* window, PuglHandle handle); + +/** + Get the handle to be passed to all callbacks. +*/ +PuglHandle +puglGetHandle(PuglWindow* window); + +/** + Set the function to call when the window is closed. +*/ +void +puglSetCloseFunc(PuglWindow* window, PuglCloseFunc closeFunc); + +/** + Set the display function which should draw the UI using GL. +*/ +void +puglSetDisplayFunc(PuglWindow* window, PuglDisplayFunc displayFunc); + +/** + Set the function to call on keyboard events. +*/ +void +puglSetKeyboardFunc(PuglWindow* window, PuglKeyboardFunc keyboardFunc); + +/** + Set the function to call on mouse motion. +*/ +void +puglSetMotionFunc(PuglWindow* window, PuglMotionFunc motionFunc); + +/** + Set the function to call on mouse button events. +*/ +void +puglSetMouseFunc(PuglWindow* window, PuglMouseFunc mouseFunc); + +/** + Set the function to call when the window size changes. +*/ +void +puglSetReshapeFunc(PuglWindow* window, PuglReshapeFunc reshapeFunc); + +/** + Return the native window handle. +*/ +PuglNativeWindow +puglGetNativeWindow(PuglWindow* win); + +/** + Process all pending window events. + + This handles input events as well as rendering, so it should be called + regularly and rapidly enough to keep the UI responsive. +*/ +PuglStatus +puglProcessEvents(PuglWindow* win); + +/** + Request a redisplay on the next call to puglProcessEvents(). +*/ +void +puglPostRedisplay(PuglWindow* win); + +/** + Destroy a GL window. +*/ +void +puglDestroy(PuglWindow* win); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PUGL_H_INCLUDED */ diff --git a/pugl/pugl_internal.h b/pugl/pugl_internal.h new file mode 100644 index 0000000..8a7b217 --- /dev/null +++ b/pugl/pugl_internal.h @@ -0,0 +1,92 @@ +/* + Copyright 2012 David Robillard + Copyright 2011-2012 Ben Loftis, Harrison Consoles + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file pugl_internal.h Private platform-independent definitions. + + Note this file contains function definitions, so it must be compiled into + the final binary exactly once. Each platform specific implementation file + including it once should achieve this. +*/ + +#include "pugl.h" + +typedef struct PuglPlatformDataImpl PuglPlatformData; + +struct PuglWindowImpl { + PuglHandle handle; + PuglCloseFunc closeFunc; + PuglDisplayFunc displayFunc; + PuglKeyboardFunc keyboardFunc; + PuglMotionFunc motionFunc; + PuglMouseFunc mouseFunc; + PuglReshapeFunc reshapeFunc; + + PuglPlatformData* impl; + + int width; + int height; + bool redisplay; +}; + +void +puglSetHandle(PuglWindow* window, PuglHandle handle) +{ + window->handle = handle; +} + +PuglHandle +puglGetHandle(PuglWindow* window) +{ + return window->handle; +} + +void +puglSetCloseFunc(PuglWindow* window, PuglCloseFunc closeFunc) +{ + window->closeFunc = closeFunc; +} + +void +puglSetDisplayFunc(PuglWindow* window, PuglDisplayFunc displayFunc) +{ + window->displayFunc = displayFunc; +} + +void +puglSetKeyboardFunc(PuglWindow* window, PuglKeyboardFunc keyboardFunc) +{ + window->keyboardFunc = keyboardFunc; +} + +void +puglSetMotionFunc(PuglWindow* window, PuglMotionFunc motionFunc) +{ + window->motionFunc = motionFunc; +} + +void +puglSetMouseFunc(PuglWindow* window, PuglMouseFunc mouseFunc) +{ + window->mouseFunc = mouseFunc; +} + +void +puglSetReshapeFunc(PuglWindow* window, PuglReshapeFunc reshapeFunc) +{ + window->reshapeFunc = reshapeFunc; +} diff --git a/pugl/pugl_x11.c b/pugl/pugl_x11.c new file mode 100644 index 0000000..33f0b42 --- /dev/null +++ b/pugl/pugl_x11.c @@ -0,0 +1,242 @@ +/* + Copyright 2012 David Robillard + Copyright 2011-2012 Ben Loftis, Harrison Consoles + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "pugl_internal.h" + +struct PuglPlatformDataImpl { + Display* display; + int screen; + Window win; + GLXContext ctx; + Bool doubleBuffered; +}; + +/** + Attributes for single-buffered RGBA with at least + 4 bits per color and a 16 bit depth buffer. +*/ +static int attrListSgl[] = { + GLX_RGBA, + GLX_RED_SIZE, 4, + GLX_GREEN_SIZE, 4, + GLX_BLUE_SIZE, 4, + GLX_DEPTH_SIZE, 16, + None +}; + +/** + Attributes for double-buffered RGBA with at least + 4 bits per color and a 16 bit depth buffer. +*/ +static int attrListDbl[] = { + GLX_RGBA, GLX_DOUBLEBUFFER, + GLX_RED_SIZE, 4, + GLX_GREEN_SIZE, 4, + GLX_BLUE_SIZE, 4, + GLX_DEPTH_SIZE, 16, + None +}; + +PuglWindow* +puglCreate(PuglNativeWindow parent, const char* title, int width, int height) +{ + PuglWindow* win = (PuglWindow*)calloc(1, sizeof(PuglWindow)); + + win->impl = (PuglPlatformData*)calloc(1, sizeof(PuglPlatformData)); + + PuglPlatformData* impl = win->impl; + + win->width = width; + win->height = height; + impl->display = XOpenDisplay(0); + impl->screen = DefaultScreen(impl->display); + + XVisualInfo* vi = glXChooseVisual(impl->display, impl->screen, attrListDbl); + if (vi == NULL) { + vi = glXChooseVisual(impl->display, impl->screen, attrListSgl); + impl->doubleBuffered = False; + printf("singlebuffered rendering will be used, no doublebuffering available\n"); + } else { + impl->doubleBuffered = True; + printf("doublebuffered rendering available\n"); + } + + int glxMajor, glxMinor; + glXQueryVersion(impl->display, &glxMajor, &glxMinor); + printf("GLX-Version %d.%d\n", glxMajor, glxMinor); + + impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE); + + Window xParent = parent + ? (Window)parent + : RootWindow(impl->display, impl->screen); + + Colormap cmap = XCreateColormap( + impl->display, xParent, vi->visual, AllocNone); + + XSetWindowAttributes attr; + memset(&attr, 0, sizeof(XSetWindowAttributes)); + attr.colormap = cmap; + attr.border_pixel = 0; + + attr.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask + | ButtonPressMask | PointerMotionMask | StructureNotifyMask; + + impl->win = XCreateWindow( + impl->display, xParent, + 0, 0, win->width, win->height, 0, vi->depth, InputOutput, vi->visual, + CWBorderPixel | CWColormap | CWEventMask, &attr); + + if (title) { + XStoreName(impl->display, impl->win, title); + } + + if (!parent) { + Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True); + XSetWMProtocols(impl->display, impl->win, &wmDelete, 1); + } + + XMapRaised(impl->display, impl->win); + + if (glXIsDirect(impl->display, impl->ctx)) { + printf("DRI enabled\n"); + } else { + printf("no DRI available\n"); + } + + return win; +} + +void +puglDestroy(PuglWindow* win) +{ + if (win->impl->ctx) { + if (!glXMakeCurrent(win->impl->display, None, NULL)) { + printf("Could not release drawing context.\n"); + } + /* destroy the context */ + glXDestroyContext(win->impl->display, win->impl->ctx); + win->impl->ctx = NULL; + } + + XCloseDisplay(win->impl->display); + free(win); +} + +void +puglDisplay(PuglWindow* win) +{ + glXMakeCurrent(win->impl->display, win->impl->win, win->impl->ctx); + glViewport(0, 0, win->width, win->height); + + if (win->displayFunc) { + win->displayFunc(win); + } + + if (win->impl->doubleBuffered) { + glXSwapBuffers(win->impl->display, win->impl->win); + } +} + +PuglStatus +puglProcessEvents(PuglWindow* win) +{ + XEvent event; + + /* handle the events in the queue */ + while (XPending(win->impl->display) > 0) { + XNextEvent(win->impl->display, &event); + switch (event.type) { + case Expose: + if (event.xexpose.count != 0) { + break; + } + puglDisplay(win); + win->redisplay = false; + break; + case ConfigureNotify: + if ((event.xconfigure.width != win->width) || + (event.xconfigure.height != win->height)) { + if (win->reshapeFunc) { + win->reshapeFunc(win, + event.xconfigure.width, + event.xconfigure.height); + } + } + break; + case MotionNotify: + if (win->motionFunc) { + win->motionFunc(win, event.xmotion.x, event.xmotion.y); + } + break; + case ButtonPress: + if (win->mouseFunc) { + win->mouseFunc(win, + event.xbutton.button, event.xbutton.state, + event.xbutton.x, event.xbutton.y); + } + break; + case KeyPress: + case KeyRelease: + if (win->keyboardFunc) { + KeySym sym = XKeycodeToKeysym( + win->impl->display, event.xkey.keycode, 0); + win->keyboardFunc(win, event.type == KeyPress, sym); + } + break; + case ClientMessage: + if (!strcmp(XGetAtomName(win->impl->display, + event.xclient.message_type), + "WM_PROTOCOLS")) { + if (win->closeFunc) { + win->closeFunc(win); + } + } + break; + default: + break; + } + } + + if (win->redisplay) { + puglDisplay(win); + } + + return PUGL_SUCCESS; +} + +void +puglPostRedisplay(PuglWindow* win) +{ + win->redisplay = true; +} + +PuglNativeWindow +puglGetNativeWindow(PuglWindow* win) +{ + return win->impl->win; +} diff --git a/pugl_test.c b/pugl_test.c new file mode 100644 index 0000000..3122a89 --- /dev/null +++ b/pugl_test.c @@ -0,0 +1,88 @@ +/* + Copyright 2012 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file pugl_test.c A simple Pugl test that creates a top-level window. +*/ + +#include + +#include "GL/gl.h" +#include "pugl/pugl.h" + +static int quit = 0; +static float xAngle = 0.0f; +static float yAngle = 0.0f; + +#define KEY_ESCAPE 27 + +static void +onDisplay(PuglWindow* win) +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glLoadIdentity(); + + glRotatef(xAngle, 1.0f, 0.0f, 0.0f); + glRotatef(xAngle, 0.0f, 1.0f, 0.0f); + + glBegin(GL_TRIANGLES); + glColor3f(1.0f, 0.0f, 0.0f); + glVertex3f(0.0f, 1.0f, 0.0f); + glColor3f(0.0f, 1.0f, 0.0f); + glVertex3f(-1.0f, -1.0f, 0.0f); + glColor3f(0.0f, 0.0f, 1.0f); + glVertex3f(1.0f, -1.0f, 0.0f); + glEnd(); +} + +static void +onKeyboard(PuglWindow* win, bool press, uint32_t key) +{ + if (key == 'q' || key == KEY_ESCAPE) { + quit = 1; + } +} + +static void +onMotion(PuglWindow* win, int x, int y) +{ + xAngle = x % 360; + yAngle = y % 360; + puglPostRedisplay(win); +} + +static void +onClose(PuglWindow* win) +{ + quit = 1; +} + +int +main(int argc, char** argv) +{ + PuglWindow* win = puglCreate(0, "Pugl Test", 512, 512); + puglSetKeyboardFunc(win, onKeyboard); + puglSetMotionFunc(win, onMotion); + puglSetDisplayFunc(win, onDisplay); + puglSetCloseFunc(win, onClose); + + while (!quit) { + puglProcessEvents(win); + } + + puglDestroy(win); + return 0; +} diff --git a/waf b/waf new file mode 100755 index 0000000..32ae229 Binary files /dev/null and b/waf differ diff --git a/wscript b/wscript new file mode 100644 index 0000000..1bf5b07 --- /dev/null +++ b/wscript @@ -0,0 +1,126 @@ +#!/usr/bin/env python +import glob +import os +import sys + +from waflib.extras import autowaf as autowaf +import waflib.Logs as Logs, waflib.Options as Options + +# Version of this package (even if built as a child) +PUGL_VERSION = '0.0.0' +PUGL_MAJOR_VERSION = '0' + +# Library version (UNIX style major, minor, micro) +# major increment <=> incompatible changes +# minor increment <=> compatible changes (additions) +# micro increment <=> no interface changes +# Pugl uses the same version number for both library and package +PUGL_LIB_VERSION = PUGL_VERSION + +# Variables for 'waf dist' +APPNAME = 'pugl' +VERSION = PUGL_VERSION + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + autowaf.set_options(opt) + opt.add_option('--test', action='store_true', default=False, dest='build_tests', + help="Build unit tests") + opt.add_option('--static', action='store_true', default=False, dest='static', + help="Build static library") + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + autowaf.display_header('Pugl Configuration') + + if conf.env['MSVC_COMPILER']: + conf.env.append_unique('CFLAGS', ['-TP', '-MD']) + else: + conf.env.append_unique('CFLAGS', '-std=c99') + + conf.env['BUILD_TESTS'] = Options.options.build_tests + conf.env['BUILD_STATIC'] = (Options.options.build_tests or + Options.options.static) + + autowaf.define(conf, 'PUGL_VERSION', PUGL_VERSION) + conf.write_config_header('pugl_config.h', remove=False) + + conf.env['INCLUDES_PUGL'] = ['%s/pugl-%s' % (conf.env['INCLUDEDIR'], + PUGL_MAJOR_VERSION)] + conf.env['LIBPATH_PUGL'] = [conf.env['LIBDIR']] + conf.env['LIB_PUGL'] = ['pugl-%s' % PUGL_MAJOR_VERSION]; + + autowaf.display_msg(conf, "Static library", str(conf.env['BUILD_STATIC'])) + autowaf.display_msg(conf, "Unit tests", str(conf.env['BUILD_TESTS'])) + print('') + +def build(bld): + # C Headers + includedir = '${INCLUDEDIR}/pugl-%s/pugl' % PUGL_MAJOR_VERSION + bld.install_files(includedir, bld.path.ant_glob('pugl/*.h')) + + # Pkgconfig file + autowaf.build_pc(bld, 'PUGL', PUGL_VERSION, PUGL_MAJOR_VERSION, [], + {'PUGL_MAJOR_VERSION' : PUGL_MAJOR_VERSION}) + + libflags = [ '-fvisibility=hidden' ] + libs = [ 'X11', 'GL' ] + defines = [] + if bld.env['MSVC_COMPILER']: + libflags = [] + libs = [] + defines = ['snprintf=_snprintf'] + + lib_source = ['./pugl/pugl_x11.c'] + + # Shared Library + obj = bld(features = 'c cshlib', + export_includes = ['.'], + source = lib_source, + includes = ['.', './src'], + lib = libs, + name = 'libpugl', + target = 'pugl-%s' % PUGL_MAJOR_VERSION, + vnum = PUGL_LIB_VERSION, + install_path = '${LIBDIR}', + defines = defines, + cflags = libflags + [ '-DPUGL_SHARED', + '-DPUGL_INTERNAL' ]) + + # Static library + if bld.env['BUILD_STATIC']: + obj = bld(features = 'c cstlib', + export_includes = ['.'], + source = lib_source, + includes = ['.', './src'], + lib = libs, + name = 'libpugl_static', + target = 'pugl-%s' % PUGL_MAJOR_VERSION, + vnum = PUGL_LIB_VERSION, + install_path = '${LIBDIR}', + defines = defines, + cflags = ['-DPUGL_INTERNAL']) + + if bld.env['BUILD_TESTS']: + test_libs = libs + test_cflags = [''] + + # Unit test program + obj = bld(features = 'c cprogram', + source = 'pugl_test.c', + includes = ['.', './src'], + use = 'libpugl_static', + lib = test_libs, + target = 'pugl_test', + install_path = '', + defines = defines, + cflags = test_cflags) + +def lint(ctx): + subprocess.call('cpplint.py --filter=+whitespace/comments,-whitespace/tab,-whitespace/braces,-whitespace/labels,-build/header_guard,-readability/casting,-readability/todo,-build/include src/* pugl/*', shell=True) + -- cgit v1.2.1