Переглянути джерело

Added BufferViewer object, the replacement for show-buffers

Josh Yelon 20 роки тому
батько
коміт
a531df3a4f
2 змінених файлів з 390 додано та 1 видалено
  1. 386 0
      direct/src/showbase/BufferViewer.py
  2. 4 1
      direct/src/showbase/ShowBase.py

+ 386 - 0
direct/src/showbase/BufferViewer.py

@@ -0,0 +1,386 @@
+from pandac.PandaModules import *
+from direct.task import Task
+from direct.directnotify.DirectNotifyGlobal import *
+from direct.showbase.DirectObject import DirectObject
+import math
+
+class BufferViewer(DirectObject):
+    notify = directNotify.newCategory('BufferViewer')
+
+    def __init__(self):
+        """Access: private.  Constructor."""
+        self.enabled = 0
+        self.sizex = 0
+        self.sizey = 0
+        self.position = "lrcorner"
+        self.layout = "hline"
+        self.include = "all"
+        self.exclude = "none"
+        self.cullbin = "fixed"
+        self.cullsort = 10000
+        self.cards = []
+        self.cardindex = 0
+        self.task = 0
+        self.window = 0
+        self.dirty = 1
+        self.accept("render-texture-targets-changed", self.refreshReadout)
+        if (ConfigVariableBool("show-buffers", 0).getValue()):
+            self.enable(1)
+
+    def refreshReadout(self):
+        """Force the readout to be refreshed.  This is usually invoked 
+        by GraphicsOutput::add_render_texture (via an event handler).
+        However, it is also possible to invoke it manually.  Currently,
+        the only time I know of that this is necessary is after a
+        window resize (and I ought to fix that)."""
+        self.dirty = 1
+
+    def isValidTextureSet(self, list):
+        """Access: private. Returns true if the parameter is a
+        list of GraphicsOutput and Texture, or the keyword 'all'."""
+        if (isinstance(x,list)):
+            for elt in x:
+                if (self.isValidTextureSet(x)==0):
+                    return 0
+        else:
+            return (x=="all") or (isinstance(x,Texture)) or (isinstance(x,GraphicsOutput))
+
+    def isEnabled(self):
+        """Returns true if the buffer viewer is currently enabled."""
+        return self.enabled
+
+    def enable(self, x):
+        """Turn the buffer viewer on or off.  The initial state of the
+        buffer viewer depends on the Config variable 'show-buffers'."""
+        if (x != 0) and (x != 1):
+            BufferViewer.notify.error('invalid parameter to BufferViewer.enable')
+            return
+        self.enabled = x
+        self.dirty = 1
+        if (x) and (self.task == 0):
+            self.task = taskMgr.add(self.maintainReadout, "buffer-viewer-maintain-readout",
+                                    priority=1)
+
+    def toggleEnable(self):
+        """Toggle the buffer viewer on or off.  The initial state of the
+        enable flag depends on the Config variable 'show-buffers'."""
+        self.enable(1-self.enabled)
+
+    def setCardSize(self, x, y):
+        """Set the size of each card.  The units are relative to
+        render2d (ie, 1x1 card is not square).  If one of the
+        dimensions is zero, then the viewer will choose a value
+        for that dimension so as to ensure that the aspect ratio
+        of the card matches the aspect ratio of the source-window.
+        If both dimensions are zero, the viewer uses a heuristic
+        to choose a reasonable size for the card.  The initial
+        value is (0,0)."""
+        if (x < 0) or (y < 0):
+            BufferViewer.notify.error('invalid parameter to BufferViewer.setCardSize')
+            return
+        self.sizex = x
+        self.sizey = y
+        self.dirty = 1
+    
+    def setPosition(self, pos):
+        """Set the position of the cards.  The valid values are:
+        * llcorner - put them in the lower-left  corner of the window
+        * lrcorner - put them in the lower-right corner of the window
+        * ulcorner - put them in the upper-left  corner of the window
+        * urcorner - put them in the upper-right corner of the window
+        * window   - put them in a separate window
+        The initial value is 'lrcorner'."""
+        valid=["llcorner","lrcorner","ulcorner","urcorner","window"]
+        if (valid.count(pos)==0):
+            BufferViewer.notify.error('invalid parameter to BufferViewer.setPosition')
+            BufferViewer.notify.error('valid parameters are: llcorner, lrcorner, ulcorner, urcorner, window')
+            return
+        if (pos == "window"):
+            BufferViewer.notify.error('BufferViewer.setPosition - "window" mode not implemented yet.')
+            return
+        self.position = pos
+        self.dirty = 1
+
+    def setLayout(self, lay):
+        """Set the layout of the cards.  The valid values are:
+        * vline - display them in a vertical line
+        * hline - display them in a horizontal line
+        * vgrid - display them in a vertical grid
+        * hgrid - display them in a horizontal grid
+        * cycle - display one card at a time, using selectCard/advanceCard
+        The default value is 'hline'."""
+        valid=["vline","hline","vgrid","hgrid","cycle"]
+        if (valid.count(lay)==0):
+            BufferViewer.notify.error('invalid parameter to BufferViewer.setLayout')
+            BufferViewer.notify.error('valid parameters are: vline, hline, vgrid, hgrid, cycle')
+            return
+        self.layout = lay
+        self.dirty = 1
+
+    def selectCard(self, i):
+        """Only useful when using setLayout('cycle').  Sets the index 
+        that selects which card to display.  The index is taken modulo
+        the actual number of cards."""
+        self.cardindex = i
+        self.dirty = 1
+
+    def advanceCard(self):
+        """Only useful when using setLayout('cycle').  Increments the index
+        that selects which card to display.  The index is taken modulo
+        the actual number of cards."""
+        self.cardindex += 1
+        self.dirty = 1
+    
+    def setInclude(self, x):
+        """Set the include-set for the buffer viewer.  The include-set
+        specifies which of the render-to-texture targets to display.
+        Valid inputs are the string 'all' (display every render-to-texture
+        target), or a list of GraphicsOutputs or Textures.  The initial
+        value is 'all'."""
+        if (self.isValidTextureSet(x)==0):
+            BufferViewer.notify.error('setInclude: must be list of textures and buffers, or "all"')
+            return
+        self.include = x
+        self.dirty = 1
+
+    def setExclude(self, x):
+        """Set the exclude-set for the buffer viewer.  The exclude-set
+        should be a list of GraphicsOutputs and Textures to ignore.
+        The exclude-set is subtracted from the include-set (so the excludes
+        effectively override the includes.)  The initial value is the
+        empty list."""
+        if (self.isValidTextureSet(x)==0):
+            BufferViewer.notify.error('setExclude: must be list of textures and buffers')
+            return
+        self.exclude = x
+        self.dirty = 1
+
+    def setSort(self, bin, sort):
+        """Set the cull-bin and sort-order for the output cards.  The
+        default value is 'fixed', 10000."""
+        self.cullbin = bin
+        self.cullsort = sort
+        self.dirty = 1
+
+    def analyzeTextureSet(self, x, set):
+        """Access: private.  Converts a list of GraphicsObject,
+        GraphicsEngine, and Texture into a table of Textures."""
+
+        if (isinstance(x,list)):
+            for elt in x:
+                self.analyzeTextureSet(elt, set)
+        elif (isinstance(x, Texture)):
+            set[x] = 1
+        elif (isinstance(x, GraphicsOutput)):
+            for itex in range(x.countTextures()):
+                tex = x.getTexture(itex)
+                set[tex] = 1
+        elif (isinstance(x, GraphicsEngine)):
+            for iwin in range(x.getNumWindows()):
+                win = x.getWindow(iwin)
+                self.analyzeTextureSet(win, set)
+        elif (x=="all"):
+            self.analyzeTextureSet(base.graphicsEngine, set)
+        else: return
+
+
+    def makeFrame(self, sizex, sizey):
+        """Access: private.  Each texture card is displayed with
+        a two-pixel wide frame (a ring of black and a ring of white).
+        This routine builds the frame geometry.  It is necessary to
+        be precise so that the frame exactly aligns to pixel
+        boundaries, and so that it doesn't overlap the card at all."""
+
+	format=GeomVertexFormat.getV3cp()
+	vdata=GeomVertexData('card-frame', format, Geom.UHDynamic)
+
+	vwriter=GeomVertexWriter(vdata, 'vertex')
+	cwriter=GeomVertexWriter(vdata, 'color')
+	
+        ringoffset = [0,1,1,2]
+        ringbright = [0,0,1,1]
+        for ring in range(4):
+            offsetx = (ringoffset[ring]*2.0) / float(sizex)
+            offsety = (ringoffset[ring]*2.0) / float(sizey)            
+            bright = ringbright[ring]
+            vwriter.addData3f(-1-offsetx, 0, -1-offsety)
+            vwriter.addData3f( 1+offsetx, 0, -1-offsety)
+	    vwriter.addData3f( 1+offsetx, 0,  1+offsety)
+	    vwriter.addData3f(-1-offsetx, 0,  1+offsety)
+            cwriter.addData3f(bright,bright,bright)
+            cwriter.addData3f(bright,bright,bright)
+            cwriter.addData3f(bright,bright,bright)
+            cwriter.addData3f(bright,bright,bright)
+
+	triangles=GeomTriangles(Geom.UHDynamic)
+        for i in range(2):
+            delta = i*8
+            triangles.addVertices(0+delta,4+delta,1+delta)
+            triangles.addVertices(1+delta,4+delta,5+delta)
+            triangles.addVertices(1+delta,5+delta,2+delta)
+            triangles.addVertices(2+delta,5+delta,6+delta)
+            triangles.addVertices(2+delta,6+delta,3+delta)
+            triangles.addVertices(3+delta,6+delta,7+delta)
+            triangles.addVertices(3+delta,7+delta,0+delta)
+            triangles.addVertices(0+delta,7+delta,4+delta)
+        triangles.closePrimitive()
+
+	geom=Geom(vdata)
+	geom.addPrimitive(triangles)
+	geomnode=GeomNode("card-frame")
+        geomnode.addGeom(geom)
+	return NodePath(geomnode)
+
+
+    def maintainReadout(self, task):
+        """Access: private.  Whenever necessary, rebuilds the entire
+        display from scratch.  This is only done when the configuration
+        parameters have changed."""
+
+        # If nothing has changed, don't update.
+        if (self.dirty==0):
+            return Task.cont
+        self.dirty = 0
+
+        # Delete the old set of cards.
+        for card in self.cards:
+            card.removeNode()
+        self.cards = []
+
+        # If not enabled, return.
+        if (self.enabled == 0):
+            self.task = 0
+            return Task.done
+        
+        # Generate the include and exclude sets.        
+        exclude = {}
+        include = {}
+        self.analyzeTextureSet(self.exclude,exclude)
+        self.analyzeTextureSet(self.include,include)
+        
+        # Generate a list of cards and the corresponding windows.
+        cards = []
+        wins = []
+        for iwin in range(base.graphicsEngine.getNumWindows()):
+            win = base.graphicsEngine.getWindow(iwin)
+            for itex in range(win.countTextures()):
+                tex = win.getTexture(itex)
+                if (include.has_key(tex)) and (exclude.has_key(tex)==0):
+                    card = win.getTextureCard()
+                    card.setTexture(tex)
+                    cards.append(card)
+                    wins.append(win)
+        self.cards = cards
+        if (len(cards)==0): return
+        ncards = len(cards)
+
+        # Decide how many rows and columns to use for the layout.
+        if (self.layout == "hline"):
+            rows = 1
+            cols = ncards
+        elif (self.layout == "vline"):
+            rows = ncards
+            cols = 1
+        elif (self.layout == "hgrid"):
+            rows = int(math.sqrt(ncards))
+            cols = rows
+            if (rows * cols < ncards): cols += 1
+            if (rows * cols < ncards): rows += 1
+        elif (self.layout == "vgrid"):
+            rows = int(math.sqrt(ncards))
+            cols = rows
+            if (rows * cols < ncards): rows += 1
+            if (rows * cols < ncards): cols += 1
+        elif (self.layout == "cycle"):
+            rows = 1
+            cols = 1
+        else: 
+            BufferViewer.notify.error('shouldnt ever get here in BufferViewer.maintainReadout')
+
+        # Choose an aspect ratio for the cards.  All card size
+        # calculations are done in pixel-units, using integers,
+        # in order to ensure that everything ends up neatly on
+        # a pixel boundary.
+
+        aspectx = wins[0].getXSize()
+        aspecty = wins[0].getYSize()
+        for win in wins:
+            if (win.getXSize()*aspecty) != (win.getYSize()*aspectx):
+                aspectx = 1
+                aspecty = 1
+
+        # Choose a card size.  If the user didn't specify a size,
+        # use a heuristic, otherwise, just follow orders.  The
+        # heuristic uses an initial card size of 42.66666667% of
+        # the screen vertically, which comes to 256 pixels on
+        # an 800x600 display.  Then, it double checks that the
+        # readout will fit on the screen, and if not, it shrinks it.
+        
+        if (float(self.sizex)==0.0) and (float(self.sizey)==0.0):
+
+            sizey = int(0.4266666667 * base.win.getYSize())
+            sizex = (sizey * aspectx) / aspecty
+            v_sizey = (base.win.getYSize() - (rows-1) - (rows*2)) / rows
+            v_sizex = (v_sizey * aspectx) / aspecty
+            if (v_sizey < sizey) or (v_sizex < sizex):
+                sizey = v_sizey
+                sizex = v_sizex
+            h_sizex = (base.win.getXSize() - (cols-1) - (cols*2)) / cols
+            h_sizey = (h_sizex * aspecty) / aspectx
+            if (h_sizey < sizey) or (h_sizex < sizex):
+                sizey = h_sizey
+                sizex = h_sizex
+
+        else:
+
+            sizex = int(self.sizex * 0.5 * base.win.getXSize())
+            sizey = int(self.sizey * 0.5 * base.win.getYSize())
+            if (sizex == 0): sizex = (sizey*aspectx) / aspecty
+            if (sizey == 0): sizey = (sizex*aspecty) / aspectx
+
+        # Convert from pixels to render2d-units.
+
+        fsizex = (2.0*sizex) / float(base.win.getXSize())
+        fsizey = (2.0*sizey) / float(base.win.getYSize())
+        fpixelx = 2.0 / float(base.win.getXSize())
+        fpixely = 2.0 / float(base.win.getYSize())
+
+        # Choose directional offsets
+        if (self.position == "llcorner"):
+            dirx = -1.0
+            diry = -1.0
+        elif (self.position == "lrcorner"):
+            dirx =  1.0
+            diry = -1.0
+        elif (self.position == "ulcorner"):
+            dirx = -1.0
+            diry =  1.0
+        elif (self.position == "urcorner"):
+            dirx =  1.0
+            diry =  1.0
+        else:
+            BufferViewer.notify.error('window mode not implemented yet')
+        
+        # Create the frame
+        frame = self.makeFrame(sizex,sizey)
+
+        # Now, position the cards on the screen.
+        # For each card, create a frame consisting of eight quads.
+        
+        for r in range(rows):
+            for c in range(cols):
+                index = c + r*cols
+                if (index < ncards):
+                    index = (index + self.cardindex) % len(cards)
+                    posx = dirx * (1.0 - fsizex*0.5 - 3*fpixelx - (c*(fsizex+6*fpixelx)))
+                    posy = diry * (1.0 - fsizey*0.5 - 3*fpixely - (r*(fsizey+6*fpixely)))
+                    placer = NodePath("card-structure")
+                    placer.setPos(posx,0,posy)
+                    placer.setScale(fsizex*0.5,1.0,fsizey*0.5)
+                    placer.setBin(self.cullbin, self.cullsort)
+                    placer.reparentTo(render2d)
+                    frame.instanceTo(placer)
+                    cards[index].reparentTo(placer)
+                    cards[index] = placer
+
+        return Task.cont

+ 4 - 1
direct/src/showbase/ShowBase.py

@@ -19,7 +19,7 @@ from PhysicsManagerGlobal import *
 #from direct.interval.IntervalManager import ivalMgr
 from direct.interval import IntervalManager
 from InputStateGlobal import inputState
-
+from direct.showbase.BufferViewer import BufferViewer
 from direct.task import Task
 import EventManager
 import math
@@ -260,6 +260,9 @@ class ShowBase(DirectObject.DirectObject):
         self.clientSleep = 0.0
         self.setSleep(sleepTime)
 
+        # Offscreen buffer viewing utility.
+        self.bufferViewer = BufferViewer()
+
         # Start Tk and DIRECT if specified by Config.prc
         fTk = self.config.GetBool('want-tk', 0)
         # Start DIRECT if specified in Config.prc or in cluster mode