2
0

BufferViewer.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. from pandac.PandaModules import *
  2. from direct.task import Task
  3. from direct.directnotify.DirectNotifyGlobal import *
  4. from direct.showbase.DirectObject import DirectObject
  5. import math
  6. class BufferViewer(DirectObject):
  7. notify = directNotify.newCategory('BufferViewer')
  8. def __init__(self):
  9. """Access: private. Constructor."""
  10. self.enabled = 0
  11. self.sizex = 0
  12. self.sizey = 0
  13. self.position = "lrcorner"
  14. self.layout = "hline"
  15. self.include = "all"
  16. self.exclude = "none"
  17. self.cullbin = "fixed"
  18. self.cullsort = 10000
  19. self.cards = []
  20. self.cardindex = 0
  21. self.task = 0
  22. self.window = 0
  23. self.dirty = 1
  24. self.accept("render-texture-targets-changed", self.refreshReadout)
  25. if (ConfigVariableBool("show-buffers", 0).getValue()):
  26. self.enable(1)
  27. def refreshReadout(self):
  28. """Force the readout to be refreshed. This is usually invoked
  29. by GraphicsOutput::add_render_texture (via an event handler).
  30. However, it is also possible to invoke it manually. Currently,
  31. the only time I know of that this is necessary is after a
  32. window resize (and I ought to fix that)."""
  33. self.dirty = 1
  34. def isValidTextureSet(self, list):
  35. """Access: private. Returns true if the parameter is a
  36. list of GraphicsOutput and Texture, or the keyword 'all'."""
  37. if (isinstance(x,list)):
  38. for elt in x:
  39. if (self.isValidTextureSet(x)==0):
  40. return 0
  41. else:
  42. return (x=="all") or (isinstance(x,Texture)) or (isinstance(x,GraphicsOutput))
  43. def isEnabled(self):
  44. """Returns true if the buffer viewer is currently enabled."""
  45. return self.enabled
  46. def enable(self, x):
  47. """Turn the buffer viewer on or off. The initial state of the
  48. buffer viewer depends on the Config variable 'show-buffers'."""
  49. if (x != 0) and (x != 1):
  50. BufferViewer.notify.error('invalid parameter to BufferViewer.enable')
  51. return
  52. self.enabled = x
  53. self.dirty = 1
  54. if (x) and (self.task == 0):
  55. self.task = taskMgr.add(self.maintainReadout, "buffer-viewer-maintain-readout",
  56. priority=1)
  57. def toggleEnable(self):
  58. """Toggle the buffer viewer on or off. The initial state of the
  59. enable flag depends on the Config variable 'show-buffers'."""
  60. self.enable(1-self.enabled)
  61. def setCardSize(self, x, y):
  62. """Set the size of each card. The units are relative to
  63. render2d (ie, 1x1 card is not square). If one of the
  64. dimensions is zero, then the viewer will choose a value
  65. for that dimension so as to ensure that the aspect ratio
  66. of the card matches the aspect ratio of the source-window.
  67. If both dimensions are zero, the viewer uses a heuristic
  68. to choose a reasonable size for the card. The initial
  69. value is (0,0)."""
  70. if (x < 0) or (y < 0):
  71. BufferViewer.notify.error('invalid parameter to BufferViewer.setCardSize')
  72. return
  73. self.sizex = x
  74. self.sizey = y
  75. self.dirty = 1
  76. def setPosition(self, pos):
  77. """Set the position of the cards. The valid values are:
  78. * llcorner - put them in the lower-left corner of the window
  79. * lrcorner - put them in the lower-right corner of the window
  80. * ulcorner - put them in the upper-left corner of the window
  81. * urcorner - put them in the upper-right corner of the window
  82. * window - put them in a separate window
  83. The initial value is 'lrcorner'."""
  84. valid=["llcorner","lrcorner","ulcorner","urcorner","window"]
  85. if (valid.count(pos)==0):
  86. BufferViewer.notify.error('invalid parameter to BufferViewer.setPosition')
  87. BufferViewer.notify.error('valid parameters are: llcorner, lrcorner, ulcorner, urcorner, window')
  88. return
  89. if (pos == "window"):
  90. BufferViewer.notify.error('BufferViewer.setPosition - "window" mode not implemented yet.')
  91. return
  92. self.position = pos
  93. self.dirty = 1
  94. def setLayout(self, lay):
  95. """Set the layout of the cards. The valid values are:
  96. * vline - display them in a vertical line
  97. * hline - display them in a horizontal line
  98. * vgrid - display them in a vertical grid
  99. * hgrid - display them in a horizontal grid
  100. * cycle - display one card at a time, using selectCard/advanceCard
  101. The default value is 'hline'."""
  102. valid=["vline","hline","vgrid","hgrid","cycle"]
  103. if (valid.count(lay)==0):
  104. BufferViewer.notify.error('invalid parameter to BufferViewer.setLayout')
  105. BufferViewer.notify.error('valid parameters are: vline, hline, vgrid, hgrid, cycle')
  106. return
  107. self.layout = lay
  108. self.dirty = 1
  109. def selectCard(self, i):
  110. """Only useful when using setLayout('cycle'). Sets the index
  111. that selects which card to display. The index is taken modulo
  112. the actual number of cards."""
  113. self.cardindex = i
  114. self.dirty = 1
  115. def advanceCard(self):
  116. """Only useful when using setLayout('cycle'). Increments the index
  117. that selects which card to display. The index is taken modulo
  118. the actual number of cards."""
  119. self.cardindex += 1
  120. self.dirty = 1
  121. def setInclude(self, x):
  122. """Set the include-set for the buffer viewer. The include-set
  123. specifies which of the render-to-texture targets to display.
  124. Valid inputs are the string 'all' (display every render-to-texture
  125. target), or a list of GraphicsOutputs or Textures. The initial
  126. value is 'all'."""
  127. if (self.isValidTextureSet(x)==0):
  128. BufferViewer.notify.error('setInclude: must be list of textures and buffers, or "all"')
  129. return
  130. self.include = x
  131. self.dirty = 1
  132. def setExclude(self, x):
  133. """Set the exclude-set for the buffer viewer. The exclude-set
  134. should be a list of GraphicsOutputs and Textures to ignore.
  135. The exclude-set is subtracted from the include-set (so the excludes
  136. effectively override the includes.) The initial value is the
  137. empty list."""
  138. if (self.isValidTextureSet(x)==0):
  139. BufferViewer.notify.error('setExclude: must be list of textures and buffers')
  140. return
  141. self.exclude = x
  142. self.dirty = 1
  143. def setSort(self, bin, sort):
  144. """Set the cull-bin and sort-order for the output cards. The
  145. default value is 'fixed', 10000."""
  146. self.cullbin = bin
  147. self.cullsort = sort
  148. self.dirty = 1
  149. def analyzeTextureSet(self, x, set):
  150. """Access: private. Converts a list of GraphicsObject,
  151. GraphicsEngine, and Texture into a table of Textures."""
  152. if (isinstance(x,list)):
  153. for elt in x:
  154. self.analyzeTextureSet(elt, set)
  155. elif (isinstance(x, Texture)):
  156. set[x] = 1
  157. elif (isinstance(x, GraphicsOutput)):
  158. for itex in range(x.countTextures()):
  159. tex = x.getTexture(itex)
  160. set[tex] = 1
  161. elif (isinstance(x, GraphicsEngine)):
  162. for iwin in range(x.getNumWindows()):
  163. win = x.getWindow(iwin)
  164. self.analyzeTextureSet(win, set)
  165. elif (x=="all"):
  166. self.analyzeTextureSet(base.graphicsEngine, set)
  167. else: return
  168. def makeFrame(self, sizex, sizey):
  169. """Access: private. Each texture card is displayed with
  170. a two-pixel wide frame (a ring of black and a ring of white).
  171. This routine builds the frame geometry. It is necessary to
  172. be precise so that the frame exactly aligns to pixel
  173. boundaries, and so that it doesn't overlap the card at all."""
  174. format=GeomVertexFormat.getV3cp()
  175. vdata=GeomVertexData('card-frame', format, Geom.UHDynamic)
  176. vwriter=GeomVertexWriter(vdata, 'vertex')
  177. cwriter=GeomVertexWriter(vdata, 'color')
  178. ringoffset = [0,1,1,2]
  179. ringbright = [0,0,1,1]
  180. for ring in range(4):
  181. offsetx = (ringoffset[ring]*2.0) / float(sizex)
  182. offsety = (ringoffset[ring]*2.0) / float(sizey)
  183. bright = ringbright[ring]
  184. vwriter.addData3f(-1-offsetx, 0, -1-offsety)
  185. vwriter.addData3f(1+offsetx, 0, -1-offsety)
  186. vwriter.addData3f(1+offsetx, 0, 1+offsety)
  187. vwriter.addData3f(-1-offsetx, 0, 1+offsety)
  188. cwriter.addData3f(bright,bright,bright)
  189. cwriter.addData3f(bright,bright,bright)
  190. cwriter.addData3f(bright,bright,bright)
  191. cwriter.addData3f(bright,bright,bright)
  192. triangles=GeomTriangles(Geom.UHDynamic)
  193. for i in range(2):
  194. delta = i*8
  195. triangles.addVertices(0+delta,4+delta,1+delta)
  196. triangles.addVertices(1+delta,4+delta,5+delta)
  197. triangles.addVertices(1+delta,5+delta,2+delta)
  198. triangles.addVertices(2+delta,5+delta,6+delta)
  199. triangles.addVertices(2+delta,6+delta,3+delta)
  200. triangles.addVertices(3+delta,6+delta,7+delta)
  201. triangles.addVertices(3+delta,7+delta,0+delta)
  202. triangles.addVertices(0+delta,7+delta,4+delta)
  203. triangles.closePrimitive()
  204. geom=Geom(vdata)
  205. geom.addPrimitive(triangles)
  206. geomnode=GeomNode("card-frame")
  207. geomnode.addGeom(geom)
  208. return NodePath(geomnode)
  209. def maintainReadout(self, task):
  210. """Access: private. Whenever necessary, rebuilds the entire
  211. display from scratch. This is only done when the configuration
  212. parameters have changed."""
  213. # If nothing has changed, don't update.
  214. if (self.dirty==0):
  215. return Task.cont
  216. self.dirty = 0
  217. # Delete the old set of cards.
  218. for card in self.cards:
  219. card.removeNode()
  220. self.cards = []
  221. # If not enabled, return.
  222. if (self.enabled == 0):
  223. self.task = 0
  224. return Task.done
  225. # Generate the include and exclude sets.
  226. exclude = {}
  227. include = {}
  228. self.analyzeTextureSet(self.exclude,exclude)
  229. self.analyzeTextureSet(self.include,include)
  230. # Generate a list of cards and the corresponding windows.
  231. cards = []
  232. wins = []
  233. for iwin in range(base.graphicsEngine.getNumWindows()):
  234. win = base.graphicsEngine.getWindow(iwin)
  235. for itex in range(win.countTextures()):
  236. tex = win.getTexture(itex)
  237. if (include.has_key(tex)) and (exclude.has_key(tex)==0):
  238. card = win.getTextureCard()
  239. card.setTexture(tex)
  240. cards.append(card)
  241. wins.append(win)
  242. self.cards = cards
  243. if (len(cards)==0): return
  244. ncards = len(cards)
  245. # Decide how many rows and columns to use for the layout.
  246. if (self.layout == "hline"):
  247. rows = 1
  248. cols = ncards
  249. elif (self.layout == "vline"):
  250. rows = ncards
  251. cols = 1
  252. elif (self.layout == "hgrid"):
  253. rows = int(math.sqrt(ncards))
  254. cols = rows
  255. if (rows * cols < ncards): cols += 1
  256. if (rows * cols < ncards): rows += 1
  257. elif (self.layout == "vgrid"):
  258. rows = int(math.sqrt(ncards))
  259. cols = rows
  260. if (rows * cols < ncards): rows += 1
  261. if (rows * cols < ncards): cols += 1
  262. elif (self.layout == "cycle"):
  263. rows = 1
  264. cols = 1
  265. else:
  266. BufferViewer.notify.error('shouldnt ever get here in BufferViewer.maintainReadout')
  267. # Choose an aspect ratio for the cards. All card size
  268. # calculations are done in pixel-units, using integers,
  269. # in order to ensure that everything ends up neatly on
  270. # a pixel boundary.
  271. aspectx = wins[0].getXSize()
  272. aspecty = wins[0].getYSize()
  273. for win in wins:
  274. if (win.getXSize()*aspecty) != (win.getYSize()*aspectx):
  275. aspectx = 1
  276. aspecty = 1
  277. # Choose a card size. If the user didn't specify a size,
  278. # use a heuristic, otherwise, just follow orders. The
  279. # heuristic uses an initial card size of 42.66666667% of
  280. # the screen vertically, which comes to 256 pixels on
  281. # an 800x600 display. Then, it double checks that the
  282. # readout will fit on the screen, and if not, it shrinks it.
  283. if (float(self.sizex)==0.0) and (float(self.sizey)==0.0):
  284. sizey = int(0.4266666667 * base.win.getYSize())
  285. sizex = (sizey * aspectx) / aspecty
  286. v_sizey = (base.win.getYSize() - (rows-1) - (rows*2)) / rows
  287. v_sizex = (v_sizey * aspectx) / aspecty
  288. if (v_sizey < sizey) or (v_sizex < sizex):
  289. sizey = v_sizey
  290. sizex = v_sizex
  291. h_sizex = (base.win.getXSize() - (cols-1) - (cols*2)) / cols
  292. h_sizey = (h_sizex * aspecty) / aspectx
  293. if (h_sizey < sizey) or (h_sizex < sizex):
  294. sizey = h_sizey
  295. sizex = h_sizex
  296. else:
  297. sizex = int(self.sizex * 0.5 * base.win.getXSize())
  298. sizey = int(self.sizey * 0.5 * base.win.getYSize())
  299. if (sizex == 0): sizex = (sizey*aspectx) / aspecty
  300. if (sizey == 0): sizey = (sizex*aspecty) / aspectx
  301. # Convert from pixels to render2d-units.
  302. fsizex = (2.0*sizex) / float(base.win.getXSize())
  303. fsizey = (2.0*sizey) / float(base.win.getYSize())
  304. fpixelx = 2.0 / float(base.win.getXSize())
  305. fpixely = 2.0 / float(base.win.getYSize())
  306. # Choose directional offsets
  307. if (self.position == "llcorner"):
  308. dirx = -1.0
  309. diry = -1.0
  310. elif (self.position == "lrcorner"):
  311. dirx = 1.0
  312. diry = -1.0
  313. elif (self.position == "ulcorner"):
  314. dirx = -1.0
  315. diry = 1.0
  316. elif (self.position == "urcorner"):
  317. dirx = 1.0
  318. diry = 1.0
  319. else:
  320. BufferViewer.notify.error('window mode not implemented yet')
  321. # Create the frame
  322. frame = self.makeFrame(sizex,sizey)
  323. # Now, position the cards on the screen.
  324. # For each card, create a frame consisting of eight quads.
  325. for r in range(rows):
  326. for c in range(cols):
  327. index = c + r*cols
  328. if (index < ncards):
  329. index = (index + self.cardindex) % len(cards)
  330. posx = dirx * (1.0 - fsizex*0.5 - 3*fpixelx - (c*(fsizex+6*fpixelx)))
  331. posy = diry * (1.0 - fsizey*0.5 - 3*fpixely - (r*(fsizey+6*fpixely)))
  332. placer = NodePath("card-structure")
  333. placer.setPos(posx,0,posy)
  334. placer.setScale(fsizex*0.5,1.0,fsizey*0.5)
  335. placer.setBin(self.cullbin, self.cullsort)
  336. placer.reparentTo(render2d)
  337. frame.instanceTo(placer)
  338. cards[index].reparentTo(placer)
  339. cards[index] = placer
  340. return Task.cont