proof-of-concept: OpenGL in emacs window

Well, this is more like a hack. It works only under X11, so windoze junkies are out of luck. Basically the trick is, obtain window id via emacs lisp, pass it to a C program. The C program will render stuff in the window.

Obligatory screenshot follows:


Now the code. First, the C part, that does actual rendering.

#if 0
gcc -o ./emacsgl $0 -lX11 -lGL -l GLU
exit
#endif
 
/* Xl-ib.h is the default header that is included and has the core functionallity */
#include <X11/Xlib.h>                                                              
/* Xatom.h includes functionallity for creating new protocol messages */           
#include <X11/Xatom.h>                                                             
/* keysym.h contains keysymbols which we use to resolv what keys that are being pressed */
#include <X11/keysym.h>                                                                   
 
/* printf */
#include <stdio.h>
 
/* the XF86 Video Mode extension allows us to change the displaymode of the server
 * this allows us to set the display to fullscreen and also read videomodes and   
 * other information.                                                             
 */                                                                               
#include <X11/extensions/xf86vmode.h>                                             
 
/* gl.h we need OpenGL :-) */
#include <GL/gl.h>           
/* this file is needed for X11 applications if we want to use hardware rendering */
#include <GL/glx.h>                                                                
#include <GL/glu.h>                                                                
 
#define WIDTH 640
#define HEIGHT 480
#define TITLE "EmacsGL"
 
typedef struct {
    Display *dpy;
    int screen;  
    Window win;  
    GLXContext ctx;
    XSetWindowAttributes attr;
    Bool fs;                  
    Bool doubleBuffered;      
    XF86VidModeModeInfo deskMode;
    int x, y;                    
    unsigned int width, height;  
    unsigned int depth;                                                                                                                                        
} GLWindow;                                                                                                                                                    
 
GLWindow GLWin;                                                                                                                                                
 
/* most important variable
 * it contains information about the X server which we communicate with
 */                                                                    
Display               * display;                                       
int                     screen;                                        
/* our window instance */                                              
Window                  window;                                        
GLXContext              context;                                       
XSetWindowAttributes    winAttr;
XWindowAttributes    emacsAttr;
Window                  emacsWindowId;
Bool                    fullscreen = False;                            
Bool                    doubleBuffered;                                
/* original desktop mode which we save so we can restore it later */   
XF86VidModeModeInfo     desktopMode;                                   
int                     x, y;                                          
unsigned int            width, height;                                 
unsigned int            depth;                                         
 
GLfloat                 rotQuad = 0.0f;
 
/* attributes for a single buffered visual in RGBA format 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 a double buffered visual in RGBA format 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                                                               
};                                                                     
 
/* prototypes */
void createWindow();
void destroyWindow();
void resizeGL(unsigned int, unsigned int);
void initGL();                            
 
/*
 * create a window
 */               
void createWindow()
{                  
    XVisualInfo *vi;
    Colormap cmap;  
    int i, dpyWidth, dpyHeight;
    int glxMajor, glxMinor, vmMajor, vmMinor;
    XF86VidModeModeInfo **modes;             
    int modeNum, bestMode;                   
    Atom wmDelete;                           
    Window winDummy;                         
    unsigned int borderDummy;                
 
    /* set best mode to current */
    bestMode = 0;                 
    /* get a connection */        
    display = XOpenDisplay(0);    
    screen = DefaultScreen(display);
    XF86VidModeQueryVersion(display, &vmMajor, &vmMinor);
    printf("XF86 VideoMode extension version %d.%d\n", vmMajor, vmMinor);
    XF86VidModeGetAllModeLines(display, screen, &modeNum, &modes);       
    /* save desktop-resolution before switching modes */                 
    GLWin.deskMode = *modes[0];
    desktopMode = *modes[0];                                      
    /* look for mode with requested resolution */                        
    for (i = 0; i < modeNum; i++)                                        
    {                                                                    
        if ((modes[i]->hdisplay == width) && (modes[i]->vdisplay == height))
            bestMode = i;                                                   
    }                                                                       
    /* get an appropriate visual */                                         
    vi = glXChooseVisual(display, screen, attrListDbl);                     
    if (vi == NULL)                                                         
    {                                                                       
        vi = glXChooseVisual(display, screen, attrListSgl);                 
        doubleBuffered = False;                                             
        printf("singlebuffered rendering will be used, no doublebuffering available\n");
    }                                                                                   
    else                                                                                
    {                                                                                   
        doubleBuffered = True;                                                          
        printf("doublebuffered rendering available\n");                                 
    }                                                                                   
    glXQueryVersion(display, &glxMajor, &glxMinor);                                     
    printf("GLX-Version %d.%d\n", glxMajor, glxMinor);                                  
    /* create a GLX context */                                                          
    context = glXCreateContext(display, vi, 0, GL_TRUE);                                
    /* create a color map */                                                            
    cmap = XCreateColormap(display, RootWindow(display, vi->screen),                    
        vi->visual, AllocNone);                                                         
    winAttr.colormap = cmap;                                                            
    winAttr.border_pixel = 0;                                                           
 
            /* create a window in window mode*/                                  
        winAttr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | 
            StructureNotifyMask;
	XGetWindowAttributes( display, emacsWindowId, &emacsAttr );
        window = XCreateWindow(display, emacsWindowId,     
            0, 0, emacsAttr.width, emacsAttr.height, 0, vi->depth, InputOutput, vi->visual,      
            CWBorderPixel | CWColormap | CWEventMask, &winAttr);             
        /* only set window title and handle wm_delete_events if in windowed mode */
        wmDelete = XInternAtom(display, "WM_DELETE_WINDOW", True);                 
        XSetWMProtocols(display, window, &wmDelete, 1);                            
        XSetStandardProperties(display, window, TITLE,                             
            TITLE, None, NULL, 0, NULL);                                           
        XMapRaised(display, window);                                               
 
    /* connect the glx-context to the window */                                    
    glXMakeCurrent(display, window, context);                                      
    if (glXIsDirect(display, context))                                             
        printf("DRI enabled\n");                                                   
    else                                                                           
        printf("no DRI available\n");                                              
    initGL();                                                                      
}                                                                                  
 
/*
 * destroy the window
 */                  
void destroyWindow() 
{                    
    if( context )    
    {                
        if( !glXMakeCurrent(display, None, NULL))
        {                                        
            printf("Could not release drawing context.\n");
        }                                                  
        /* destroy the context */                          
        glXDestroyContext(display, context);               
        context = NULL;                                    
    }                                                      
    /* switch back to original desktop resolution if we were in fullscreen */
    if( fullscreen )                                                         
    {                                                                        
        XF86VidModeSwitchToMode(display, screen, &desktopMode);              
        XF86VidModeSetViewPort(display, screen, 0, 0);                       
    }                                                                        
    XCloseDisplay(display);                                                  
}                                                                            
 
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();                                 
    gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 0.1f, 100.0f);
    glMatrixMode(GL_MODELVIEW);                                           
}                                                                         
 
void initGL()
{            
    glShadeModel(GL_SMOOTH);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClearDepth(1.0f);                  
    glEnable(GL_DEPTH_TEST);             
    glDepthFunc(GL_LEQUAL);              
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    /* we use resizeGL once to set up our initial perspective */
    resizeGL(width, height);                                    
    /* Reset the rotation angle of our object */                
    rotQuad = 0;                                                
    glFlush();                                                  
}                                                               
 
void renderGL()
{              
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();                                  
    glTranslatef(0.0f, 0.0f, -7.0f);                   
    glRotatef(rotQuad, 1.0f, 0.5f, 0.25f);             
    glBegin(GL_QUADS);                                 
        /* top of cube */                              
        glColor3f(0.0f, 1.0f, 0.0f);                   
        glVertex3f(1.0f, 1.0f, -1.0f);                 
        glVertex3f(-1.0f, 1.0f, -1.0f);                
        glVertex3f(-1.0f, 1.0f, 1.0f);                 
        glVertex3f(1.0f, 1.0f, 1.0f);                  
        /* bottom of cube */                           
        glColor3f(1.0f, 0.5f, 0.0f);                   
        glVertex3f(1.0f, -1.0f, 1.0f);                 
        glVertex3f(-1.0f, -1.0f, 1.0f);                
        glVertex3f(-1.0f, -1.0f, -1.0f);               
        glVertex3f(1.0f, -1.0f, -1.0f);                
        /* front of cube */                            
        glColor3f(1.0f, 0.0f, 0.0f);                   
        glVertex3f(1.0f, 1.0f, 1.0f);                  
        glVertex3f(-1.0f, 1.0f, 1.0f);                 
        glVertex3f(-1.0f, -1.0f, 1.0f);                
        glVertex3f(1.0f, -1.0f, 1.0f);                 
        /* back of cube */                             
        glColor3f(1.0f, 1.0f, 0.0f);                   
        glVertex3f(-1.0f, 1.0f, -1.0f);                
        glVertex3f(1.0f, 1.0f, -1.0f);                 
        glVertex3f(1.0f, -1.0f, -1.0f);                
        glVertex3f(-1.0f, -1.0f, -1.0f);               
        /* right side of cube */                       
        glColor3f(1.0f, 0.0f, 1.0f);                   
        glVertex3f(1.0f, 1.0f, -1.0f);                 
        glVertex3f(1.0f, 1.0f, 1.0f);                  
        glVertex3f(1.0f, -1.0f, 1.0f);                 
        glVertex3f(1.0f, -1.0f, -1.0f);                
        /* left side of cube */                        
        glColor3f(0.0f, 1.0f, 1.0f);                   
        glVertex3f(-1.0f, 1.0f, 1.0f);                 
        glVertex3f(-1.0f, 1.0f, -1.0f);                
        glVertex3f(-1.0f, -1.0f, -1.0f);               
        glVertex3f(-1.0f, -1.0f, 1.0f);                
    glEnd();                                           
    rotQuad += 0.1f;                                   
    /* swap the buffers if we have doublebuffered */   
    if (doubleBuffered)                                
    {                                                  
        glXSwapBuffers(display, window);               
    }                                                  
}                                                      
 
int main(int argc, char ** argv)
{                               
    XEvent event;               
    Bool done = False;
    if (argc < 2)
    {
	    printf ("Necromant's emacsGL proof-of-concept\n");
	    printf ("Usage:\t %s [window-id]\n", argv[0]);
	    printf ("Window is must be obtained from within emacs, lol. \n");
	    printf ("This code is subject to WTFPL, google for it\n");
	    exit(1);
    }
 
    width = WIDTH;
    height = HEIGHT;
    sscanf(argv[1],"%d",&emacsWindowId);
    printf("Emacs WinID: %d\n",emacsWindowId);
    createWindow();
 
    /* wait for events and eat up cpu. ;-) */
    while (!done)                            
    {                                        
        /* handle the events in the queue */ 
        while (XPending(display) > 0)        
        {                                    
            XNextEvent(display, &event);     
            switch (event.type)              
            {                                
                case Expose:                 
                        if (event.xexpose.count != 0)
                            break;                   
                    renderGL();                      
                        break;                       
                    case ConfigureNotify:            
                    /* call resizeGL only if our window-size changed */
                        if ((event.xconfigure.width != GLWin.width) ||
                            (event.xconfigure.height != GLWin.height))
                        {
                            width = event.xconfigure.width;
                            height = event.xconfigure.height;
                            resizeGL(width, height);
                        }
                        break;
                /* exit in case of a mouse button press */
                case ButtonPress:
                    done = True;
                    break;
                case KeyPress:
                    if (XLookupKeysym(&event.xkey, 0) == XK_Escape)
                    {
                        done = True;
                    }
                    if (XLookupKeysym(&event.xkey,0) == XK_F1)
                    {
                        destroyWindow();
                        fullscreen = !fullscreen;
                        createWindow();
                    }
                    break;
                case ClientMessage:
                    if (strcmp(XGetAtomName(display, event.xclient.message_type),
                               "WM_PROTOCOLS") == 0)
                    {
                        done = True;
                    }
                    break;
                default:
                    break;
            }
        }
        renderGL();
    }
 
    destroyWindow();
 
    return 0;
}

Save this somewhere, you can just run

wget -o emacsgl.c http://p.ncrmnt.org/1013/raw/
chmod +x emacsgl.c
./emacsgl.c

This will compile an emacsgl binary. Remember the path and put it in th following LISP
snipplet.

(shell-command 
 (concat "/full/path/to/emacsgl " 
	 (cdr (assoc 'window-id (frame-parameters (selected-frame))))))

Paste that into scratch buffer, press C-j, and enjoy.
This is only proof-of-concept code, with no actual use. Yet.

 

 

 

 

2 thoughts on “proof-of-concept: OpenGL in emacs window

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.