2
0

BufferViewer.py 19 KB

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