Initialising Offscreen Textures

I’m currently working on a shader to a particular purpose, and as part of its calculations am storing 2D vector-values in a texture; much as with normals, a zero-vector produces pixel-values of (0.5, 0.5, <unused value>, 1).

Now, the vectors are intended to have zero-values at the start; having them be otherwise messes with the calculations. And so I’m attempting to initialise my textures to (0.5, 0.5, 0.5, 1).

To some degree, this can be done by some or all of calling “myBuffer.setClearColor(Vec4(0.5, 0.5, 0.5, 1))” and “myTexture.setClearColor(Vec4(0.5, 0.5, 0.5, 1))” followed by “myTexture.clearImage()”. (After a variety of attempts, I’m honestly not sure of how much of that is required. ^^; )

However, it doesn’t take effect immediately, it would appear: the texture seems to still have invalid data (values of (1, 1, 1, 1), I suspect) for at least the first run of the shader.

So far I’ve patched over the issue by running a two-second timer before properly using the shader–but I’m hoping that there’s a better solution.

Does anyone know of such a solution…?

Do you write using render-to-texture? If so, the way to do that is with setClearColor on the buffer and setClearColorActive(True) is needed for that as well.

For other uses of a texture, you can indeed use setClearColor (clearImage may not always be necessary but doesn’t hurt), which should be enough, though you can also follow up with a call to prepare(base.win.gsg.getPreparedObjects()) to force it to happen at the beginning of the next frame.

Can you tell me more about how you’re using the texture?

If I’m correct in understanding, then yes. (See below for more detail on what I’m doing.)

Ah, I think that I missed “setClearColorActive”!

But I think that I may have actually stumbled upon a solution in the meanwhile: I’m now rendering not to the standard colour-output, but to an aux-output. (That is, I’m adding a render-texture with the parameter “GraphicsOutput.RTP_aux_rgba_<n>

In short, I’m ping-ponging between two buffers, using the output of each as an input to the other. These buffers are generated via calls to “GraphicsEngine.makeOutput”, and are rendered into via dedicated cameras. The textures in question are then added to the buffers as render-textures, via calls to “addRenderTexture”.

The “image” that is rendered to each represents a vector quantity (in truth, two vector quantities), on which I’m attempting to do some maths in the shader.

The clear settings on the texture itself really are for single use. The clear settings on the buffer are meant for clearing the texture every time it’s rendered. (You could call clearImage() every frame, but clearing it on the buffer level if you’re about to render to it is generally more efficient.)

If you wish to clear an auxiliary render target, you need to use the more general setClearValue(RTP_aux_rgba_<n>, color) and setClearActive(RTP_aux_rgba_<n>, True).

To be–ah–clear, I don’t want to clear the buffers every frame–in fact, it’s important that their contents persist between frames!

However, I do want the buffers to be initialised to valid starting-data. Hence my desire to clear the textures to (0.5, 0.5, 0.5, 1).

(Which, as I say, seems to be working now that I’m only (really) using aux-buffers rather than the main texture-buffer.)

Ah, so you want to disable clears in fact, both on the buffer and on the display region.
I don’t understand why it would be different with an aux buffer, other than the default clear settings perhaps being different.

Hmm, that’s a good point! I can only presume that clears for textures are off by default–otherwise I would expect my data to be wiped every frame!

But ultimately, what I’m striving to do is to set the content of the textures at startup: After all, each step of the process is based on the result of the previous step. But in the case of the very first step, the “result of the previous step” is effectively whatever the textures were initialised to.

Hence the thought to set a clear-colour on the textures and then immediately call “clearImage”, hoping for that to fill the texture with the clear-colour.

Yes, that’s the right way to do it. The clears for textures are only one-time.

If you can show a very small self-contained sample program showing that it doesn’t work when rendering to it, please file a bug report.

I’ll give it a shot soon, then, I intend!

[edit]
Okay, I believe that I’ve put together a fairly-minimal example:

In short, it ping-pongs between two buffers, both rendering the same off-screen scene to a texture. This texture is then fed into its counterpart’s buffer.

One of the textures thus produced is additionally added to an on-screen card, so that we can see it.

The specific rendering result is generated by a simple shader that just renders colour from a single point on the input-texture, sending it to both “color” and “color1”–presumably baseline colour and the aux-output, respectively.

As to that texture, it is initialised to blue via a call to “setClearColor” followed by a call to “clearImage”.

As a result, the output should be a blue card floating against the grey of the default window-clear.

As to use of the program:

Simply put, run the code below. As-is, you should find that it renders a blue square as intended.

Then go to about line 105, uncomment the first call to “addRenderTexture”, and comment-out the second call to “addRenderTexture” (just below the first). Where the buffer was rendering to aux, it should now be rendering to “color”.

Once again, run the code–on my machine, at least, it now renders black.

from direct.showbase.ShowBase import ShowBase

from panda3d.core import CardMaker, NodePath, Texture, GraphicsOutput, GraphicsPipe, \
    FrameBufferProperties, Vec4, PandaNode, OrthographicLens, Vec2, Shader, WindowProperties


vert = """
#version 150

in vec4 p3d_Vertex;

uniform mat4 p3d_ModelViewProjectionMatrix;

void main()
{
    gl_Position = p3d_ModelViewProjectionMatrix*p3d_Vertex;
}
"""

frag = """
#version 150

uniform sampler2D mew;

out vec4 color;
out vec4 color1;

void main()
{
    color.xyz = texture(mew, vec2(0.5, 0.5)).xyz;
    color.w = 1;

    color1.xyz = texture(mew, vec2(0.5, 0.5)).xyz;
    color1.w = 1;
}
"""

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

        cardMaker = CardMaker("mew")

        cardMaker.setFrame(-0.5, 0.5, -0.5, 0.5)

        frameProperties = FrameBufferProperties()
        frameProperties.setRgbaBits(8, 8, 8, 8)
        frameProperties.setAuxRgba(4)

        windowProperties = WindowProperties(self.win.getProperties())
        windowProperties.setSize(512, 512)

        self.testBuffer1, self.testCamera1, \
        self.testTexture1, \
        self.testRoot1, \
        self.testPaintingCard1 = \
            self.makeTestRenderer(frameProperties, windowProperties, cardMaker)

        self.testBuffer2, self.testCamera2, \
        self.testTexture2, \
        self.testRoot2, \
        self.testPaintingCard2 = \
            self.makeTestRenderer(frameProperties, windowProperties, cardMaker)

        self.testPaintingCard1.setShaderInput("mew", self.testTexture2)
        self.testPaintingCard2.setShaderInput("mew", self.testTexture1)

        self.testBuffer1.setActive(True)
        self.testBuffer2.setActive(False)
        self.activeBuffer = 1

        self.testTexCard = self.render.attachNewNode(cardMaker.generate())
        self.testTexCard.setScale(2.5)
        self.testTexCard.setPos(2, 10, -1)
        self.testTexCard.setTexture(self.testTexture2)

        self.disableMouse()

        self.taskMgr.add(self.update, "update")

    def makeTestRenderer(self, frameProperties, windowProperties, cardMaker):
        testRoot = NodePath(PandaNode("vel root"))
        testPaintingCard = testRoot.attachNewNode(cardMaker.generate())
        testPaintingCard.setY(10)

        shader = Shader.make(Shader.SL_GLSL, vert, frag)
        testPaintingCard.setShader(shader)

        testBuffer = self.graphicsEngine.makeOutput(self.pipe, "Test Buffer", -1,
                                                         frameProperties, windowProperties,
                                                         GraphicsPipe.BFRefuseWindow, self.win.getGsg(), self.win)

        testCamera = self.makeCamera(testBuffer)
        testCamera.node().setScene(testRoot)
        newLens = OrthographicLens()
        newLens.setFilmSize(Vec2(1, 1))
        newLens.setNearFar(5, 100)
        testCamera.node().setLens(newLens)

        testTexture = Texture()
        testTexture.setWrapU(Texture.WM_clamp)
        testTexture.setWrapV(Texture.WM_clamp)

        """
        testBuffer.addRenderTexture(testTexture,
                                         GraphicsOutput.RTMBindOrCopy,
                                        GraphicsOutput.RTP_color)
        """
        testBuffer.addRenderTexture(testTexture,
                                    GraphicsOutput.RTMBindOrCopy,
                                    GraphicsOutput.RTP_aux_rgba_0)
        #"""

        testTexture.setClearColor(Vec4(0, 0, 1, 1))
        testTexture.clearImage()

        return testBuffer, testCamera, testTexture, testRoot, testPaintingCard

    def update(self, task):
        if self.activeBuffer == 1:
            self.testBuffer1.setActive(False)
            self.testBuffer2.setActive(True)
            self.activeBuffer = 2
        else:
            self.testBuffer2.setActive(False)
            self.testBuffer1.setActive(True)
            self.activeBuffer = 1

        return task.cont

game = GameCore()

game.run()

It seems there is some explicit code in our OpenGL buffer code that clears the buffer to black when it is created. I don’t agree with this implementation choice. Interestingly, though this code is still there, the issue does not occur on the latest master version of Panda. Nor does it occur on 1.10.15 when my mouse is outside the window when the window opens. Then it’s blue. It seems to have something to do with the timing of the buffer creation; perhaps the texture update call somehow gets triggered again after the buffer is set up.

You could render a dummy frame and then call clearImage() again. Or keep using the aux attachment for now. For 1.11, I would be inclined to change this code in Panda so that it doesn’t do it if the texture already has a clear color. Or I could just take this code out entirely.

Note that your framebuffer has 5 color attachments. The argument of setAuxRgba is a count of buffers of the given type.

Very strange! o_0

Perhaps it was originally implemented to ensure that there isn’t junk in the buffer at startup, or for some specific use-case that someone had…?

I would be inclined to agree, overall.

As to whether to have a conditional or to remove the code entirely… Hum, it depends, to my mind, on why it’s there in the first place. If removing it leaves the buffer full of junk, then that may cause problems for someone.

Further, it occurs to me that there’s always the risk that someone is relying on this behaviour somewhere…

As such, I’m inclined to think that the conditional is the safest option.

I have noticed as such myself: during earlier testing I actually added a two-second timer, and only began use of the shader-algorithm proper after that time had passed.

(Note that a whole two seconds was likely called for–it was just a safe-but-short amount of time.)

I think, given what you say, this is likely the simplest solution. I don’t like the (presumed) inefficiency of it–but I doubt that it’ll have a major impact. And I can revisit the matter if it does.

Ah! I wondered about that argument, as I recall!

Out documentation doesn’t seem to specify its purpose, so I think that I just guessed at something that seemed to make sense.

So I take it that, in my case, the more-proper value would be 2?

The right value is 1, because you have one “auxiliary” buffer on top of the regular one. Setting it to 1 makes RTPAuxRgba0 available.

Also, yes, the original purpose of the clear is so that there would be no random junk in the buffer, but that code predated the existence of setClearColor, which is now a good way to prevent a texture from having random junk in it. It should follow the texture clear color, with black as fallback.

I checked in a change: the existing “initial clear” code is no longer enabled for bitplanes that you attached a texture to. But, Panda will now assign a clear color of black to the texture if there is no clear color on it currently, so you won’t see junk in the texture.

Ah, that’s excellent, and thank you! :slight_smile:

(And I do think that your approach makes sense, and should remain consistent with extant code. ^_^)

While the example-program above only outputs to one, my actual program outputs to two aux-buffers (“aux_rgba_0” and “aux_rgba_1”). (Which I now realise that I don’t seem to have explicitly mentioned in this thread; my apologies.)

(I’m rendering a vector quantity, and the rate-of-change of that quantity.)

Ah, right. Then 2 it is.

1 Like