draw to texture > draw texture on screen

Me with czarek.tomczak are working on CEF+panda3d example (ultimate goal is to use CEF for GUI in panda3d)

Idea is simple - CEF returns BGRA (how handy!) buffer of window and all we need to do is draw it on screen.

Now question is - what is the best way to draw pixel buffer whose dimensions are not necessarily power of 2?

my first attempt was to use makeTextureBuffer / getTexture / modifyRamImage + OnscreenImage to do all the work. Few problems with that:

  1. Texture dimensions have to be power of 2, however we need texture with dimensions equal to screen resolution to be able to draw GUI all over screen.
  2. Modifying texture somehow does not work - im sure it is just me not knowing how to do it properly so please bear with me
from p3d import *

import cefpython_py27 as cefpython
import cefwindow
import sys
from direct.gui.OnscreenImage import OnscreenImage
from pandac.PandaModules import TransparencyAttrib
from direct.gui.DirectGui import DirectFrame
import libpanda, array, math

CEF_ON = False

class MyApp(object):
    def __init__(self):
        sys.excepthook = cefpython.ExceptHook
        screen_size = (800, 600)
        cefpython.Initialize()
        core.loadPrcFileData('', 'win-size %d %d' % screen_size) 
        
        if CEF_ON:
            windowInfo = cefpython.CefWindowInfo()
            windowInfo.SetAsOffScreen(base.win);
            self._browser = cefpython.CreateBrowser(windowInfo, browserSettings={}, navigateURL="http://google.com/")
        
        self._buf   = base.win.makeTextureBuffer("My Buffer", 1024, 1024)
        # texture for cef contents to render to
        self._tex   = self._buf.getTexture()
        
        # image to display texture on screen
        self._img   = OnscreenImage(image=self._tex, pos=(0, 0, 0))
        #self._img.setTransparency(TransparencyAttrib.MAlpha)
        
        def cef_step(task):
            img = self._tex.modifyRamImage()
            # sample for modification of img buffer
            buf = array.array('B')
            buf.fromstring(img.getData())
            
            for i in range(1024):
                buf[i * 4 + 0] = 100
                buf[i * 4 + 1] = 0
                buf[i * 4 + 2] = 100
                buf[i * 4 + 3] = 0
            
            img.setData(buf.tostring())
            #self._img.setImage(self._tex)
            
            if CEF_ON:
                cefpython.SingleMessageLoop()
            return task.cont
        taskMgr.add(cef_step, 'cef_step')
    
    def __del__(self):
        self._img.destroy()
        cefpython.Shutdown()

app = MyApp()
run()

And for p3d: pastebox.it/file/KyOwUJQMh54BxwM … hwY/p3d.py

Easiest solution: ignore the power-of-two requirement and create a texture of your required size, whatever it is. Most modern graphics cards can do this without trouble.

Most robust solution: create a texture that is the next-largest power-of-two, and only fill the bottom corner of its pixels. Use texture coordinates to apply only the relevant part of your texture to the window card. This is the way, for instance, that Panda renders non-power-2 movies in a Texture.

David

Still dont quite get it to work. As far as i understand Texture.FRgba8 should result in a pixel buffer of size wh4 right? 8 bits per pixel? Thing is resulting texture uses 16 bits per pixel. Also setting bits as in code below seems to modify them from bottom-left corner instead of bottom-right. This introduces some additional work for us later - CEF will return 8-bit pixel array starting from top-left corner. To achieve maximum performance i really would like buffer to be simple copy-paste with no conversions. So how should i set up 8-bit per pixel texture? And is it possible to “flip” it so pixel buffer starts at top-left corner?

My code:

        self._tex = Texture()
        self._tex.setup2dTexture(w, h, Texture.CMOff, Texture.FRgba8)
        
        cm = CardMaker("browser2d")
        cm.setFrame(-(w/h), (w/h), -1, 1)
        card = render2d.attachNewNode(cm.generate())
        card.setTexture(self._tex)
        self._dirty = True
        
        def cef_step(task):
            if self._dirty:
                img = self._tex.modifyRamImage()
                buf = array.array('H')
                buf.fromstring(img.getData())
    
                for i in range(w*h):
                    buf[i * 4 + 0] = 255    # B
                    buf[i * 4 + 1] = 255    # G
                    buf[i * 4 + 2] = 255    # R
                    buf[i * 4 + 3] = 0      # A
                
                img.setData(buf.tostring())
                self._dirty = False
            
            if CEF_ON:
                cefpython.SingleMessageLoop()
            return task.cont
        taskMgr.add(cef_step, 'cef_step')

EDIT:
turns out i had to use Texture.TUnsignedByte instead of Texture.CMOff for 8-bit texture. So only one question stands - can we have byte buffer start from top-left corner of texture?

You can’t flip the texture, but you can certainly flip your own UV’s that you use to render the texture.

David

i see, ill keep that in mind!

thanks for all the good advices :slight_smile: