Example of cooperative rendering into one window by two processes
authorBrian <brian.paul@tungstengraphics.com>
Fri, 12 Oct 2007 00:25:12 +0000 (18:25 -0600)
committerBrian <brian.paul@tungstengraphics.com>
Fri, 12 Oct 2007 00:25:12 +0000 (18:25 -0600)
progs/xdemos/Makefile
progs/xdemos/corender.c [new file with mode: 0644]
progs/xdemos/ipc.c [new file with mode: 0644]
progs/xdemos/ipc.h [new file with mode: 0644]

index 4c4ecac38ac5f7869b81c76af7c239c5b034b498..a7ba9afcac7f50eea2e1a33852de8f995f389d1c 100644 (file)
@@ -8,7 +8,9 @@ INCDIR = $(TOP)/include
 
 LIB_DEP = $(TOP)/$(LIB_DIR)/$(GL_LIB_NAME) $(TOP)/$(LIB_DIR)/$(GLU_LIB_NAME)
 
-PROGS = glthreads \
+PROGS = \
+       corender \
+       glthreads \
        glxdemo \
        glxgears \
        glxgears_fbconfig \
@@ -83,3 +85,11 @@ xuserotfont.o: xuserotfont.c xuserotfont.h
 xrotfontdemo.o: xrotfontdemo.c xuserotfont.h
        $(CC) -c -I. -I$(INCDIR) $(X11_INCLUDES) $(CFLAGS) xrotfontdemo.c
 
+corender: corender.o ipc.o
+       $(CC) $(CFLAGS) corender.o ipc.o $(APP_LIB_DEPS) -o $@
+
+corender.o: corender.c ipc.h
+       $(CC) -c -I. -I$(INCDIR) $(X11_INCLUDES) $(CFLAGS) corender.c
+
+ipc.o: ipc.c ipc.h
+       $(CC) -c -I. -I$(INCDIR) $(X11_INCLUDES) $(CFLAGS) ipc.c
\ No newline at end of file
diff --git a/progs/xdemos/corender.c b/progs/xdemos/corender.c
new file mode 100644 (file)
index 0000000..02e4ac0
--- /dev/null
@@ -0,0 +1,396 @@
+/**
+ * Example of cooperative rendering into one window by two processes.
+ * The first instance of the program creates the GLX window.
+ * The second instance of the program gets the window ID from the first
+ * and draws into it.
+ * Socket IPC is used for synchronization.
+ *
+ * Usage:
+ * 1. run 'corender &'
+ * 2. run 'corender 2'  (any arg will do)
+ *
+ * Brian Paul
+ * 11 Oct 2007
+ */
+
+
+#include <GL/gl.h>
+#include <GL/glx.h>
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/keysym.h>
+#include <unistd.h>
+#include "ipc.h"
+
+
+static int MyID = 0;  /* 0 or 1 */
+static int WindowID = 0;
+static GLXContext Context = 0;
+static int Width = 700, Height = 350;
+static int Rot = 0;
+static int Sock = 0;
+
+static GLfloat Red[4] = {1.0, 0.2, 0.2, 1.0};
+static GLfloat Blue[4] = {0.2, 0.2, 1.0, 1.0};
+
+static int Sync = 1;  /** synchronized rendering? */
+
+
+static void
+setup_ipc(void)
+{
+   int k, port = 10001;
+
+   if (MyID == 0) {
+      /* I'm the first one, wait for connection from second */
+      k = CreatePort(&port);
+      assert(k != -1);
+
+      printf("Waiting for connection from another 'corender'\n");
+      Sock = AcceptConnection(k);
+
+      printf("Got connection, sending windowID\n");
+
+      /* send windowID */
+      SendData(Sock, &WindowID, sizeof(WindowID));
+   }
+   else {
+      /* I'm the second one, connect to first */
+      char hostname[1000];
+
+      MyHostName(hostname, 1000);
+      Sock = Connect(hostname, port);
+      assert(Sock != -1);
+
+      /* get windowID */
+      ReceiveData(Sock, &WindowID, sizeof(WindowID));
+      printf("Contacted first 'corender', getting WindowID\n");
+   }
+}
+
+
+
+/** from GLUT */
+static void
+doughnut(GLfloat r, GLfloat R, GLint nsides, GLint rings)
+{
+  int i, j;
+  GLfloat theta, phi, theta1;
+  GLfloat cosTheta, sinTheta;
+  GLfloat cosTheta1, sinTheta1;
+  GLfloat ringDelta, sideDelta;
+
+  ringDelta = 2.0 * M_PI / rings;
+  sideDelta = 2.0 * M_PI / nsides;
+
+  theta = 0.0;
+  cosTheta = 1.0;
+  sinTheta = 0.0;
+  for (i = rings - 1; i >= 0; i--) {
+    theta1 = theta + ringDelta;
+    cosTheta1 = cos(theta1);
+    sinTheta1 = sin(theta1);
+    glBegin(GL_QUAD_STRIP);
+    phi = 0.0;
+    for (j = nsides; j >= 0; j--) {
+      GLfloat cosPhi, sinPhi, dist;
+
+      phi += sideDelta;
+      cosPhi = cos(phi);
+      sinPhi = sin(phi);
+      dist = R + r * cosPhi;
+
+      glNormal3f(cosTheta1 * cosPhi, -sinTheta1 * cosPhi, sinPhi);
+      glVertex3f(cosTheta1 * dist, -sinTheta1 * dist, r * sinPhi);
+      glNormal3f(cosTheta * cosPhi, -sinTheta * cosPhi, sinPhi);
+      glVertex3f(cosTheta * dist, -sinTheta * dist,  r * sinPhi);
+    }
+    glEnd();
+    theta = theta1;
+    cosTheta = cosTheta1;
+    sinTheta = sinTheta1;
+  }
+}
+
+
+static void
+redraw(Display *dpy)
+{
+   int dbg = 0;
+
+   glXMakeCurrent(dpy, WindowID, Context);
+   glEnable(GL_LIGHTING);
+   glEnable(GL_LIGHT0);
+   glEnable(GL_DEPTH_TEST);
+   glClearColor(0.5, 0.5, 0.5, 0.0);
+
+   if (MyID == 0) {
+      /* First process */
+
+      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+      glPushMatrix();
+      glTranslatef(-1, 0, 0);
+      glRotatef(Rot, 1, 0, 0);
+      glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Red);
+      doughnut(0.5, 2.0, 20, 30);
+      glPopMatrix();
+
+      glFinish();
+      if (!Sync) {
+         usleep(1000*10);
+      }
+
+      /* signal second process to render */
+      if (Sync) {
+         int code = 1;
+         if (dbg) printf("0: send signal\n");
+         SendData(Sock, &code, sizeof(code));
+         SendData(Sock, &Rot, sizeof(Rot));
+      }
+
+      /* wait for second process to finish rendering */
+      if (Sync) {
+         int code = 0;
+         if (dbg) printf("0: wait signal\n");
+         ReceiveData(Sock, &code, sizeof(code));
+         if (dbg) printf("0: got signal\n");
+         assert(code == 2);
+      }
+
+   }
+   else {
+      /* Second process */
+
+      /* wait for first process's signal for me to render */
+      if (Sync) {
+         int code = 0;
+         if (dbg) printf("1: wait signal\n");
+         ReceiveData(Sock, &code, sizeof(code));
+         ReceiveData(Sock, &Rot, sizeof(Rot));
+
+         if (dbg) printf("1: got signal\n");
+         assert(code == 1);
+      }
+
+      /* XXX this clear should not be here, but for some reason, it
+       * makes things _mostly_ work correctly w/ NVIDIA's driver.
+       * There's only occasional glitches.
+       * Without this glClear(), depth buffer for the second process
+       * is pretty much broken.
+       */
+      //glClear(GL_DEPTH_BUFFER_BIT);
+
+      glPushMatrix();
+      glTranslatef(1, 0, 0);
+      glRotatef(Rot + 90 , 1, 0, 0);
+      glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Blue);
+      doughnut(0.5, 2.0, 20, 30);
+      glPopMatrix();
+      glFinish();
+
+      glXSwapBuffers(dpy, WindowID);
+      usleep(1000*10);
+
+      /* signal first process that I'm done rendering */
+      if (Sync) {
+         int code = 2;
+         if (dbg) printf("1: send signal\n");
+         SendData(Sock, &code, sizeof(code));
+      }
+   }
+}
+
+
+static void
+resize(Display *dpy, int width, int height)
+{
+   float ar = (float) width / height;
+
+   glXMakeCurrent(dpy, WindowID, Context);
+
+   glViewport(0, 0, width, height);
+   glMatrixMode(GL_PROJECTION);
+   glLoadIdentity();
+   glFrustum(-ar, ar, 1.0, -1.0, 5.0, 200.0);
+   glMatrixMode(GL_MODELVIEW);
+   glLoadIdentity();
+   glTranslatef(0, 0, -15);
+
+   Width = width;
+   Height = height;
+}
+
+
+
+static void
+set_window_title(Display *dpy, Window win, const char *title)
+{
+   XSizeHints sizehints;
+   sizehints.flags = 0;
+   XSetStandardProperties(dpy, win, title, title,
+                          None, (char **)NULL, 0, &sizehints);
+}
+
+
+static Window
+make_gl_window(Display *dpy, XVisualInfo *visinfo, int width, int height)
+{
+   int scrnum;
+   XSetWindowAttributes attr;
+   unsigned long mask;
+   Window root;
+   Window win;
+   int x = 0, y = 0;
+   char *name = NULL;
+
+   scrnum = DefaultScreen( dpy );
+   root = RootWindow( dpy, scrnum );
+
+   /* window attributes */
+   attr.background_pixel = 0;
+   attr.border_pixel = 0;
+   attr.colormap = XCreateColormap( dpy, root, visinfo->visual, AllocNone);
+   attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
+   mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
+
+   win = XCreateWindow( dpy, root, x, y, width, height,
+                       0, visinfo->depth, InputOutput,
+                       visinfo->visual, mask, &attr );
+
+   /* set hints and properties */
+   {
+      XSizeHints sizehints;
+      sizehints.x = x;
+      sizehints.y = y;
+      sizehints.width  = width;
+      sizehints.height = height;
+      sizehints.flags = USSize | USPosition;
+      XSetNormalHints(dpy, win, &sizehints);
+      XSetStandardProperties(dpy, win, name, name,
+                              None, (char **)NULL, 0, &sizehints);
+   }
+
+   return win;
+}
+
+
+static void
+set_event_mask(Display *dpy, Window win)
+{
+   XSetWindowAttributes attr;
+   attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
+   XChangeWindowAttributes(dpy, win, CWEventMask, &attr);
+}
+
+
+static void
+event_loop(Display *dpy)
+{
+   while (1) {
+      while (XPending(dpy) > 0) {
+         XEvent event;
+         XNextEvent(dpy, &event);
+
+         switch (event.type) {
+         case Expose:
+            redraw(dpy);
+            break;
+         case ConfigureNotify:
+            resize(dpy, event.xconfigure.width, event.xconfigure.height);
+            break;
+         case KeyPress:
+            {
+               char buffer[10];
+               int r, code;
+               code = XLookupKeysym(&event.xkey, 0);
+               if (code == XK_Left) {
+               }
+               else {
+                  r = XLookupString(&event.xkey, buffer, sizeof(buffer),
+                                    NULL, NULL);
+                  if (buffer[0] == 27) {
+                     exit(0);
+                  }
+               }
+            }
+         default:
+            /* nothing */
+            ;
+         }
+      }
+
+      if (MyID == 0 || !Sync)
+         Rot += 1;
+      redraw(dpy);
+   }
+}
+
+
+static XVisualInfo *
+choose_visual(Display *dpy)
+{
+   int attribs[] = { GLX_RGBA,
+                     GLX_RED_SIZE, 1,
+                     GLX_GREEN_SIZE, 1,
+                     GLX_BLUE_SIZE, 1,
+                     GLX_DOUBLEBUFFER,
+                     GLX_DEPTH_SIZE, 1,
+                     None };
+   int scrnum = DefaultScreen( dpy );
+   return glXChooseVisual(dpy, scrnum, attribs);
+}
+
+
+static void
+parse_opts(int argc, char *argv[])
+{
+   if (argc > 1) {
+      MyID = 1;
+   }
+}
+
+
+int
+main( int argc, char *argv[] )
+{
+   Display *dpy;
+   XVisualInfo *visinfo;
+
+   parse_opts(argc, argv);
+
+   dpy = XOpenDisplay(NULL);
+
+   visinfo = choose_visual(dpy);
+
+   Context = glXCreateContext( dpy, visinfo, NULL, True );
+   if (!Context) {
+      printf("Error: glXCreateContext failed\n");
+      exit(1);
+   }
+
+   if (MyID == 0) {
+      WindowID = make_gl_window(dpy, visinfo, Width, Height);
+      set_window_title(dpy, WindowID, "corender");
+      XMapWindow(dpy, WindowID);
+      /*printf("WindowID 0x%x\n", (int) WindowID);*/
+   }
+
+   /* do ipc hand-shake here */
+   setup_ipc();
+   assert(Sock);
+   assert(WindowID);
+
+   if (MyID == 1) {
+      set_event_mask(dpy, WindowID);
+   }
+
+   resize(dpy, Width, Height);
+
+   event_loop(dpy);
+
+   return 0;
+}
diff --git a/progs/xdemos/ipc.c b/progs/xdemos/ipc.c
new file mode 100644 (file)
index 0000000..fa52b09
--- /dev/null
@@ -0,0 +1,264 @@
+/* Copyright (c) 2003 Tungsten Graphics, Inc.
+ * 
+ * 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, the Tungsten
+ * Graphics splash screen, 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.
+ */
+
+/*
+ * Simple IPC API
+ * Brian Paul
+ */
+
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include "ipc.h"
+
+#if defined(IRIX) || defined(irix)
+typedef int socklen_t;
+#endif
+
+#define NO_DELAY 1
+
+#define DEFAULT_MASTER_PORT 7011
+
+
+/*
+ * Return my hostname in <nameOut>.
+ * Return 1 for success, 0 for error.
+ */
+int
+MyHostName(char *nameOut, int maxNameLength)
+{
+    int k = gethostname(nameOut, maxNameLength);
+    return k==0;
+}
+
+
+/*
+ * Create a socket attached to a port.  Later, we can call AcceptConnection
+ * on the socket returned from this function.
+ * Return the new socket number or -1 if error.
+ */
+int
+CreatePort(int *port)
+{
+    char hostname[1000];
+    struct sockaddr_in servaddr;
+    struct hostent *hp;
+    int so_reuseaddr = 1;
+    int tcp_nodelay = 1;
+    int sock, k;
+
+    /* create socket */
+    sock = socket(AF_INET, SOCK_STREAM, 0);
+    assert(sock > 2);
+
+    /* get my host name */
+    k = gethostname(hostname, 1000);
+    assert(k == 0);
+
+    /* get hostent info */
+    hp = gethostbyname(hostname);
+    assert(hp);
+
+    /* initialize the servaddr struct */
+    memset(&servaddr, 0, sizeof(servaddr) );
+    servaddr.sin_family = AF_INET;
+    servaddr.sin_port = htons((unsigned short) (*port));
+    memcpy((char *) &servaddr.sin_addr, hp->h_addr,
+          sizeof(servaddr.sin_addr));
+
+    /* deallocate when we exit */
+    k = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+                  (char *) &so_reuseaddr, sizeof(so_reuseaddr));
+    assert(k==0);
+
+    /* send packets immediately */
+#if NO_DELAY
+    k = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
+                  (char *) &tcp_nodelay, sizeof(tcp_nodelay));
+    assert(k==0);
+#endif
+
+    if (*port == 0)
+        *port = DEFAULT_MASTER_PORT;
+
+    k = 1;
+    while (k && (*port < 65534)) {
+       /* bind our address to the socket */
+       servaddr.sin_port = htons((unsigned short) (*port));
+       k = bind(sock, (struct sockaddr *) &servaddr, sizeof(servaddr));
+        if (k)
+           *port = *port + 1;
+    }
+
+#if 0
+    printf("###### Real Port: %d\n", *port);
+#endif
+
+    /* listen for connections */
+    k = listen(sock, 100);
+    assert(k == 0);
+
+    return sock;
+}
+
+
+/*
+ * Accept a connection on the named socket.
+ * Return a new socket for the new connection, or -1 if error.
+ */
+int
+AcceptConnection(int socket)
+{
+    struct sockaddr addr;
+    socklen_t addrLen;
+    int newSock;
+
+    addrLen = sizeof(addr);
+    newSock = accept(socket, &addr, &addrLen);
+    if (newSock == 1)
+       return -1;
+    else
+       return newSock;
+}
+
+
+/*
+ * Contact the server running on the given host on the named port.
+ * Return socket number or -1 if error.
+ */
+int
+Connect(const char *hostname, int port)
+{
+    struct sockaddr_in servaddr;
+    struct hostent *hp;
+    int sock, k;
+    int tcp_nodelay = 1;
+
+    assert(port);
+
+    sock = socket(AF_INET, SOCK_STREAM, 0);
+    assert(sock >= 0);
+
+    hp = gethostbyname(hostname);
+    assert(hp);
+
+    memset(&servaddr, 0, sizeof(servaddr));
+    servaddr.sin_family = AF_INET;
+    servaddr.sin_port = htons((unsigned short) port);
+    memcpy((char *) &servaddr.sin_addr, hp->h_addr, sizeof(servaddr.sin_addr));
+
+    k = connect(sock, (struct sockaddr *) &servaddr, sizeof(servaddr));
+    if (k != 0) {
+       perror("Connect:");
+       return -1;
+    }
+
+#if NO_DELAY
+    /* send packets immediately */
+    k = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
+                  (char *) &tcp_nodelay, sizeof(tcp_nodelay));
+    assert(k==0);
+#endif
+
+    return sock;
+}
+
+
+void
+CloseSocket(int socket)
+{
+    close(socket);
+}
+
+
+int
+SendData(int socket, const void *data, int bytes)
+{
+    int sent = 0;
+    int b;
+
+    while (sent < bytes) {
+        b = write(socket, (char *) data + sent, bytes - sent);
+        if (b <= 0)
+            return -1; /* something broke */
+        sent += b;
+    }
+    return sent;
+}
+
+
+int
+ReceiveData(int socket, void *data, int bytes)
+{
+    int received = 0, b;
+
+    while (received < bytes) {
+        b = read(socket, (char *) data + received, bytes - received);
+        if (b <= 0)
+            return -1;
+        received += b;
+    }
+    return received;
+}
+
+
+int
+SendString(int socket, const char *str)
+{
+    const int len = strlen(str);
+    int sent, b;
+
+    /* first, send a 4-byte length indicator */
+    b = write(socket, &len, sizeof(len));
+    if (b <= 0)
+       return -1;
+
+    sent = SendData(socket, str, len);
+    assert(sent == len);
+    return sent;
+}
+
+
+int
+ReceiveString(int socket, char *str, int maxLen)
+{
+    int len, received, b;
+
+    /* first, read 4 bytes to see how long of string to receive */
+    b = read(socket, &len, sizeof(len));
+    if (b <= 0)
+       return -1;
+
+    assert(len <= maxLen);  /* XXX fix someday */
+    assert(len >= 0);
+    received = ReceiveData(socket, str, len);
+    assert(received != -1);
+    assert(received == len);
+    str[len] = 0;
+    return received;
+}
diff --git a/progs/xdemos/ipc.h b/progs/xdemos/ipc.h
new file mode 100644 (file)
index 0000000..3f43445
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef IPC_H
+#define IPC_H
+
+
+extern int MyHostName(char *nameOut, int maxNameLength);
+extern int CreatePort(int *port);
+extern int AcceptConnection(int socket);
+extern int Connect(const char *hostname, int port);
+extern void CloseSocket(int socket);
+extern int SendData(int socket, const void *data, int bytes);
+extern int ReceiveData(int socket, void *data, int bytes);
+extern int SendString(int socket, const char *str);
+extern int ReceiveString(int socket, char *str, int maxLen);
+
+
+#endif /* IPC_H */