|
|
@@ -20,8 +20,16 @@ class TexMemWatcher(DirectObject):
|
|
|
memory fragmentation issues.
|
|
|
"""
|
|
|
|
|
|
+ NextIndex = 1
|
|
|
+
|
|
|
def __init__(self, gsg = None, limit = None):
|
|
|
DirectObject.__init__(self)
|
|
|
+
|
|
|
+ # First, we'll need a name to uniquify the object.
|
|
|
+ self.name = 'tex-mem%s' % (TexMemWatcher.NextIndex)
|
|
|
+ TexMemWatcher.NextIndex += 1
|
|
|
+
|
|
|
+ self.cleanedUp = False
|
|
|
|
|
|
# If no GSG is specified, use the main GSG.
|
|
|
if gsg is None:
|
|
|
@@ -65,20 +73,15 @@ class TexMemWatcher(DirectObject):
|
|
|
# We should render at the end of the frame.
|
|
|
self.win.setSort(10000)
|
|
|
|
|
|
- # Set our GSG to a texture memory limit of 0. This will force
|
|
|
- # it to unload any textures as soon as they are offscreen, so
|
|
|
- # that our GSG's texture memory utilization will match that of
|
|
|
- # the real GSG.
|
|
|
- self.win.getGsg().getPreparedObjects().setGraphicsMemoryLimit(0)
|
|
|
-
|
|
|
# We don't need to clear the color buffer, since we'll be
|
|
|
# filling it with a texture. We also don't need to clear the
|
|
|
# depth buffer, since we won't be using it.
|
|
|
self.win.setClearColor(False)
|
|
|
self.win.setClearDepth(False)
|
|
|
|
|
|
- self.win.setWindowEvent('tex-mem-window')
|
|
|
- self.accept('tex-mem-window', self.windowEvent)
|
|
|
+ eventName = '%s-window' % (self.name)
|
|
|
+ self.win.setWindowEvent(eventName)
|
|
|
+ self.accept(eventName, self.windowEvent)
|
|
|
|
|
|
# Make a render2d in this new window.
|
|
|
self.render2d = NodePath('render2d')
|
|
|
@@ -96,9 +99,32 @@ class TexMemWatcher(DirectObject):
|
|
|
self.cam = self.render2d.attachNewNode(cam)
|
|
|
self.dr.setCamera(self.cam)
|
|
|
|
|
|
+ # We'll need a Mouse and a MouseWatcher in the data graph, so
|
|
|
+ # we can interact with the various textures.
|
|
|
+ self.mouse = base.dataRoot.attachNewNode(MouseAndKeyboard(self.win, 0, '%s-mouse' % (self.name)))
|
|
|
+ self.mw = MouseWatcher('%s-watcher' % (self.name))
|
|
|
+ mwnp = self.mouse.attachNewNode(self.mw)
|
|
|
+ bt = ButtonThrower('%s-thrower' % (self.name))
|
|
|
+ mwnp.attachNewNode(bt)
|
|
|
+ bt.setPrefix('button-%s-' % (self.name))
|
|
|
+ self.accept('button-%s-mouse1' % (self.name), self.mouseClick)
|
|
|
+
|
|
|
+ eventName = '%s-enter' % (self.name)
|
|
|
+ self.mw.setEnterPattern(eventName)
|
|
|
+ self.accept(eventName, self.enterRegion)
|
|
|
+
|
|
|
+ eventName = '%s-leave' % (self.name)
|
|
|
+ self.mw.setLeavePattern(eventName)
|
|
|
+ self.accept(eventName, self.leaveRegion)
|
|
|
+
|
|
|
+ # Now start handling up the actual stuff in the scene.
|
|
|
+
|
|
|
self.canvas = self.render2d.attachNewNode('canvas')
|
|
|
self.background = None
|
|
|
self.overflowing = False
|
|
|
+ self.nextTexRecordKey = 0
|
|
|
+ self.rollover = None
|
|
|
+ self.isolate = None
|
|
|
|
|
|
self.task = taskMgr.doMethodLater(0.5, self.updateTextures, 'TexMemWatcher')
|
|
|
|
|
|
@@ -123,6 +149,12 @@ class TexMemWatcher(DirectObject):
|
|
|
|
|
|
self.dynamicLimit = True
|
|
|
|
|
|
+ if not self.dynamicLimit:
|
|
|
+ # Set our GSG to limit itself to no more textures than we
|
|
|
+ # expect to display onscreen, so we don't go crazy with
|
|
|
+ # texture memory.
|
|
|
+ self.win.getGsg().getPreparedObjects().setGraphicsMemoryLimit(self.limit)
|
|
|
+
|
|
|
# The actual height of the canvas, including the overflow
|
|
|
# area. The texture memory itself is restricted to (0..1)
|
|
|
# vertically; anything higher than 1 is overflow.
|
|
|
@@ -138,20 +170,25 @@ class TexMemWatcher(DirectObject):
|
|
|
self.reconfigureWindow()
|
|
|
|
|
|
def cleanup(self):
|
|
|
- # Remove the window.
|
|
|
- if self.win:
|
|
|
+ if not self.cleanedUp:
|
|
|
+ self.cleanedUp = True
|
|
|
+
|
|
|
+ # Remove the window.
|
|
|
base.graphicsEngine.removeWindow(self.win)
|
|
|
self.win = None
|
|
|
+ self.gsg = None
|
|
|
+ self.pipe = None
|
|
|
+
|
|
|
+ # Remove the mouse.
|
|
|
+ self.mouse.detachNode()
|
|
|
|
|
|
- if self.task:
|
|
|
taskMgr.remove(self.task)
|
|
|
- self.task = None
|
|
|
-
|
|
|
- self.ignoreAll()
|
|
|
+ self.ignoreAll()
|
|
|
|
|
|
- self.canvas.getChildren().detach()
|
|
|
- self.texRecords = {}
|
|
|
- self.texPlacements = {}
|
|
|
+ self.canvas.getChildren().detach()
|
|
|
+ self.texRecordsByTex = {}
|
|
|
+ self.texRecordsByKey = {}
|
|
|
+ self.texPlacements = {}
|
|
|
|
|
|
|
|
|
def windowEvent(self, win):
|
|
|
@@ -167,6 +204,131 @@ class TexMemWatcher(DirectObject):
|
|
|
self.winSize = size
|
|
|
self.reconfigureWindow()
|
|
|
|
|
|
+ def enterRegion(self, region, buttonName):
|
|
|
+ """ the mouse has rolled over a texture. """
|
|
|
+ key, pi = map(int, region.getName().split(':'))
|
|
|
+ tr = self.texRecordsByKey.get(key)
|
|
|
+ if not tr:
|
|
|
+ return
|
|
|
+
|
|
|
+ self.setRollover(tr, pi)
|
|
|
+
|
|
|
+ def leaveRegion(self, region, buttonName):
|
|
|
+ """ the mouse is no longer over a texture. """
|
|
|
+ key, pi = map(int, region.getName().split(':'))
|
|
|
+ tr = self.texRecordsByKey.get(key)
|
|
|
+ if tr != self.rollover:
|
|
|
+ return
|
|
|
+
|
|
|
+ self.setRollover(None, None)
|
|
|
+
|
|
|
+ def mouseClick(self):
|
|
|
+ """ Received a mouse-click within the window. This isolates
|
|
|
+ the currently-highlighted texture into a full-window
|
|
|
+ presentation. """
|
|
|
+
|
|
|
+ if self.isolate:
|
|
|
+ # We're already isolating a texture; the click undoes this.
|
|
|
+ self.isolateTexture(None)
|
|
|
+ return
|
|
|
+
|
|
|
+ if self.rollover:
|
|
|
+ self.isolateTexture(self.rollover)
|
|
|
+
|
|
|
+ def setRollover(self, tr, pi):
|
|
|
+ """ Sets the highlighted texture (due to mouse rollover) to
|
|
|
+ the indicated texture, or None to clear it. """
|
|
|
+
|
|
|
+ if self.rollover:
|
|
|
+ self.rollover.clearRollover()
|
|
|
+
|
|
|
+ self.rollover = tr
|
|
|
+
|
|
|
+ if self.rollover:
|
|
|
+ self.rollover.showRollover(pi, self)
|
|
|
+
|
|
|
+ def isolateTexture(self, tr):
|
|
|
+ """ Isolates the indicated texture onscreen, or None to
|
|
|
+ restore normal mode. """
|
|
|
+
|
|
|
+ if self.isolate:
|
|
|
+ self.isolate.removeNode()
|
|
|
+ self.isolate = None
|
|
|
+
|
|
|
+ self.canvas.show()
|
|
|
+ self.background.clearColor()
|
|
|
+ self.win.getGsg().setTextureQualityOverride(Texture.QLDefault)
|
|
|
+
|
|
|
+ if not tr:
|
|
|
+ return
|
|
|
+
|
|
|
+ self.canvas.hide()
|
|
|
+ # Disable the red bar at the top.
|
|
|
+ self.background.setColor(1, 1, 1, 1, 1)
|
|
|
+
|
|
|
+ # Show the texture in all its filtered glory.
|
|
|
+ self.win.getGsg().setTextureQualityOverride(Texture.QLBest)
|
|
|
+
|
|
|
+ self.isolate = self.render2d.attachNewNode('isolate')
|
|
|
+ self.isolate.setBin('fixed', 0)
|
|
|
+
|
|
|
+ # Put a label on the bottom of the screen.
|
|
|
+ tn = TextNode('tn')
|
|
|
+ tn.setText('%s\n%s x %s\n%s bytes' % (
|
|
|
+ tr.tex.getName(), tr.tex.getXSize(), tr.tex.getYSize(),
|
|
|
+ tr.size))
|
|
|
+ tn.setAlign(tn.ACenter)
|
|
|
+ tn.setCardAsMargin(100.0, 100.0, 0.1, 0.1)
|
|
|
+ tn.setCardColor(0.1, 0.2, 0.4, 1)
|
|
|
+ tnp = self.isolate.attachNewNode(tn)
|
|
|
+ scale = 15.0 / self.winSize[1]
|
|
|
+ tnp.setScale(scale * self.winSize[1] / self.winSize[0], scale, scale)
|
|
|
+ tnp.setPos(0.5, 0, -tn.getBottom() * scale)
|
|
|
+
|
|
|
+ labelTop = tn.getHeight() * scale
|
|
|
+
|
|
|
+ # Make a card that shows the texture in actual pixel size, but
|
|
|
+ # don't let it exceed the screen size.
|
|
|
+ tw = tr.tex.getXSize()
|
|
|
+ th = tr.tex.getYSize()
|
|
|
+
|
|
|
+ wx = self.winSize[0] * 0.9
|
|
|
+ wy = self.winSize[1] * (1.0 - labelTop) * 0.9
|
|
|
+
|
|
|
+ w = min(tw, wx)
|
|
|
+ h = min(th, wy)
|
|
|
+
|
|
|
+ sx = w / tw
|
|
|
+ sy = h / th
|
|
|
+ s = min(sx, sy)
|
|
|
+
|
|
|
+ w = tw * s / float(self.winSize[0])
|
|
|
+ h = th * s / float(self.winSize[1])
|
|
|
+
|
|
|
+ cx = 0.5
|
|
|
+ cy = 1.0 - (1.0 - labelTop) * 0.5
|
|
|
+
|
|
|
+ l = cx - w * 0.5
|
|
|
+ r = cx + w * 0.5
|
|
|
+ b = cy - h * 0.5
|
|
|
+ t = cy + h * 0.5
|
|
|
+
|
|
|
+ cm = CardMaker('card')
|
|
|
+ cm.setFrame(l, r, b, t)
|
|
|
+ c = self.isolate.attachNewNode(cm.generate())
|
|
|
+ c.setTexture(tr.tex)
|
|
|
+ c.setTransparency(TransparencyAttrib.MAlpha)
|
|
|
+
|
|
|
+ ls = LineSegs('frame')
|
|
|
+ ls.setColor(0, 0, 0, 1)
|
|
|
+ ls.moveTo(l, 0, b)
|
|
|
+ ls.drawTo(r, 0, b)
|
|
|
+ ls.drawTo(r, 0, t)
|
|
|
+ ls.drawTo(l, 0, t)
|
|
|
+ ls.drawTo(l, 0, b)
|
|
|
+ self.isolate.attachNewNode(ls.create())
|
|
|
+
|
|
|
+
|
|
|
def reconfigureWindow(self):
|
|
|
""" Resets everything for a new window size. """
|
|
|
|
|
|
@@ -218,7 +380,7 @@ class TexMemWatcher(DirectObject):
|
|
|
totalSize = 0
|
|
|
|
|
|
texRecords = []
|
|
|
- neverVisited = copy.copy(self.texRecords)
|
|
|
+ neverVisited = copy.copy(self.texRecordsByTex)
|
|
|
for tex in self.gsg.getPreparedTextures():
|
|
|
# We have visited this texture; remove it from the
|
|
|
# neverVisited list.
|
|
|
@@ -229,14 +391,16 @@ class TexMemWatcher(DirectObject):
|
|
|
if tex.getResident(pgo):
|
|
|
size = tex.getDataSizeBytes(pgo)
|
|
|
|
|
|
- tr = self.texRecords.get(tex, None)
|
|
|
+ tr = self.texRecordsByTex.get(tex, None)
|
|
|
|
|
|
if size:
|
|
|
totalSize += size
|
|
|
active = tex.getActive(pgo)
|
|
|
if not tr:
|
|
|
# This is a new texture; need to record it.
|
|
|
- tr = TexRecord(tex, size, active)
|
|
|
+ key = self.nextTexRecordKey
|
|
|
+ self.nextTexRecordKey += 1
|
|
|
+ tr = TexRecord(key, tex, size, active)
|
|
|
texRecords.append(tr)
|
|
|
else:
|
|
|
tr.setActive(active)
|
|
|
@@ -254,7 +418,8 @@ class TexMemWatcher(DirectObject):
|
|
|
# textures that we didn't visit at all this pass.
|
|
|
for tex, tr in neverVisited.items():
|
|
|
self.unplaceTexture(tr)
|
|
|
- del self.texRecords[tex]
|
|
|
+ del self.texRecordsByTex[tex]
|
|
|
+ del self.texRecordsByKey[tr.key]
|
|
|
|
|
|
self.totalSize = totalSize
|
|
|
if totalSize > self.limit and self.dynamicLimit:
|
|
|
@@ -279,7 +444,8 @@ class TexMemWatcher(DirectObject):
|
|
|
self.overflowing = False
|
|
|
for tr in texRecords:
|
|
|
self.placeTexture(tr)
|
|
|
- self.texRecords[tr.tex] = tr
|
|
|
+ self.texRecordsByTex[tr.tex] = tr
|
|
|
+ self.texRecordsByKey[tr.key] = tr
|
|
|
|
|
|
return task.again
|
|
|
|
|
|
@@ -288,8 +454,11 @@ class TexMemWatcher(DirectObject):
|
|
|
""" Repacks all of the current textures. """
|
|
|
|
|
|
self.canvas.getChildren().detach()
|
|
|
- self.texRecords = {}
|
|
|
+ self.texRecordsByTex = {}
|
|
|
+ self.texRecordsByKey = {}
|
|
|
self.texPlacements = {}
|
|
|
+ self.mw.clearRegions()
|
|
|
+ self.setRollover(None, None)
|
|
|
self.w = 1
|
|
|
self.h = 1
|
|
|
|
|
|
@@ -301,8 +470,11 @@ class TexMemWatcher(DirectObject):
|
|
|
size = tex.getDataSizeBytes(pgo)
|
|
|
if size:
|
|
|
active = tex.getActive(pgo)
|
|
|
- tr = TexRecord(tex, size, active)
|
|
|
- self.texRecords[tex] = tr
|
|
|
+ key = self.nextTexRecordKey
|
|
|
+ self.nextTexRecordKey += 1
|
|
|
+ tr = TexRecord(key, tex, size, active)
|
|
|
+ self.texRecordsByTex[tr.tex] = tr
|
|
|
+ self.texRecordsByKey[tr.key] = tr
|
|
|
totalSize += size
|
|
|
|
|
|
self.totalSize = totalSize
|
|
|
@@ -313,6 +485,11 @@ class TexMemWatcher(DirectObject):
|
|
|
# Choose a suitable limit by rounding to the next power of two.
|
|
|
self.limit = Texture.upToPower2(self.totalSize)
|
|
|
|
|
|
+ # Set our GSG to limit itself to no more textures than we
|
|
|
+ # expect to display onscreen, so we don't go crazy with
|
|
|
+ # texture memory.
|
|
|
+ self.win.getGsg().getPreparedObjects().setGraphicsMemoryLimit(self.limit)
|
|
|
+
|
|
|
# Now make that into a 2-D rectangle of the appropriate shape,
|
|
|
# such that w * h == limit.
|
|
|
|
|
|
@@ -332,10 +509,11 @@ class TexMemWatcher(DirectObject):
|
|
|
self.h = h
|
|
|
|
|
|
self.canvas.setScale(1.0 / w, 1.0, 1.0 / h)
|
|
|
+ self.mw.setFrame(0, w, 0, h)
|
|
|
|
|
|
# Sort the regions from largest to smallest to maximize
|
|
|
# packing effectiveness.
|
|
|
- texRecords = self.texRecords.values()
|
|
|
+ texRecords = self.texRecordsByTex.values()
|
|
|
texRecords.sort(key = lambda tr: (-tr.w, -tr.h))
|
|
|
|
|
|
self.overflowing = False
|
|
|
@@ -347,10 +525,7 @@ class TexMemWatcher(DirectObject):
|
|
|
for tp in tr.placements:
|
|
|
del self.texPlacements[tp]
|
|
|
tr.placements = []
|
|
|
-
|
|
|
- if tr.root:
|
|
|
- tr.root.detachNode()
|
|
|
- tr.root = None
|
|
|
+ tr.clearCard(self)
|
|
|
|
|
|
def placeTexture(self, tr):
|
|
|
""" Places the texture somewhere on the canvas where it will
|
|
|
@@ -625,11 +800,14 @@ class TexMemWatcher(DirectObject):
|
|
|
|
|
|
|
|
|
class TexRecord:
|
|
|
- def __init__(self, tex, size, active):
|
|
|
+ def __init__(self, key, tex, size, active):
|
|
|
+ self.key = key
|
|
|
self.tex = tex
|
|
|
self.active = active
|
|
|
self.root = None
|
|
|
+ self.regions = []
|
|
|
self.placements = []
|
|
|
+ self.rollover = None
|
|
|
|
|
|
self.setSize(size)
|
|
|
|
|
|
@@ -658,10 +836,17 @@ class TexRecord:
|
|
|
self.matte.setColor((0.2, 0.2, 0.2, 1), 2)
|
|
|
self.card.setColor((0.4, 0.4, 0.4, 1), 2)
|
|
|
|
|
|
- def makeCard(self, tmw):
|
|
|
+ def clearCard(self, tmw):
|
|
|
if self.root:
|
|
|
self.root.detachNode()
|
|
|
+ self.root = None
|
|
|
+
|
|
|
+ for r in self.regions:
|
|
|
+ tmw.mw.removeRegion(r)
|
|
|
+ self.regions = []
|
|
|
|
|
|
+ def makeCard(self, tmw):
|
|
|
+ self.clearCard(tmw)
|
|
|
root = NodePath('root')
|
|
|
|
|
|
# A backing to put behind the card.
|
|
|
@@ -734,6 +919,113 @@ class TexRecord:
|
|
|
|
|
|
self.root = root
|
|
|
|
|
|
+ # Also, make one or more clickable MouseWatcherRegions.
|
|
|
+ assert self.regions == []
|
|
|
+ for pi in range(len(self.placements)):
|
|
|
+ p = self.placements[pi]
|
|
|
+ r = MouseWatcherRegion('%s:%s' % (self.key, pi), *p.p)
|
|
|
+ tmw.mw.addRegion(r)
|
|
|
+ self.regions.append(p)
|
|
|
+
|
|
|
+ def showRollover(self, pi, tmw):
|
|
|
+ self.clearRollover()
|
|
|
+ try:
|
|
|
+ p = self.placements[pi]
|
|
|
+ except IndexError:
|
|
|
+ return
|
|
|
+
|
|
|
+ # Center the rollover rectangle over the placement
|
|
|
+ l, r, b, t = p.p
|
|
|
+ cx0 = (l + r) * 0.5
|
|
|
+ cy0 = (b + t) * 0.5
|
|
|
+
|
|
|
+ # Exaggerate its size a bit
|
|
|
+ w = self.w
|
|
|
+ h = self.h
|
|
|
+
|
|
|
+ cx = cx0
|
|
|
+ if cx + w > tmw.w:
|
|
|
+ cx = tmw.w - w
|
|
|
+ if cx - w < 0:
|
|
|
+ cx = w
|
|
|
+
|
|
|
+ cy = cy0
|
|
|
+ if cy + h > tmw.h:
|
|
|
+ cy = tmw.h - h
|
|
|
+ if cy - h < 0:
|
|
|
+ cy = h
|
|
|
+
|
|
|
+ # But keep it within the window
|
|
|
+ l = max(cx - w, 0)
|
|
|
+ r = min(cx + w, tmw.w)
|
|
|
+ b = max(cy - h, 0)
|
|
|
+ t = min(cy + h, tmw.h)
|
|
|
+
|
|
|
+ # If it needs to be shrunk to fit within the window, keep it
|
|
|
+ # the same aspect ratio.
|
|
|
+ sx = float(r - l) / float(w)
|
|
|
+ sy = float(t - b) / float(h)
|
|
|
+ if sx != sy:
|
|
|
+ s = min(sx, sy)
|
|
|
+ w *= s / sx
|
|
|
+ h *= s / sy
|
|
|
+
|
|
|
+ cx = cx0
|
|
|
+ if cx + w > tmw.w:
|
|
|
+ cx = tmw.w - w
|
|
|
+ if cx - w < 0:
|
|
|
+ cx = w
|
|
|
+
|
|
|
+ cy = cy0
|
|
|
+ if cy + h > tmw.h:
|
|
|
+ cy = tmw.h - h
|
|
|
+ if cy - h < 0:
|
|
|
+ cy = h
|
|
|
+
|
|
|
+ l = max(cx - w, 0)
|
|
|
+ r = min(cx + w, tmw.w)
|
|
|
+ b = max(cy - h, 0)
|
|
|
+ t = min(cy + h, tmw.h)
|
|
|
+
|
|
|
+ self.rollover = tmw.canvas.attachNewNode('rollover')
|
|
|
+
|
|
|
+ cm = CardMaker('backing')
|
|
|
+ cm.setFrame(l, r, b, t)
|
|
|
+ cm.setColor(0.1, 0.3, 0.5, 1)
|
|
|
+ c = self.rollover.attachNewNode(cm.generate())
|
|
|
+ c.setBin('fixed', 100)
|
|
|
+
|
|
|
+ cm = CardMaker('card')
|
|
|
+ cm.setFrame(l, r, b, t)
|
|
|
+ c = self.rollover.attachNewNode(cm.generate())
|
|
|
+ c.setTexture(self.tex)
|
|
|
+ c.setBin('fixed', 110)
|
|
|
+ c.setTransparency(TransparencyAttrib.MAlpha)
|
|
|
+
|
|
|
+ # Label the font too.
|
|
|
+ tn = TextNode('tn')
|
|
|
+ tn.setText('%s\n%s x %s' % (self.tex.getName(), self.tex.getXSize(), self.tex.getYSize()))
|
|
|
+ tn.setAlign(tn.ACenter)
|
|
|
+ tn.setShadow(0.05, 0.05)
|
|
|
+ tnp = self.rollover.attachNewNode(tn)
|
|
|
+ scale = 20.0 / tmw.winSize[1] * tmw.h
|
|
|
+ tnp.setScale(scale)
|
|
|
+ tx = (l + r) * 0.5
|
|
|
+ ty = b + scale * 2
|
|
|
+ tnp.setPos(tx, 0, ty)
|
|
|
+ if tx + tn.getWidth() * scale * 0.5 > tmw.w:
|
|
|
+ tn.setAlign(tn.ARight)
|
|
|
+ tnp.setX(r)
|
|
|
+ elif tx - tn.getWidth() * scale * 0.5 < 0:
|
|
|
+ tn.setAlign(tn.ALeft)
|
|
|
+ tnp.setX(l)
|
|
|
+ tnp.setBin('fixed', 120)
|
|
|
+
|
|
|
+ def clearRollover(self):
|
|
|
+ if self.rollover:
|
|
|
+ self.rollover.removeNode()
|
|
|
+ self.rollover = None
|
|
|
+
|
|
|
class TexPlacement:
|
|
|
def __init__(self, l, r, b, t):
|
|
|
self.p = (l, r, b, t)
|