Reading buffer contents?

I’m a bit confused on how to read a buffer’s content to a PNMImage. The contents of my pnmimage seem to be the main window’s content instead of my buffer. The test code and related files can be downloaded from here

from direct.showbase.DirectObject import *
from pandac.PandaModules import Texture, PNMImage, GraphicsOutput, NodePath, Filename, TextureStage, Vec3, PandaNode, Point3
from direct.interval.IntervalGlobal import *
import sys

class ShaderPaintTest(DirectObject):

    def __init__(self):
        self.makeBuffer()
        
        base.disableMouse()
        
        self.smiley = loader.loadModel('models/smiley')
        self.smiley.reparentTo(render)
        self.smiley.setPos(-3, 0, 10)
        
        self.frown = loader.loadModel('models/frowney')
        self.frown.reparentTo(render)
        self.frown.setPos(3, 0, 10)
        
        base.camera.setPos(0, -40, 15)
        base.camera.lookAt(0,0,5)
        
        frownTurn = LerpHprInterval(self.frown, 5, Vec3(360,0,0))
        smileTurn = LerpHprInterval(self.smiley, 5, Vec3(360,0,0))
        frownTurn.loop()
        smileTurn.loop()
        
        camPan = LerpPosInterval(base.camera, 10, Point3(0, -40, 10))
        camPan.loop()
        
        self.makeStages()
        
        taskMgr.add(self.paintTask, "Paint Task")
        
        self.isPainting = False
        self.accept('mouse1', setattr, [self, 'isPainting', True])
        self.accept('mouse1-up', setattr, [self, 'isPainting', False])
        self.accept('[', self.writeImg)
        
        self.pickLayer = PNMImage()
        
        self.windowX = base.win.getXSize()
        self.windowY = base.win.getYSize()
        
        self.accept('v', base.bufferViewer.toggleEnable)
        self.accept('V',base.bufferViewer.toggleEnable)
    
    def makeStages(self):
        self.paintTexture = loader.loadTexture('index-512-256.png')
        
        self.painterStage = TextureStage('Painter')
        self.painterStage.setMode(TextureStage.MReplace)
        self.painterStage.setSort(1)
        
        render.setTexture(self.painterStage, self.paintTexture)
        
        self.frownStage = TextureStage('Frown')
        self.frownStage.setMode(TextureStage.MReplace)
        self.frownStage.setSort(2)
        
        self.frown.setTexture(self.frownStage, self.frown.findTexture("**"))
        
        self.smileyStage = TextureStage('Smiley')
        self.smileyStage.setMode(TextureStage.MReplace)
        self.smileyStage.setSort(3)
        
        self.smiley.setTexture(self.smileyStage, self.smiley.findTexture("**"))
    
    def makeBuffer(self):
        self.pickTex = Texture()
        self.pickLayer = PNMImage()
        
        self.buffer = base.win.makeTextureBuffer("pickBuffer", base.win.getXSize(), base.win.getYSize())
        #self.buffer.addRenderTexture(self.pickTex, GraphicsOutput.RTMCopyRam)
        self.buffCam = base.makeCamera(self.buffer, lens=base.cam.node().getLens())
        
        self.shader = loader.loadShader('painter.sha')
        tempnode = NodePath(PandaNode("temp node"))
        tempnode.setShader(self.shader)
        
        self.buffCam.node().setInitialState(tempnode.getState())

    def paintTask(self, task):
        if not base.mouseWatcherNode.hasMouse():
            return task.cont
        
        if self.isPainting is False:
            return task.cont
        
        if self.buffer.getScreenshot(self.pickLayer) is False:
            return task.cont

        print "PNIMage size", self.pickLayer.getXSize(),self.pickLayer.getYSize()
        
        mpos = base.mouseWatcherNode.getMouse()
        mx = int(((mpos.getX()+1)/2)*self.windowX)
        my = self.windowY - int(((mpos.getY()+1)/2)*self.windowY)
        
        # get the color below the mousepick from the rendered frame
        r = self.pickLayer.getRedVal(mx,my)
        g = self.pickLayer.getGreenVal(mx,my)
        b = self.pickLayer.getBlueVal(mx,my)
        # calculate uv-texture position from the color
        x = r + ((b%16)*256)
        y = g + ((b//16)*256)
        
        print "UV Coordinates:", x, y
        
        return task.cont
    
    def writeImg(self):
        self.pickLayer.write(Filename('test.png'))

if __name__ == "__main__":
    import direct.directbase.DirectStart
    s = ShaderPaintTest()
    run()
    sys.exit()

painter.sha

//Cg

void vshader(
    uniform float4x4 mat_modelproj,
    in float4 vtx_position : POSITION,
    in float2 vtx_texcoord0 : TEXCOORD0,
    out float2 l_my : TEXCOORD0,
    out float4 l_position : POSITION)
{
    l_position = mul(mat_modelproj, vtx_position);
    l_my = vtx_texcoord0;
}

void fshader(
    uniform sampler2D tex_0 : TEXUNIT0,
    uniform sampler2D tex_1 : TEXUNIT1,
    in float2 l_my : TEXCOORD0,
    out float4 o_color : COLOR)
{
    o_color = tex2D(tex_1, l_my);
}

The problem is if Panda chooses to implement your buffer with a ParasiteBuffer, which shares the framebuffer data with your main window, then by the time you sample the buffer contents they have already been overwritten with the main window contents. There are two possible solutions.

(1) Don’t use a ParasiteBuffer. Put:

prefer-parasite-buffer 0

in your Config.prc file. Note that if something is wrong with your driver support for offscreen buffers, Panda may fail over to a ParasiteBuffer anyway.

(2) Get the buffer image from your texture instead of from your buffer. The image has been copied to the texture, so this will be a viable option; however, you still need to copy the image from the graphics memory into your system RAM. You can do this by specifying toRam = True on your makeTextureBuffer call to do this every frame (you’ll need to pass in a default Texture object to get to the toRam parameter), or you can call base.graphicsEngine.extractTextureData() to get the texture data in a one-shot pull. Then you can use tex.store() to save the texture data to your PNMImage.

David

The second suggestion fixed it. Thanks drwr.