|
|
@@ -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
|