strange screenshot problem -- Hardware Issue?

I am trying to use offscreen buffer to render models to take their thumbnail images. But it doesn’t work. The screenshot that I get is that from the main camera. I tried to do something similar on the teapot-tv example but the problem persists.

This is my edited code for the teapot-tv example:
(my changes are in between bold tags)

import direct.directbase.DirectStart
from pandac.PandaModules import Filename,Texture
from pandac.PandaModules import AmbientLight,DirectionalLight
from pandac.PandaModules import NodePath,TextNode
from pandac.PandaModules import Point3,Vec3,Vec4
from direct.task.Task import Task
from direct.actor.Actor import Actor
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.DirectObject import DirectObject
import sys, os, random

# Figure out what directory contains this program
MYDIR=os.path.abspath(sys.path[0])
MYDIR=Filename.fromOsSpecific(MYDIR).getFullpath()

# Function to put instructions on the screen.
def addInstructions(pos, msg):
    return OnscreenText(text=msg, style=1, fg=(1,1,1,1),
			pos=(-1.3, pos), align=TextNode.ALeft, scale = .05)

# Function to put title on the screen.
def addTitle(text):
    return OnscreenText(text=text, style=1, fg=(1,1,1,1),
	                pos=(1.3,-0.95), align=TextNode.ARight, scale = .07)

random.seed()
base.setBackgroundColor(Vec4(0.0,0.0,0.0,1.0))

class World(DirectObject):
    def __init__(self):
        
	# Post the instructions.
	self.title = addTitle("Panda3D: Tutorial - Using Render-to-Texture")
	self.inst1 = addInstructions(0.95,"ESC: Quit")
	self.inst2 = addInstructions(0.90,"Up/Down: Zoom in/out on the Teapot")
	self.inst3 = addInstructions(0.85,"Left/Right: Move teapot left/right")
	self.inst4 = addInstructions(0.80,"V: View the render-to-texture results")

        #we get a handle to the default window
        mainWindow=base.win

        #we now get buffer thats going to hold the texture of our new scene   
        altBuffer=mainWindow.makeTextureBuffer("hello", 256, 256)
        
        #now we have to setup a new scene graph to make this scene
        altRender=NodePath("new render")

        #this takes care of setting up ther camera properly
        self.altCam=base.makeCamera(altBuffer)
        self.altCam.reparentTo(altRender)        
        self.altCam.setPos(0,-10,0)

        #get the teapot and rotates it for a simple animation
        self.teapot=loader.loadModel('models/teapot')
        self.teapot.reparentTo(altRender)
        self.teapot.setPos(0,0,-1)
        self.teapot.hprInterval(1.5,Point3(360,360,360)).loop()
        
        #put some lighting on the teapot
        dlight = DirectionalLight('dlight')
        alight = AmbientLight('alight')
        dlnp = altRender.attachNewNode(dlight.upcastToPandaNode()) 
        alnp = altRender.attachNewNode(alight.upcastToPandaNode())
        dlight.setColor(Vec4(0.8, 0.8, 0.5, 1))
        alight.setColor(Vec4(0.2, 0.2, 0.2, 1))
        dlnp.setHpr(0, -60, 0) 
        altRender.setLight(dlnp)
        altRender.setLight(alnp)
        
        # Panda contains a built-in viewer that lets you view the results of
        # your render-to-texture operations.  This code configures the viewer.

        self.accept("v", base.bufferViewer.toggleEnable)
        self.accept("V", base.bufferViewer.toggleEnable)
       [b] self.accept("s", self.takeScreenshot, [altBuffer])[/b]
        base.bufferViewer.setPosition("llcorner")
        base.bufferViewer.setCardSize(1.0, 0.0)

        # Create the tv-men. Each TV-man will display the
        # offscreen-texture on his TV screen.
        self.tvMen = []
        self.makeTvMan(-5,30, 1,altBuffer.getTexture(),0.9)
        self.makeTvMan( 5,30, 1,altBuffer.getTexture(),1.4)
        self.makeTvMan( 0,23,-3,altBuffer.getTexture(),2.0)
        self.makeTvMan(-5,20,-6,altBuffer.getTexture(),1.1)
        self.makeTvMan( 5,18,-5,altBuffer.getTexture(),1.7)

        self.accept("escape", sys.exit, [0])
        self.accept("arrow_up", self.zoomIn)
        self.accept("arrow_down", self.zoomOut)
        self.accept("arrow_left", self.moveLeft)
        self.accept("arrow_right", self.moveRight)

    def makeTvMan(self,x,y,z,tex,playrate):
        man = Actor()
        man.loadModel('models/mechman_idle')
        man.setPos(x,y,z)
        man.reparentTo(render)
        fp = man.find("**/faceplate")
        fp.setTexture(tex,1)
        man.setPlayRate(playrate, "mechman_anim")
        man.loop("mechman_anim")
        self.tvMen.append(man)

    def zoomIn(self):
        self.altCam.setY(self.altCam.getY()*0.9)
    
    def zoomOut(self):
        self.altCam.setY(self.altCam.getY()*1.2)
    
    def moveLeft(self):
        self.altCam.setX(self.altCam.getX()+1)
    
    def moveRight(self):
        self.altCam.setX(self.altCam.getX()-1)

   [b] def takeScreenshot(self, buff):
        base.screenshot(defaultFilename = 0, source = buff,
                                namePrefix = 'screenshot_' + 'teaPot' + '.bmp')[/b]
        

w=World()
run()

try to press ‘s’ without the “View the render-to-texture” option enabled
and check out the image saved scrrenshot_teapot.bmp.
This images is actually the screenshot of main window’s left down corner.

What I think I should have got is the image of the teapot alone.
Where am I wrong?
Is this something hardware specific??

Shashank

Your offscreen buffer is getting created as a “parasite buffer”. This means it is actually rendering into a corner of the main window, and then the main window renders on top of it. This works out pretty well for most reasons that you want to have an offscreen buffer, and plus has the advantage that it doesn’t rely on creating an actual offscreen buffer on the graphics card, which tends to be fraught with driver and hardware issues.

However, you can’t grab a screenshot from a parasite buffer, because by the time you call base.screenshot(), that area of the buffer has already been overdrawn. Your two choices are therefore: (a) don’t use a parasite buffer, or (b) save the buffer’s texture image instead of calling base.screenshot (this requires copying the texture image to ram, but that’s what base.screenshot does anyway).

To do (a), try putting:

prefer-parasite-buffer 0

in your Config.prc.

To do (b), add toRam = True to the makeTextureBuffer call, and then use altBuffer.getTexture().write() to write out the screenshot.

David

Thanks for the quick response David

the solution ‘b’ you suggested worked for me. :slight_smile:

However the first option of

prefer-parasite-buffer 0

didn’t appear to have any effect.
Is it that due to limitations of my graphics card panda3d is forced to make a parasite buffer or could it be something else?

Shashank

[#] Windows, P3D 1.5.1, last year NV driver :
getSupportsRenderTexture() returns 1.
I can get GLGraphicsBuffer, I can create a buffer larger than framebuffer.

[#] Ubuntu Hardy, P3D 1.5.2, latest NV driver :
getSupportsRenderTexture() returns 0, so p-p-b 0 doesn’t help.
I can only get ParasiteBuffer, thus it’s limited by framebuffer size.

All working RTT samples run at the same speed on both OSes (visual effects disabled on Linux).
What could be the problem, David ? Is it the driver for Linux ?

There is still a small bit of problem here :frowning:

But first let me tell you about the prefer-parasite buffer option:
What I had reported was the result of use of this option on my laptop which does not have a graphics card but only an accelerator. And this option didn’t have any effect there.

But when I used this on a desktop with a graphics card it did appear to be doing its job, i.e.,

isinstance(buff, ParasiteBuffer)

returns False.

Now the problem:

I used the option ‘b’ on the teapot example and it works fine and i get the correct screen-shot. But when I try to do the same to my actual application it doesn’t work.
I tried all sorts of combination but without success.
When I try to use the option b :

texture = Texture()
        #create the buffer
        offScreenbuffer = base.win.makeTextureBuffer("propInspectBuff", 256, 256, texture, True)
     

but now when immediately after this I apply:

offScreenbuffer.getTexture().hasRamImage()
offScreenbuffer.getTexture().mightHaveRamImage()

both return False

and then obviously

offScreenbuffer.getTexture().write(Filnename('...'))

throws an error.

Why is the toRam option not having any effect??

I also tried to use the option a but without luck.
With

prefer-parasite buffer 0

set, I tried to take the screen shot and what I get now is a blank-black image??

Whats going on here???

All this is about the desktop with a graphics card, so can’t blame the poor laptop for this!

Shashank

The answer is simple :
at that moment right after the buffer created, no texture is generated since you haven’t rendered anything into it. Call base.graphicsEngine.renderFrame() to actually render the teapot.

Thanks ynjh_jo, silly to have missed it… problem solved now :slight_smile:

In this case, it is probably the NV Linux driver. Opening a true offscreen buffer requires two things: (a) a graphics driver that supports some kind of offscreen buffer, and (b) code in Panda to open the kind of offscreen buffer supported by your driver.

There are lots of different API’s to open offscreen buffers, largely because OpenGL didn’t really define a standard about this until recently. So there was a GLX standard for Linux, a WGL standard on Windows, and a AGL standard on Mac. All of these were spottily supported by the various graphics drivers. Recently, OpenGL has defined a native GL buffer object, which is even more spottily supported by the various graphics drivers. To further cloud the issue, we haven’t implemented support for all of the above in Panda (in particular, we haven’t implemented AGL buffer support, so most Mac machines can’t open true offscreen buffers in Panda).

Short answer: sometimes you can open a true offscreen buffer, and sometimes you just have to stick with parasite buffers.

David

Thanks for the great info, David.

Hi, i was trying to save the actual screen to a texture, this topic really help me to find the way but now i have a problem.
For testing proposes i writing the capture to a file but im obtaining a grey image on all my attempts.

Seems im missing something…
This is the code:

...
self.accept( 'mouse1', self.capture )
..
..
def capture(self):
  texture = Texture()
  offScreenbuffer = base.win.makeTextureBuffer("OffscreenBuffer",     base.win.getXSize(), base.win.getYSize(), texture, True)
  base.graphicsEngine.renderFrame()
  texture.write(Filename("capture.jpg"))

Thanks!

You are making an offscreen buffer and copying those results to your texture. This will only result in the main window being copied to the texture if you are experiencing the bug described in this thread; but generally, a gray screen is the correct result (because there is no scene in your offscreen buffer).

If all you want is to copy the main window to a texture, use this instead:

base.win.addRenderTexture(tex, RTMCopyRam)

This will copy the texture every frame, and it will be copied all the way to the RAM, which is expensive. I don’t know what your larger purpose was, so I can’t comment about whether this is appropriate for your needs or not.

David

Thanks drwr.
During the game, i just want to take a screenshot of the whole screen and later, use it as a texture.

So, is your suggestion the right choice for that?

In the code i put before, i write the texture to a file just to see if my code worked (but as i said, i get a grey image).

Thanks again.

In your case, then, you probably want to use GraphicsOutput.RTMCopyTexture instead of RTMCopyRam (no need to copy the texture image all the way to ram), and you probably want to set a task to stop the copying after one frame, with something like:

taskMgr.doMethodLater(0.001, base.win.clearRenderTextures, 'clearRenderTextures', extraArgs = [])

otherwise it will continue to copy the main window to the texture every frame until the end of the game.

Another, simpler option, is to simply save a screenshot image with base.screenshot() or base.win.saveScreenshot(), and then load the resulting image with loader.loadTexture(). This may introduce a visible chug at the time you call screenshot(), but that may or may not be a problem for you; and it’s a very straightforward solution.

David