teaching machines

A texture renderer on the Raspberry Pi

December 19, 2012 by . Filed under buster, public.

A collaborator is working on using a cluster of Raspberry Pis to build a multi-monitor display wall. We’re using textures in OpenGL to blit each frame across the display. For his benefit, I pieced together a simple texture renderer, where the texture is procedurally generated by XORing the texel indices. XOR textures are interesting:

A Raspberry Pi powering an XOR texture display.

The renderer, which has lots of dependencies not included here, follows:

#!/usr/bin/env python

import time
import sys

from pyopengles import *
from geometry import *
from glop import *

WIDTH = 1680
HEIGHT = 1050

class XortexRenderer(Renderer):
  def __init__(self):
    Renderer.__init__(self)
    self.context = EGL(depth_size=0)
    self.startTime = time.time()

    # Position attributes are in [-1, 1] x [-1, 1]. We can turn these into
    # texture coordinates ourselves, so we only take in one vertex attribute.
    self.vertexShader = """
      attribute vec2 position;

      varying vec2 texcoord;

      void main() {
        gl_Position = vec4(position, 0.0, 1.0);
        texcoord = position * 0.5 + 0.5;
      }
    """

    # Color is determined entirely by the texture.
    self.fragmentShader = """
      precision mediump float;
      uniform sampler2D frame;  

      varying vec2 texcoord; 

      void main() {
        gl_FragColor = texture2D(frame, texcoord);
      }
    """

    # The position attribute is in slot 0.
    self.binding = ((0, 'position'),)
    self.program = self.context.get_program(self.vertexShader, self.fragmentShader, self.binding)

    # Fill the viewport.
    self.vertices = eglfloats((-1.0, -1.0,
                                1.0, -1.0,
                               -1.0,  1.0,
                                1.0,  1.0))

    # The will be uploaded to texture unit 0.
    opengles.glUseProgram(self.program)
    self.frameTextureUniformID = opengles.glGetUniformLocation(self.program, "frame")
    opengles.glUniform1i(self.frameTextureUniformID, 0)
    opengles.glUseProgram(0)

    # Let's make that texture!
    self.frameTextureID = eglint()
    opengles.glActiveTexture(GL_TEXTURE0) 
    opengles.glGenTextures(1, ctypes.byref(self.frameTextureID)) 
    opengles.glBindTexture(GL_TEXTURE_2D, ctypes.byref(self.frameTextureID)) 
    opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    opengles.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)

    # An RGB image. We'll create a colorful XOR texture, 'cuz it's pretty.
    width = 128
    height = 128
    maxIntensity = max(width, height) - 1.0
    vals = [0] * width * height * 3
    for r in range(height):
      for c in range(width):
        intensity = int((r ^ c) / maxIntensity * 255)
        vals[(r * width + c) * 3 + 0] = intensity * (r < height / 2)
        vals[(r * width + c) * 3 + 1] = intensity * (r > height / 2)
        vals[(r * width + c) * 3 + 2] = intensity * (c > height / 2)

    # Now we ship the texture off. Bye-bye.
    opengles.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, eglubytes(vals))

    self.checkError('after init')

  def onKeyEvent(self, keycode):
    pass

  def onMouseEvent(self, event):
    pass

  def onDraw(self):
    opengles.glViewport(0, 0, self.context.width, self.context.height);
    opengles.glClear(GL_COLOR_BUFFER_BIT)

    opengles.glUseProgram(self.program)

    opengles.glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, self.vertices)
    opengles.glEnableVertexAttribArray(0)
    opengles.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)

    self.checkError('after draw')

    openegl.eglSwapBuffers(self.context.display, self.context.surface)

  def checkError(self, msg):
    e = opengles.glGetError()
    if e:
      print msg, 'Error: ', hex(e)
      sys.exit(1)

if __name__ == "__main__":
  with XortexRenderer() as renderer:
    render.goMouseless(WIDTH, HEIGHT, 0.02, renderer)