Copy buffer to image or better approach to blending images

Is there a fast way to copy offscreen buffer contents to an image? I found this question, but I don’t see where they are putting the buffer in the PNMImage. Currently I have this which is reading from a texture, but it takes 10 seconds on a 8192 squared image, which is undoable.

img = PNMImage(img_size, img_size, num_channels=1, maxval=65535)
img.set_color_type(PNMImageHeader.CT_grayscale)

for x in range(0, img_size):
    for y in range(0, img_size):
        peek.fetch_pixel(color, x, y)
        img.set_gray(x, y, color.x)

I’m making a terrain heightmap editor via layers like Photoshop or GIMP. So, the results of a shader go into an offscreen buffer. I thought I would copy this to an image for the layer. So, I can blend the layers together. This might not even be the best approach, but I think it has to become an image at some point to feed into the GeoMipTerrain.

Another approach I thought of was feeding the buffer, which might be null, straight into the next layer’s shader for blending, if that’s doable, but it still needs to become an image at the end for the terrain. Plus, it seems this is a lot more code.

Any help is greatly appreciated.

PS I tried passing the buffer into img.read(), but it didn’t match the signature.

It can be done like this.

img = PNMImage(1024, 1024, num_channels = 1)
self.texture.store(img)
code
from panda3d.core import FrameBufferProperties, WindowProperties, GraphicsPipe, Texture, GraphicsOutput, LColor, \
    CardMaker, GraphicsPipeSelection, Shader, PNMImage, PNMImageHeader, NodePath, Camera, OrthographicLens
from direct.showbase.ShowBase import ShowBase

# https://discourse.panda3d.org/t/write-in-to-image-in-shader-then-read-into-image-on-cpu-side/31141/10
# https://github.com/panda3d/panda3d/issues/1756

class App(ShowBase):
    def __init__(self):
        super().__init__(self)

        zup = loader.loadModel('zup-axis')
        zup.reparentTo(render)
        # 1 setup the buffer
        # https://docs.panda3d.org/1.10/python/programming/rendering-process/creating-windows-and-buffers
        pipe = base.pipe # GraphicsPipeSelection.get_global_ptr().make_module_pipe("pandagl")
        name = "shader buffer"
        sort = 0
        # fb_prop = FrameBufferProperties.getDefault()
        fb_prop = FrameBufferProperties()  # getDefault() results in an error
        fb_prop.set_rgba_bits(16, 0, 0, 0)

        # win_prop = WindowProperties.getDefault()
        img_size = 1024 # 8192
        win_prop = WindowProperties(size=(img_size, img_size))
        flags = GraphicsPipe.BFRefuseWindow
        # gsg and host are optional, but give more options for the buffer properties
        buffer = base.graphicsEngine.makeOutput(pipe, name=name, sort=sort, fb_prop=fb_prop, win_prop=win_prop, flags=flags, gsg=base.win.get_gsg(), host=base.win)

        # 2 create a texture to handle rendering and accessing on the CPU side
        self.texture = Texture("texture_in_memory")
        buffer.add_render_texture(self.texture, GraphicsOutput.RTM_copy_ram)

        # You need to create a node that will be rendered.
        my_render_node = NodePath("my_render_node")

        # Set up a camera with an orthographic lens(2D)
        camera_class = Camera("camera")
        lens = OrthographicLens()
        # Camera zoom
        lens.set_film_size(2, 2) # Set 1, 1
        lens.set_near_far(-1000, 1000)
        camera_class.set_lens(lens)
        # Attach the camera to the rendering node
        buffer_camera = NodePath(camera_class)
        buffer_camera.reparentTo(my_render_node)
        # Create a buffer rendering display
        render_region = buffer.make_display_region()
        render_region.camera = buffer_camera

        # 3 make some geometry to render to
        cm = CardMaker('card')
        card = my_render_node.attachNewNode(cm.generate())
        card.setTexture(self.texture)
        card.setPos(-0.5, 10.0, -0.5)

        # 4 load the shaders
        shader = Shader.load(Shader.SL_GLSL,
                             vertex="shaders/write_in_shader_read_in_cpu.vert",
                             fragment="shaders/write_in_shader_read_in_cpu.frag")
        card.setShader(shader)

        def readTexture(task):
            peek = self.texture.peek()
            if task.time > 0.01 and peek:

                img = PNMImage(1024, 1024, num_channels = 1)
                self.texture.store(img)

                img.write('write_in_shader_read_in_cpu.png')
                print("done saving image")

                return task.done
            return task.cont
        self.taskMgr.add(readTexture)

app = App()
app.run()
1 Like

Thanks. I didn’t think to look on Texture which is silly, since that’s where I’m reading the buffer from. It makes sense that’s where I would write it.