Hardware instanced Tiles

Hi everyone!

After a lot of head bashing, I have managed to make something which is plausibly functional, a GoL board. However, I can’t seem to figure out why the empty cells all turn out white, when I set their vertex colors to what should be grey values.

Since it looks okay without the shader, I’m assuming the problem lies there.

Another funny thing I discovered is that the shader crashes when I try to send any more than 1024 uniforms. It doesn’t seem to matter whether they are floats or float4s. Is there any way around it?

I’m hoping this prototype will provide a basis for a game with a lots of tiles that need to switch textures rapidly.

__author__="Jon"
__date__ ="$Apr 6, 2011 8:42:59 AM$"

from direct.showbase.ShowBase import ShowBase
from direct.task.Task import Task
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import GeomVertexFormat, GeomVertexData, Geom
from panda3d.core import GeomVertexWriter, GeomTriangles, GeomNode
from panda3d.core import Vec4, PTAFloat, Point3, Plane, Vec3, Mat4
from panda3d.core import PStatClient, TextNode

def makePlane(name='plane', width=1, height=1):
    '''Make a flat plane Geomnode'''

    width = 0.5 * width
    height = 0.5 * height

    format = GeomVertexFormat.getV3n3c4t2()
    vdata = GeomVertexData(name, format, Geom.UHStatic)

    vertex = GeomVertexWriter(vdata, 'vertex')
    normal = GeomVertexWriter(vdata, 'normal')
    color = GeomVertexWriter(vdata, 'color')
    texcoord = GeomVertexWriter(vdata, 'texcoord')

    vertex.addData3f(-width, -height, 0)
    vertex.addData3f(width, -height, 0)
    vertex.addData3f(-width, height, 0)
    vertex.addData3f(width, height, 0)

    normal.addData3f(0,0,1)
    normal.addData3f(0,0,1)
    normal.addData3f(0,0,1)
    normal.addData3f(0,0,1)

    color.addData4f(.5,.5,.5,1)
    color.addData4f(.5,.5,.5,1)
    color.addData4f(.5,.5,.5,1)
    color.addData4f(.5,.5,.5,1)

    texcoord.addData2f(0,0)
    texcoord.addData2f(1,0)
    texcoord.addData2f(0,1)
    texcoord.addData2f(1,1)

    #Define triangles
    prim = GeomTriangles(Geom.UHStatic)
    prim.addConsecutiveVertices(0,3)
    prim.closePrimitive()
    prim.addVertex(3)
    prim.addVertex(2)
    prim.addVertex(1)
    prim.closePrimitive()

    geom = Geom(vdata)
    geom.addPrimitive(prim)

    geomnode = GeomNode(name)
    geomnode.addGeom(geom)

    return geomnode

class World(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        #Create text
        self.title = OnscreenText(text="Hardware-instanced Tiles - Game of Life",
                                  fg=(1,1,1,1), pos=(-1.3, 0.95), scale=.07,
                                  align=TextNode.ALeft)
        self.atext = OnscreenText(text="A to draw cells",
                                  fg=(1,1,1,1), pos=(-1.3, 0.88), scale=.05,
                                  align=TextNode.ALeft)
        self.stext = OnscreenText(text="S to Erase cells",
                                  fg=(1,1,1,1), pos=(-1.3, 0.83), scale=.05,
                                  align=TextNode.ALeft)
        self.spacetext = OnscreenText(text="SPACE to toggle sim",
                                      fg=(1,1,1,1), pos=(-1.3, 0.78), scale=.05,
                                      align=TextNode.ALeft)
        self.mousetext = OnscreenText(text="MOUSE to move camera",
                                      fg=(1,1,1,1), pos=(-1.3, 0.73), scale=.05,
                                      align=TextNode.ALeft)

        #Set Board Parameters
        self.xtiles = 50
        self.ytiles = 50
        self.xwidth = 2.0 / self.xtiles
        self.yheight = 2.0 / self.ytiles

        self.root = render.attachNewNode('Root')

        self.tile = self.root.attachNewNode(makePlane(width=self.xwidth,
                                                    height=self.yheight))
        self.root.setHpr(0,90,0)
        self.root.setPos(0,4,0)

        #Create plane object for intersection calculation
        self.plane = Plane(Vec3(0,1,0), self.root.getPos())

        self.instances = self.xtiles * self.ytiles
        self.gridprops = Vec4(self.xtiles, self.ytiles, self.xwidth, self.yheight)

        #The array to be sent to the shader
        self.colors = PTAFloat.emptyArray(self.instances)

        shader = loader.loadShader("shader2.sha")
        self.root.setInstanceCount(self.instances)
        self.root.setShaderInput("props", self.gridprops)
        self.root.setShaderInput("cinfo", self.colors)

        self.root.setShader(shader)

        self.nextgen = [0]*self.instances
        self.activated = False
        
        taskMgr.add(self.iterateBoard, 'iterateBoard')

        self.accept("space", self.toggleBoard)
        self.accept("a", self.keyDown, [True])
        self.accept("a-up", self.keyUp)
        self.accept("s", self.keyDown, [False])
        self.accept("s-up", self.keyUp)
        #PStatClient.connect()

        #END INIT

    def keyDown(self, write):

        taskMgr.add(self.drawTask, 'drawTask', extraArgs = [write], appendTask = True)

    def keyUp(self):
        taskMgr.remove('drawTask')

    def toggleBoard(self):
        if self.activated:
            self.activated = False
        else:
            self.activated = True

    def drawTask(self, write, task):
        if self.mouseWatcherNode.hasMouse():
            m2dpos = self.mouseWatcherNode.getMouse()

            #Find the intersection between the mouse ray and the plane
            pos3d = Point3()
            nearPoint = Point3()
            farPoint = Point3()
            self.camLens.extrude(m2dpos, nearPoint, farPoint)
            if self.plane.intersectsLine(pos3d,
                        render.getRelativePoint(camera, nearPoint),
                        render.getRelativePoint(camera, farPoint)):

                plane_mat = Mat4()
                plane_mat.invertFrom(self.root.getMat())
                #Get the relative position after transforming by the inverse rotation matrix
                mpos = plane_mat.xformVec(pos3d - self.root.getPos())

            column = int((mpos.x + 0.5*self.xtiles*self.xwidth)/self.xwidth)
            row = int((mpos.y + 0.5*self.ytiles*self.yheight)/self.yheight)

            if (row >= 0 and column >=0 and
                row < self.xtiles and column < self.ytiles):

                #convert row/column to the array index
                index = row * self.xtiles + column
                if write:
                    self.colors[index] = 1
                else:
                    self.colors[index] = 0
                self.root.setShaderInput("cinfo", self.colors)

        return Task.cont

    def iterateBoard(self, task):

        if self.activated:
            #loop through the board

            for y in xrange(self.ytiles):
                for x in xrange(self.xtiles):

                    neighbours = 0

                    for ny in xrange(y-1, y+2):
                        for nx in xrange(x-1, x+2):
                            if ny == y and nx == x:
                                state = self.colors[ny * self.xtiles + nx]
                            elif ((0 <= ny < self.ytiles ) and (0 <= nx < self.xtiles)
                                    and self.colors[ny * self.xtiles + nx] == 1):
                                neighbours += 1

                    if neighbours == 3:
                        self.nextgen[y * self.xtiles + x] = 1
                    elif neighbours == 2 and state == 1:
                        self.nextgen[y * self.xtiles + x] = 1
                    else:
                        self.nextgen[y * self.xtiles + x] = 0
            #end loop
            self.colors = PTAFloat(self.nextgen)
            self.root.setShaderInput("cinfo", self.colors)

        return Task.cont


app = World()
app.run()
//Cg
//Cg profile gp4vp gp4fp

struct VertexDataIN{
    float4 vtx_position : POSITION;
    float4 vtx_color : COLOR;
    float2 vtx_texcoord0 : TEXCOORD0;
    int l_id : INSTANCEID;
};

struct VertexDataOUT{
    float4 o_position : POSITION;
    float4 o_color : COLOR;
    float2 o_texcoord0 : TEXCOORD0;
};

//vertex shader
void vshader(VertexDataIN IN,
        out VertexDataOUT OUT,
        uniform float4x4 mat_modelproj,
        uniform float4 props,
        uniform float4 cinfo[625])

{
    //calculates vertex position based on grid properties and index number
    float row;
    float remainder = modf(IN.l_id/props.x, row);
    float xpos = ((remainder - 0.5) * props.x + 0.5) * props.z;
    float ypos = (((row-0.5 * props.y) + 0.5) * props.w);

    float4 vpos = IN.vtx_position + float4(xpos, ypos,0,0);
    OUT.o_position = mul(mat_modelproj, vpos);

    //Get the color info from the array
    float col = cinfo[IN.l_id/4][IN.l_id%4];
    if (col == 0)
    {
        OUT.o_color = IN.vtx_color;
    } else {
        OUT.o_color = float4 (0,0,1,1);
    }

    OUT.o_texcoord0 = IN.vtx_texcoord0;
}


//fragment shader
void fshader(VertexDataOUT vIN,
        uniform sampler2D tex0,
        out float4 o_color : COLOR)

{
    o_color = vIN.o_color;
}

is the PTAFloat type not supported under osx?

Known pipe types:
  osxGraphicsPipe
(all display modules loaded.)
Mon Apr 11 20:48:30 Ludos-2.local python[1209] <Error>: kCGErrorIllegalArgument: CGSCopyRegion : Null pointer
Mon Apr 11 20:48:30 Ludos-2.local python[1209] <Error>: kCGErrorFailure: Set a breakpoint @ CGErrorBreakpoint() to catch errors as they are logged.
:gobj(error): /Users/rspoerri/Desktop/hw_instancing/shader2.sha: invalid parameter name (uniform in sampler2d tex0)
:gobj(error): Shader encountered an error.
Traceback (most recent call last):
  File "main.py", line 205, in <module>
    app = World()
  File "main.py", line 109, in __init__
    self.root.setShaderInput("cinfo", self.colors)
TypeError: Arguments must match one of:
setShaderInput(non-const NodePath this, const ShaderInput inp)
setShaderInput(non-const NodePath this, non-const InternalName id)
setShaderInput(non-const NodePath this, string id)
setShaderInput(non-const NodePath this, non-const InternalName id, const Vec4 v)
setShaderInput(non-const NodePath this, non-const InternalName id, const NodePath np)
setShaderInput(non-const NodePath this, non-const InternalName id, non-const Texture tex)
setShaderInput(non-const NodePath this, string id, const Vec4 v)
setShaderInput(non-const NodePath this, string id, const NodePath np)
setShaderInput(non-const NodePath this, string id, non-const Texture tex)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1)
setShaderInput(non-const NodePath this, string id, float n1)
setShaderInput(non-const NodePath this, non-const InternalName id, const Vec4 v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const NodePath np, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, non-const Texture tex, int priority)
setShaderInput(non-const NodePath this, string id, const Vec4 v, int priority)
setShaderInput(non-const NodePath this, string id, const NodePath np, int priority)
setShaderInput(non-const NodePath this, string id, non-const Texture tex, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2)
setShaderInput(non-const NodePath this, string id, float n1, float n2)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2, float n3)
setShaderInput(non-const NodePath this, string id, float n1, float n2, float n3)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2, float n3, float n4)
setShaderInput(non-const NodePath this, string id, float n1, float n2, float n3, float n4)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2, float n3, float n4, int priority)
setShaderInput(non-const NodePath this, string id, float n1, float n2, float n3, float n4, int priority)

Mon Apr 11 20:48:31 Ludos-2.local python[1209] <Error>: kCGErrorIllegalArgument: _CGSFindSharedWindow: WID 2136
Mon Apr 11 20:48:31 Ludos-2.local python[1209] <Error>: kCGErrorIllegalArgument: CGSRemoveSurface: Invalid window 0x858

Hmm, it works in my own OSX build:

TypeError: Arguments must match one of:
setShaderInput(non-const NodePath this, const ShaderInput inp)
setShaderInput(non-const NodePath this, non-const InternalName id)
setShaderInput(non-const NodePath this, string id)
setShaderInput(non-const NodePath this, string id, non-const Texture tex)
setShaderInput(non-const NodePath this, string id, const PointerToArray v)
setShaderInput(non-const NodePath this, string id, const PointerToArray v)
setShaderInput(non-const NodePath this, string id, const PointerToArray v)
setShaderInput(non-const NodePath this, string id, const PointerToArray v)
setShaderInput(non-const NodePath this, string id, const PointerToArray v)
setShaderInput(non-const NodePath this, string id, const PointerToArray v)
setShaderInput(non-const NodePath this, string id, const PointerToArray v)
setShaderInput(non-const NodePath this, string id, const NodePath np)
setShaderInput(non-const NodePath this, string id, const VBase4 v)
setShaderInput(non-const NodePath this, string id, const VBase3 v)
setShaderInput(non-const NodePath this, string id, const VBase2 v)
setShaderInput(non-const NodePath this, string id, const Mat4 v)
setShaderInput(non-const NodePath this, string id, const Mat3 v)
setShaderInput(non-const NodePath this, non-const InternalName id, non-const Texture tex)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v)
setShaderInput(non-const NodePath this, non-const InternalName id, const NodePath np)
setShaderInput(non-const NodePath this, non-const InternalName id, const VBase4 v)
setShaderInput(non-const NodePath this, non-const InternalName id, const VBase3 v)
setShaderInput(non-const NodePath this, non-const InternalName id, const VBase2 v)
setShaderInput(non-const NodePath this, non-const InternalName id, const Mat4 v)
setShaderInput(non-const NodePath this, non-const InternalName id, const Mat3 v)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1)
setShaderInput(non-const NodePath this, string id, float n1)
setShaderInput(non-const NodePath this, string id, non-const Texture tex, int priority)
setShaderInput(non-const NodePath this, string id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, string id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, string id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, string id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, string id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, string id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, string id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, string id, const NodePath np, int priority)
setShaderInput(non-const NodePath this, string id, const VBase4 v, int priority)
setShaderInput(non-const NodePath this, string id, const VBase3 v, int priority)
setShaderInput(non-const NodePath this, string id, const VBase2 v, int priority)
setShaderInput(non-const NodePath this, string id, const Mat4 v, int priority)
setShaderInput(non-const NodePath this, string id, const Mat3 v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, non-const Texture tex, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const PointerToArray v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const NodePath np, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const VBase4 v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const VBase3 v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const VBase2 v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const Mat4 v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const Mat3 v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2)
setShaderInput(non-const NodePath this, string id, float n1, float n2)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2, float n3)
setShaderInput(non-const NodePath this, string id, float n1, float n2, float n3)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2, float n3, float n4)
setShaderInput(non-const NodePath this, string id, float n1, float n2, float n3, float n4)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2, float n3, float n4, int priority)
setShaderInput(non-const NodePath this, string id, float n1, float n2, float n3, float n4, int priority)

i’m using 1.7.1 and updated to the latest nvidia cg ( February 2011 version of Cg 3.0) from http://developer.nvidia.com/cg-toolkit .
are you using a custom build or the official one?

also i’m wondering about the error of sampler2D not being supported…

Ah, this appears to be a 1.7.x thing. The variants of setShaderInput() that take a PTAFloat were only recently added, and they are therefore defined on the cvs trunk (where I had built my own version), but not on the 1.7.x branch. They should be available in the buildbot releases.

I’m not sure about the sampler2D issue.

David

So umm why is it the vertices that are supposed to be (.5,.5,.5,1) showing up as white?

EDIT: Ah I figured it out. For some reason, the vtx_color RGB input comes in with a maximum value of 256 per color, rather than having a maximum of 1. Weird.

Is that supposed to happen?