One shot render buffer are sometimes not working

I’m pulling my hair out trying to understand a weird behavior with one shot render to texture. In the example code below, I have a render to texture buffer which is set to one shot from a task. Another task is waiting for the texture to be ready and then applies it on a card.
Most of the time this works as expected, at frame number 1 the render buffer is created and activated in one shot mode in the first task. In frame 2, the second task checks that the texture has data and if so applies it on the card.

In that case I get the following log:

New buffer color_bits=24 red_bits=8 green_bits=8 blue_bits=8 
1 Triggered
2 WAIT
2 APPLY
2d_texture 
  2-d, 256 x 256 pixels, each 3 bytes, rgb, compression off
  sampler wrap(u=repeat, v=repeat, w=repeat, border=0 0 0 1) filter(min=default, mag=default, aniso=0) lod(min=-1000, max=1000, bias=0)  196608 bytes in ram, compression off

However, from time to time, this does not work and the texture never gets any data :

New buffer color_bits=24 red_bits=8 green_bits=8 blue_bits=8 
1 Triggered
2 WAIT
2 NO RAM
3 NO RAM
4 NO RAM
5 NO RAM
6 NO RAM
7 NO RAM
8 NO RAM
9 NO RAM
...

I’m probably doing something wrong but so far it keeps eluding me :frowning:

Here is the demo code used:

from direct.showbase.ShowBase import ShowBase
from direct.task.Task import pause
from panda3d.core import Shader, AsyncFuture, FrameBufferProperties, WindowProperties, GraphicsPipe, GraphicsOutput, Texture
from panda3d.core import CardMaker, Camera, NodePath, OrthographicLens

def shader():
    return Shader.make(Shader.SL_GLSL,
                       vertex="""
#version 140
in vec4 p3d_Vertex;
uniform mat4 p3d_ModelViewProjectionMatrix;
void main()
{
    gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;
}
""",
                       fragment="""
#version 140
out vec4 color;
void main()
{
    color = vec4(gl_FragCoord.x / 256, gl_FragCoord.y / 256, 0.0, 1.0);
}

""")

base = ShowBase()

###############################################################################
fbprops = FrameBufferProperties()
fbprops.set_rgba_bits(8,8,8,0)

winprops = WindowProperties()
winprops.set_size(256, 256)

buffer_options = GraphicsPipe.BF_refuse_window

t1 = base.graphics_engine.make_output(base.win.get_pipe(), "t1", -1,
    fbprops, winprops,  buffer_options, base.win.get_gsg(), base.win)
print("New buffer", t1.get_fb_properties())
t1.disable_clears()
t1.set_sort(base.win.get_sort() - 100)
t1.set_active(False)

texture = Texture()
t1.add_render_texture(texture, GraphicsOutput.RTM_copy_ram, GraphicsOutput.RTP_color)

###############################################################################
dr = t1.make_display_region((0, 1, 0, 1))
dr.disable_clears()
dr.set_scissor_enabled(False)

###############################################################################
cm = CardMaker("plane")
cm.set_frame_fullscreen_quad()
root = NodePath(cm.generate())
root.set_depth_test(False)
root.set_depth_write(False)
root.set_shader(shader())

###############################################################################
cam = Camera("buffer-cam")
lens = OrthographicLens()
lens.set_film_size(2, 2)
lens.set_near_far(-1000, 1000)
cam.set_lens(lens)
camera = root.attach_new_node(cam)
dr.set_camera(camera)

###############################################################################
maker = CardMaker('card')
maker.setFrame(0, 1, 0, 1)

np = render.attachNewNode(maker.generate())
np.setPos(-0.5, 3, -0.5)

###############################################################################
t1_done = AsyncFuture()

async def trigger_t1():
    t1.set_active(True)
    t1.set_one_shot(True)
    print(globalClock.get_frame_count(), "Triggered")
    t1_done.set_result(t1)

async def apply_t1():
    # Force wait next frame
    await pause(0)
    print(globalClock.get_frame_count(), "WAIT")
    await t1_done
    while True:
        if texture.has_ram_image():
            break
        print(globalClock.get_frame_count(), "NO RAM")
        await pause(0)
    print(globalClock.get_frame_count(), "APPLY")
    print(texture)
    np.set_texture(texture, 1)

taskMgr.add(apply_t1())
taskMgr.add(trigger_t1())

base.run()

Well dumping the spam traces gives a clue :slight_smile:

When the texture is properly generated I have :

:display(spam): begin_frame(render): GLGraphicsBuffer t1 0x1a4b200
:display(spam): begin_frame(parasite): glxGraphicsWindow window1 0x1025010
:display:gsg:glgsg(debug): Creating color renderbuffer.
:display(spam): Drawing window t1
:display:gsg:glgsg(spam): glDisable(GL_SCISSOR_TEST)
:display:gsg:glgsg(spam): glViewportArrayv(0, 1, [0 0 256 256])
...
:display:gsg:glgsg(spam): glReadPixels(0, 0, 256, 256, GL_BGR, GL_UNSIGNED_BYTE)
:display(spam): end_frame(parasite): glxGraphicsWindow window1 0x1025010

:display(spam): begin_frame(render): glxGraphicsWindow window1 0x1025010

But when not, I have this instead :

:display(spam): begin_frame(render): GLGraphicsBuffer t1 0x2686200
:display(spam): begin_frame(parasite): glxGraphicsWindow window1 0x1aca010
:display:gsg:glgsg(debug): t1's host is not ready
:display(spam): Not drawing window t1
:display(spam): begin_frame(render): glxGraphicsWindow window1 0x1aca010
:display(spam): Not drawing window window1

It seems that in the latter case, the x11 window is still waiting for its configuration notification and, until it gets it, the rendering of the frames is disabled.

Now two questions: how I I check if a buffer (or a window) is ready to render is_active() on base.win and base.win.get_gsg() both return True (and _awaiting_configure of x11GraphicsWindow is a protected attribute) ?

Also, why is the render buffer flagged as parasite in the logs ? (I also tested with BF_refuse_parasite but the output is similar)