Browse Source

Merge branch 'master' into cmake

Sam Edwards 11 years ago
parent
commit
93af05d67a
100 changed files with 1968 additions and 1002 deletions
  1. 4 4
      direct/src/directtools/DirectSelection.py
  2. 1 3
      direct/src/distributed/MsgTypes.py
  3. 1 3
      direct/src/distributed/MsgTypesCMU.py
  4. 2 3
      direct/src/gui/OnscreenGeom.py
  5. 2 3
      direct/src/gui/OnscreenImage.py
  6. 2 3
      direct/src/gui/OnscreenText.py
  7. 6 6
      direct/src/leveleditor/ObjectMgrBase.py
  8. 6 0
      direct/src/p3d/thirdparty.pdef
  9. 2 2
      direct/src/particles/Particles.py
  10. 1 0
      direct/src/plugin/handleStreamBuf.cxx
  11. 1 2
      direct/src/plugin/mkdir_complete.cxx
  12. 1 0
      direct/src/plugin/p3dAuthSession.cxx
  13. 4 0
      direct/src/plugin/p3dHost.cxx
  14. 1 4
      direct/src/showbase/ElementTree.py
  15. 1 1
      direct/src/showbase/PythonUtil.py
  16. 25 2
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  17. 1 0
      dtool/src/interrogate/interfaceMakerPythonNative.h
  18. 5 5
      makepanda/installpanda.py
  19. 4 6
      makepanda/makepanda.py
  20. 6 2
      makepanda/makepandacore.py
  21. 3 2
      panda/src/cocoadisplay/cocoaGraphicsWindow.h
  22. 71 5
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  23. 11 0
      panda/src/display/config_display.cxx
  24. 1 0
      panda/src/display/config_display.h
  25. 1 28
      panda/src/display/displayRegion.I
  26. 4 7
      panda/src/display/displayRegion.cxx
  27. 1 4
      panda/src/display/displayRegion.h
  28. 1 2
      panda/src/display/graphicsOutput.I
  29. 63 35
      panda/src/display/graphicsOutput.cxx
  30. 1 1
      panda/src/display/graphicsOutput.h
  31. 26 3
      panda/src/display/graphicsStateGuardian.I
  32. 7 6
      panda/src/display/graphicsStateGuardian.cxx
  33. 4 3
      panda/src/display/graphicsStateGuardian.h
  34. 18 5
      panda/src/display/graphicsWindow.cxx
  35. 2 1
      panda/src/display/graphicsWindow.h
  36. 16 8
      panda/src/display/stereoDisplayRegion.cxx
  37. 1 1
      panda/src/display/stereoDisplayRegion.h
  38. 1 1
      panda/src/display/subprocessWindow.cxx
  39. 8 2
      panda/src/downloadertools/multify.cxx
  40. 14 12
      panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx
  41. 8 4
      panda/src/dxgsg8/dxGraphicsStateGuardian8.h
  42. 5 1
      panda/src/dxgsg8/dxTextureContext8.cxx
  43. 1 1
      panda/src/dxgsg8/dxTextureContext8.h
  44. 1 1
      panda/src/dxgsg8/wdxGraphicsBuffer8.cxx
  45. 2 2
      panda/src/dxgsg8/wdxGraphicsBuffer8.h
  46. 12 12
      panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
  47. 8 4
      panda/src/dxgsg9/dxGraphicsStateGuardian9.h
  48. 5 1
      panda/src/dxgsg9/dxTextureContext9.cxx
  49. 1 1
      panda/src/dxgsg9/dxTextureContext9.h
  50. 1 1
      panda/src/dxgsg9/wdxGraphicsBuffer9.cxx
  51. 2 2
      panda/src/dxgsg9/wdxGraphicsBuffer9.h
  52. 2 2
      panda/src/egg/Sources.pp
  53. 2 0
      panda/src/egg/eggGroupNode_ext.cxx
  54. 2 1
      panda/src/ffmpeg/ffmpegAudioCursor.cxx
  55. 13 10
      panda/src/ffmpeg/ffmpegVirtualFile.cxx
  56. 0 1
      panda/src/ffmpeg/ffmpegVirtualFile.h
  57. 4 4
      panda/src/framework/windowFramework.cxx
  58. 3 1
      panda/src/framework/windowFramework.h
  59. 56 48
      panda/src/glstuff/glGraphicsBuffer_src.cxx
  60. 2 3
      panda/src/glstuff/glGraphicsBuffer_src.h
  61. 55 45
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  62. 2 3
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  63. 45 2
      panda/src/grutil/pfmVizzer.I
  64. 78 1
      panda/src/grutil/pfmVizzer.cxx
  65. 9 0
      panda/src/grutil/pfmVizzer.h
  66. 3 3
      panda/src/gsgbase/graphicsStateGuardianBase.h
  67. 5 1
      panda/src/pgraph/Sources.pp
  68. 9 7
      panda/src/pgraph/nodePath_ext.I
  69. 1 1
      panda/src/pgraph/nodePath_ext.cxx
  70. 5 0
      panda/src/pgraph/p3pgraph_ext_composite.cxx
  71. 0 253
      panda/src/pgraph/pandaNode.cxx
  72. 13 14
      panda/src/pgraph/pandaNode.h
  73. 259 0
      panda/src/pgraph/pandaNode_ext.cxx
  74. 50 0
      panda/src/pgraph/pandaNode_ext.h
  75. 0 159
      panda/src/pgraph/renderState.cxx
  76. 4 7
      panda/src/pgraph/renderState.h
  77. 174 0
      panda/src/pgraph/renderState_ext.cxx
  78. 42 0
      panda/src/pgraph/renderState_ext.h
  79. 0 203
      panda/src/pgraph/transformState.cxx
  80. 7 8
      panda/src/pgraph/transformState.h
  81. 214 0
      panda/src/pgraph/transformState_ext.cxx
  82. 43 0
      panda/src/pgraph/transformState_ext.h
  83. 25 0
      panda/src/pnmimage/pfmFile.I
  84. 107 0
      panda/src/pnmimage/pfmFile.cxx
  85. 8 0
      panda/src/pnmimage/pfmFile.h
  86. 7 5
      panda/src/putil/Sources.pp
  87. 16 0
      panda/src/putil/buttonHandle.cxx
  88. 1 0
      panda/src/putil/buttonHandle.h
  89. 141 0
      panda/src/putil/buttonMap.I
  90. 67 0
      panda/src/putil/buttonMap.cxx
  91. 81 0
      panda/src/putil/buttonMap.h
  92. 19 0
      panda/src/putil/buttonRegistry.cxx
  93. 1 0
      panda/src/putil/buttonRegistry.h
  94. 2 0
      panda/src/putil/config_util.cxx
  95. 1 0
      panda/src/putil/p3putil_composite1.cxx
  96. 4 0
      panda/src/rocket/rocketRegion_ext.cxx
  97. 6 11
      panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx
  98. 2 2
      panda/src/tinydisplay/tinyGraphicsStateGuardian.h
  99. 1 1
      panda/src/wgldisplay/wglGraphicsBuffer.cxx
  100. 1 1
      panda/src/wgldisplay/wglGraphicsBuffer.h

+ 4 - 4
direct/src/directtools/DirectSelection.py

@@ -83,7 +83,7 @@ class SelectedNodePaths(DirectObject):
                     break
 
         # Get this pointer
-        id = nodePath.id()
+        id = nodePath.get_key()
         # First see if its already in the selected dictionary
         dnp = self.getSelectedDict(id)
         # If so, deselect it
@@ -104,7 +104,7 @@ class SelectedNodePaths(DirectObject):
                 # Show its bounding box
                 dnp.highlight(fRecompute = 0)
             # Add it to the selected dictionary
-            self.selectedDict[dnp.id()] = dnp
+            self.selectedDict[dnp.get_key()] = dnp
             self.selectedList.append(dnp) # [gjeon]
 
         # And update last
@@ -117,7 +117,7 @@ class SelectedNodePaths(DirectObject):
     def deselect(self, nodePath):
         """ Deselect the specified node path """
         # Get this pointer
-        id = nodePath.id()
+        id = nodePath.get_key()
         # See if it is in the selected dictionary
         dnp = self.getSelectedDict(id)
         if dnp:
@@ -240,7 +240,7 @@ class SelectedNodePaths(DirectObject):
 
     def getDirectNodePath(self, nodePath):
         # Get this pointer
-        id = nodePath.id()
+        id = nodePath.get_key()
         # First check selected dict
         dnp = self.getSelectedDict(id)
         if dnp:

+ 1 - 3
direct/src/distributed/MsgTypes.py

@@ -107,9 +107,7 @@ MsgName2Id = {
 MsgId2Names = invertDictLossless(MsgName2Id)
     
 # put msg names in module scope, assigned to msg value
-for name, value in MsgName2Id.items():
-    exec('%s = %s' % (name, value))
-del name, value
+globals().update(MsgName2Id)
 
 # These messages are ignored when the client is headed to the quiet zone
 QUIET_ZONE_IGNORED_LIST = [

+ 1 - 3
direct/src/distributed/MsgTypesCMU.py

@@ -26,6 +26,4 @@ MsgName2Id = {
 MsgId2Names = invertDictLossless(MsgName2Id)
     
 # put msg names in module scope, assigned to msg value
-for name, value in MsgName2Id.items():
-    exec('%s = %s' % (name, value))
-del name, value
+globals().update(MsgName2Id)

+ 2 - 3
direct/src/gui/OnscreenGeom.py

@@ -113,8 +113,7 @@ class OnscreenGeom(DirectObject, NodePath):
         for option, value in kw.items():
             # Use option string to access setter function
             try:
-                setter = eval('self.set' +
-                              string.upper(option[0]) + option[1:])
+                setter = getattr(self, 'set' + option[0].upper() + option[1:])
                 if (((setter == self.setPos) or
                      (setter == self.setHpr) or
                      (setter == self.setScale)) and
@@ -133,7 +132,7 @@ class OnscreenGeom(DirectObject, NodePath):
     def cget(self, option):
         # Get current configuration setting.
         # This is for compatibility with DirectGui functions
-        getter = eval('self.get' + string.upper(option[0]) + option[1:])
+        getter = getattr(self, 'get' + option[0].upper() + option[1:])
         return getter()
 
     # Allow index style refererences

+ 2 - 3
direct/src/gui/OnscreenImage.py

@@ -130,8 +130,7 @@ class OnscreenImage(DirectObject, NodePath):
         for option, value in kw.items():
             # Use option string to access setter function
             try:
-                setter = eval('self.set' +
-                              string.upper(option[0]) + option[1:])
+                setter = getattr(self, 'set' + option[0].upper() + option[1:])
                 if (((setter == self.setPos) or
                      (setter == self.setHpr) or
                      (setter == self.setScale)) and
@@ -150,7 +149,7 @@ class OnscreenImage(DirectObject, NodePath):
     def cget(self, option):
         # Get current configuration setting.
         # This is for compatibility with DirectGui functions
-        getter = eval('self.get' + string.upper(option[0]) + option[1:])
+        getter = getattr(self, 'get' + option[0].upper() + option[1:])
         return getter()
 
     # Allow index style refererences

+ 2 - 3
direct/src/gui/OnscreenText.py

@@ -381,8 +381,7 @@ class OnscreenText(DirectObject, NodePath):
         for option, value in kw.items():
             # Use option string to access setter function
             try:
-                setter = eval('self.set' +
-                              string.upper(option[0]) + option[1:])
+                setter = getattr(self, 'set' + option[0].upper() + option[1:])
                 if setter == self.setPos:
                     setter(value[0], value[1])
                 else:
@@ -397,7 +396,7 @@ class OnscreenText(DirectObject, NodePath):
     def cget(self, option):
         # Get current configuration setting.
         # This is for compatibility with DirectGui functions
-        getter = eval('self.get' + string.upper(option[0]) + option[1:])
+        getter = getattr(self, 'get' + option[0].upper() + option[1:])
         return getter()
 
     def setAlign(self, align):

+ 6 - 6
direct/src/leveleditor/ObjectMgrBase.py

@@ -189,9 +189,9 @@ class ObjectMgrBase:
                         if funcName.startswith('.'):
                             # when it's using default objectHandler
                             if self.editor:
-                                func = Functor(eval("self.editor.objectHandler%s"%funcName))
+                                func = Functor(getattr(self.editor, "objectHandler%s"%funcName))
                             else: # when loaded outside of LE
-                                func = Functor(eval("base.objectHandler%s"%funcName))                        
+                                func = Functor(getattr(base, "objectHandler%s"%funcName))                        
                         else:
                             # when it's not using default objectHandler, whole name of the handling obj
                             # should be included in function name
@@ -686,11 +686,11 @@ class ObjectMgrBase:
                 if type(funcName) == types.StringType:
                     if funcName.startswith('.'):
                         if self.editor:
-                            func = Functor(eval("self.editor.objectHandler%s"%funcName), **kwargs)
-                            undoFunc = Functor(eval("self.editor.objectHandler%s"%funcName), **undoKwargs)
+                            func = Functor(getattr(self.editor, "objectHandler%s"%funcName), **kwargs)
+                            undoFunc = Functor(getattr(self.editor, "objectHandler%s"%funcName), **undoKwargs)
                         else: # when loaded outside of LE
-                            func = Functor(eval("base.objectHandler%s"%funcName), **kwargs)
-                            undoFunc = Functor(eval("base.objectHandler%s"%funcName), **undoKwargs)                    
+                            func = Functor(getattr(base, "objectHandler%s"%funcName), **kwargs)
+                            undoFunc = Functor(getattr(base, ".objectHandler%s"%funcName), **undoKwargs)                    
                     else:
                         func = Functor(eval(funcName), **kwargs)
                         undoFunc = Functor(eval(funcName), **undoKwargs)

+ 6 - 0
direct/src/p3d/thirdparty.pdef

@@ -147,3 +147,9 @@ class box2d(package):
     require('panda3d')
 
     module('Box2D', required = True)
+
+class pyglet(package):
+    config(display_name = "pyglet")
+    require('panda3d')
+
+    module('pyglet', required = True)

+ 2 - 2
direct/src/particles/Particles.py

@@ -354,7 +354,7 @@ class Particles(ParticleSystem):
                     else:
                         file.write(targ+'.renderer.setColorBlendMode(ColorBlendAttrib.%s)\n' % cbmLut[cbMode])
             cim = self.renderer.getColorInterpolationManager()
-            segIdList = eval('['+cim.getSegmentIdList().replace(' ',', ')+']')
+            segIdList = [int(seg) for seg in cim.getSegmentIdList().split()]
             for sid in segIdList:
                 seg = cim.getSegment(sid)
                 if seg.isEnabled():
@@ -457,7 +457,7 @@ class Particles(ParticleSystem):
                     else:
                         file.write(targ+'.renderer.setColorBlendMode(ColorBlendAttrib.%s)\n' % cbmLut[cbMode])
             cim = self.renderer.getColorInterpolationManager()
-            segIdList = eval('['+cim.getSegmentIdList().replace(' ',', ')+']')
+            segIdList = [int(seg) for seg in cim.getSegmentIdList().split()]
             for sid in segIdList:
                 seg = cim.getSegment(sid)
                 if seg.isEnabled():

+ 1 - 0
direct/src/plugin/handleStreamBuf.cxx

@@ -22,6 +22,7 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <unistd.h>
 #endif  // _WIN32
 
 #if !defined(_WIN32) && !defined(__APPLE__) && !defined(__FreeBSD__)

+ 1 - 2
direct/src/plugin/mkdir_complete.cxx

@@ -24,10 +24,9 @@
 #include <sys/stat.h>  // for mkdir()
 #include <errno.h>
 #include <string.h>     // strerror()
+#include <unistd.h>
 #endif
 
-
-
 ////////////////////////////////////////////////////////////////////
 //     Function: get_dirname
 //  Description: Returns the directory component of the indicated

+ 1 - 0
direct/src/plugin/p3dAuthSession.cxx

@@ -28,6 +28,7 @@
 #include <sys/select.h>
 #include <signal.h>
 #include <dlfcn.h>
+#include <unistd.h>
 #endif
 
 ////////////////////////////////////////////////////////////////////

+ 4 - 0
direct/src/plugin/p3dHost.cxx

@@ -22,6 +22,10 @@
 
 #include <algorithm>
 
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DHost::Constructor
 //       Access: Private

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

@@ -749,10 +749,7 @@ def _encode(s, encoding):
     except AttributeError:
         return s # 1.5.2: assume the string uses the right encoding
 
-if sys.version[:3] == "1.5":
-    _escape = re.compile(r"[&<>\"\x80-\xff]+") # 1.5.2
-else:
-    _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"'))
+_escape = re.compile(u"[&<>\"\u0080-\uffff]+")
 
 _escape_map = {
     "&": "&amp;",

+ 1 - 1
direct/src/showbase/PythonUtil.py

@@ -4188,7 +4188,7 @@ def unescapeHtmlString(s):
             char = ' '
         elif char == '%':
             if i < (len(s)-2):
-                num = eval('0x' + s[i+1:i+3])
+                num = int(s[i+1:i+3], 16)
                 char = chr(num)
                 i += 2
         i += 1

+ 25 - 2
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -508,9 +508,9 @@ get_slotted_function_def(Object *obj, Function *func, SlottedFunctionDef &def) {
       return true;
     }
 
-    if (method_name == "next") {
+    if (method_name == "next" || method_name == "__next__") {
       def._answer_location = "tp_iternext";
-      def._wrapper_type = WT_no_params;
+      def._wrapper_type = WT_iter_next;
       return true;
     }
   }
@@ -1701,6 +1701,29 @@ write_module_class(ostream &out, Object *obj) {
         }
         break;
 
+      case WT_iter_next:
+        // PyObject *func(PyObject *self)
+        // However, returns NULL instead of None
+        {
+          Function *func = rfi->first;
+          out << "//////////////////\n";
+          out << "//  A wrapper function to satisfy Python's internal calling conventions.\n";
+          out << "//     " << ClassName << " ..." << rfi->second._answer_location << " = " << methodNameFromCppName(func, export_class_name, false) << "\n";
+          out << "//////////////////\n";
+          out << "static PyObject *" <<  func->_name << methodNameFromCppName(func, export_class_name, false) << "(PyObject *self) {\n";
+          out << "  PyObject *args = Py_BuildValue(\"()\");\n";
+          out << "  PyObject *result = " << func->_name << "(self, args, NULL);\n";
+          out << "  Py_DECREF(args);\n";
+          out << "  if (result == Py_None) {\n";
+          out << "    Py_DECREF(Py_None);\n";
+          out << "    return NULL;\n";
+          out << "  } else {\n";
+          out << "    return result;\n";
+          out << "  }\n";
+          out << "}\n\n";
+        }
+        break;
+
       case WT_none:
         break;
       }

+ 1 - 0
dtool/src/interrogate/interfaceMakerPythonNative.h

@@ -76,6 +76,7 @@ private:
     WT_inquiry,
     WT_getbuffer,
     WT_releasebuffer,
+    WT_iter_next,
   };
 
   class SlottedFunctionDef {

+ 5 - 5
makepanda/installpanda.py

@@ -204,15 +204,15 @@ def InstallRuntime(destdir="", prefix="/usr", outputdir="built"):
         if sys.platform.startswith("freebsd"):
             oscmd("mkdir -m 0755 -p "+destdir+libdir+"/browser_plugins/symlinks/gecko19")
             oscmd("mkdir -m 0755 -p "+destdir+libdir+"/libxul/plugins")
-            oscmd("ln -s "+libdir+"/nppanda3d.so  "+destdir+libdir+"/browser_plugins/symlinks/gecko19/nppanda3d.so")
-            oscmd("ln -s "+libdir+"/nppanda3d.so  "+destdir+libdir+"/libxul/plugins/nppanda3d.so")
+            oscmd("ln -f -s "+libdir+"/nppanda3d.so  "+destdir+libdir+"/browser_plugins/symlinks/gecko19/nppanda3d.so")
+            oscmd("ln -f -s "+libdir+"/nppanda3d.so  "+destdir+libdir+"/libxul/plugins/nppanda3d.so")
         else:
             oscmd("mkdir -m 0755 -p "+destdir+libdir+"/mozilla/plugins")
             oscmd("mkdir -m 0755 -p "+destdir+libdir+"/mozilla-firefox/plugins")
             oscmd("mkdir -m 0755 -p "+destdir+libdir+"/xulrunner-addons/plugins")
-            oscmd("ln -s "+libdir+"/nppanda3d.so  "+destdir+libdir+"/mozilla/plugins/nppanda3d.so")
-            oscmd("ln -s "+libdir+"/nppanda3d.so  "+destdir+libdir+"/mozilla-firefox/plugins/nppanda3d.so")
-            oscmd("ln -s "+libdir+"/nppanda3d.so  "+destdir+libdir+"/xulrunner-addons/plugins/nppanda3d.so")
+            oscmd("ln -f -s "+libdir+"/nppanda3d.so  "+destdir+libdir+"/mozilla/plugins/nppanda3d.so")
+            oscmd("ln -f -s "+libdir+"/nppanda3d.so  "+destdir+libdir+"/mozilla-firefox/plugins/nppanda3d.so")
+            oscmd("ln -f -s "+libdir+"/nppanda3d.so  "+destdir+libdir+"/xulrunner-addons/plugins/nppanda3d.so")
     WriteMimeFile(destdir+prefix+"/share/mime-info/panda3d-runtime.mime", MIME_INFO_PLUGIN)
     WriteKeysFile(destdir+prefix+"/share/mime-info/panda3d-runtime.keys", MIME_INFO_PLUGIN)
     WriteMimeXMLFile(destdir+prefix+"/share/mime/packages/panda3d-runtime.xml", MIME_INFO_PLUGIN)

+ 4 - 6
makepanda/makepanda.py

@@ -715,12 +715,12 @@ if (COMPILER=="GCC"):
             # We use a statically linked libboost_python on OSX
             rocket_libs += ("boost_python",)
         SmartPkgEnable("ROCKET",    "",          rocket_libs, "Rocket/Core.h")
-        SmartPkgEnable("GTK2",      "gtk+-2.0")
 
     SmartPkgEnable("JPEG",      "",          ("jpeg"), "jpeglib.h")
     SmartPkgEnable("OPENSSL",   "openssl",   ("ssl", "crypto"), ("openssl/ssl.h", "openssl/crypto.h"))
     SmartPkgEnable("PNG",       "libpng",    ("png"), "png.h", tool = "libpng-config")
     SmartPkgEnable("ZLIB",      "zlib",      ("z"), "zlib.h")
+    SmartPkgEnable("GTK2",      "gtk+-2.0")
 
     if (RTDIST and GetHost() == "darwin" and "PYTHONVERSION" in SDK):
         # Don't use the framework for the OSX rtdist build. I'm afraid it gives problems somewhere.
@@ -3200,8 +3200,7 @@ if (not RUNTIME):
   TargetAdd('libp3pgraph.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3pgraph.in', opts=['IMOD:core', 'ILIB:libp3pgraph', 'SRCDIR:panda/src/pgraph'])
   TargetAdd('libp3pgraph_igate.obj', input='libp3pgraph.in', opts=["DEPENDENCYONLY","BIGOBJ"])
-  TargetAdd('p3pgraph_nodePath_ext.obj', opts=OPTS, input='nodePath_ext.cxx')
-  TargetAdd('p3pgraph_nodePathCollection_ext.obj', opts=OPTS, input='nodePathCollection_ext.cxx')
+  TargetAdd('p3pgraph_ext_composite.obj', opts=OPTS, input='p3pgraph_ext_composite.cxx')
 
 #
 # DIRECTORY: panda/src/cull/
@@ -3543,8 +3542,7 @@ if (not RUNTIME):
   TargetAdd('libpanda.dll', input='p3putil_typedWritable_ext.obj')
   TargetAdd('libpanda.dll', input='p3pnmimage_pfmFile_ext.obj')
   TargetAdd('libpanda.dll', input='p3gobj_geomVertexArrayData_ext.obj')
-  TargetAdd('libpanda.dll', input='p3pgraph_nodePath_ext.obj')
-  TargetAdd('libpanda.dll', input='p3pgraph_nodePathCollection_ext.obj')
+  TargetAdd('libpanda.dll', input='p3pgraph_ext_composite.obj')
   TargetAdd('libpanda.dll', input='p3display_graphicsStateGuardian_ext.obj')
 
   if PkgSkip("FREETYPE")==0:
@@ -4721,7 +4719,7 @@ if (RTDIST or RUNTIME):
 #
 
 if (RUNTIME and PkgSkip("NPAPI")==0):
-  OPTS=['DIR:direct/src/plugin_npapi', 'RUNTIME']
+  OPTS=['DIR:direct/src/plugin_npapi', 'RUNTIME', 'GTK2']
   if GetTarget() == 'windows':
     nppanda3d_rc = {"name" : "Panda3D Game Engine Plug-in",
                     "version" : VERSION,

+ 6 - 2
makepanda/makepandacore.py

@@ -2333,8 +2333,12 @@ def CopyFile(dstfile, srcfile):
         if (fnl < 0): fn = srcfile
         else: fn = srcfile[fnl+1:]
         dstfile = dstdir + fn
-    if (NeedsBuild([dstfile], [srcfile])):
-        WriteBinaryFile(dstfile, ReadBinaryFile(srcfile))
+    if NeedsBuild([dstfile], [srcfile]):
+        if os.path.islink(srcfile):
+            # Preserve symlinks
+            os.symlink(os.readlink(srcfile), dstfile)
+        else:
+            WriteBinaryFile(dstfile, ReadBinaryFile(srcfile))
         JustBuilt([dstfile], [srcfile])
 
 def CopyAllFiles(dstdir, srcdir, suffix=""):

+ 3 - 2
panda/src/cocoadisplay/cocoaGraphicsWindow.h

@@ -58,6 +58,7 @@ public:
   void handle_mouse_button_event(int button, bool down);
   void handle_mouse_moved_event(bool in_window, double x, double y, bool absolute);
   void handle_wheel_event(double x, double y);
+  virtual ButtonMap *get_keyboard_map() const;
 
   INLINE NSWindow *get_nswindow() const;
   INLINE NSView *get_nsview() const;
@@ -81,8 +82,8 @@ private:
   NSImage *load_image(const Filename &filename);
 
   void handle_modifier(NSUInteger modifierFlags, NSUInteger mask, ButtonHandle button);
-  ButtonHandle map_key(unsigned short c);
-  ButtonHandle map_raw_key(unsigned short keycode);
+  ButtonHandle map_key(unsigned short c) const;
+  ButtonHandle map_raw_key(unsigned short keycode) const;
 
 private:
   NSWindow *_window;

+ 71 - 5
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -40,7 +40,7 @@
 #import <AppKit/NSScreen.h>
 #import <AppKit/NSText.h>
 #import <OpenGL/OpenGL.h>
-//#import <Carbon/Carbon.h>
+#import <Carbon/Carbon.h>
 
 TypeHandle CocoaGraphicsWindow::_type_handle;
 
@@ -1731,13 +1731,70 @@ handle_wheel_event(double x, double y) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CocoaGraphicsWindow::get_keyboard_map
+//       Access: Published, Virtual
+//  Description: Returns a ButtonMap containing the association
+//               between raw buttons and virtual buttons.
+////////////////////////////////////////////////////////////////////
+ButtonMap *CocoaGraphicsWindow::
+get_keyboard_map() const {
+  TISInputSourceRef input_source;
+  CFDataRef layout_data;
+  const UCKeyboardLayout *layout;
+
+  // Get the current keyboard layout data.
+  input_source = TISCopyCurrentKeyboardInputSource();
+  layout_data = (CFDataRef) TISGetInputSourceProperty(input_source, kTISPropertyUnicodeKeyLayoutData);
+  layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data);
+
+  ButtonMap *map = new ButtonMap;
+
+  UniChar chars[4];
+  UniCharCount num_chars;
+
+  // Iterate through the known scancode range and see what
+  // every scan code is mapped to.
+  for (int k = 0; k <= 0x7E; ++k) {
+    ButtonHandle raw_button = map_raw_key(k);
+    if (raw_button == ButtonHandle::none()) {
+      continue;
+    }
+
+    UInt32 dead_keys = 0;
+    if (UCKeyTranslate(layout, k, kUCKeyActionDisplay, 0, LMGetKbdType(),
+                       kUCKeyTranslateNoDeadKeysMask, &dead_keys, 4,
+                       &num_chars, chars) == noErr) {
+      if (num_chars > 0 && chars[0] != 0x10) {
+        ButtonHandle button = ButtonHandle::none();
+
+        if (chars[0] > 0 && chars[0] <= 0x7f) {
+          button = KeyboardButton::ascii_key(chars[0]);
+        }
+        if (button == ButtonHandle::none()) {
+          button = map_key(chars[0]);
+        }
+        if (button != ButtonHandle::none()) {
+          map->map_button(raw_button, button);
+        }
+      } else {
+        // A special function key or modifier key, which isn't remapped by the OS.
+        map->map_button(raw_button, raw_button);
+      }
+    }
+  }
+
+  CFRelease(input_source);
+  return map;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CocoaGraphicsWindow::map_key
 //       Access: Private
-//  Description: Maps a Cocoa key character to a ButtonHandle.
+//  Description: Maps a unicode key character to a ButtonHandle.
 ////////////////////////////////////////////////////////////////////
 ButtonHandle CocoaGraphicsWindow::
-map_key(unsigned short c) {
+map_key(unsigned short c) const {
   switch (c) {
   case NSEnterCharacter:
     return KeyboardButton::enter();
@@ -1750,17 +1807,21 @@ map_key(unsigned short c) {
     // BackTabCharacter is sent when shift-tab is used.
     return KeyboardButton::tab();
 
-  case 16:
+  case 0x10:
     // No idea where this constant comes from, but it
     // is sent whenever the menu key is pressed.
     return KeyboardButton::menu();
 
+  case 0x1e:
   case NSUpArrowFunctionKey:
     return KeyboardButton::up();
+  case 0x1f:
   case NSDownArrowFunctionKey:
     return KeyboardButton::down();
+  case 0x1c:
   case NSLeftArrowFunctionKey:
     return KeyboardButton::left();
+  case 0x1d:
   case NSRightArrowFunctionKey:
     return KeyboardButton::right();
   case NSF1FunctionKey:
@@ -1819,14 +1880,18 @@ map_key(unsigned short c) {
     return KeyboardButton::insert();
   case NSDeleteFunctionKey:
     return KeyboardButton::del();
+  case 0x01:
   case NSHomeFunctionKey:
     return KeyboardButton::home();
   case NSBeginFunctionKey:
     break;
+  case 0x04:
   case NSEndFunctionKey:
     return KeyboardButton::end();
+  case 0x0b:
   case NSPageUpFunctionKey:
     return KeyboardButton::page_up();
+  case 0x0c:
   case NSPageDownFunctionKey:
     return KeyboardButton::page_down();
   case NSPrintScreenFunctionKey:
@@ -1859,6 +1924,7 @@ map_key(unsigned short c) {
   case NSRedoFunctionKey:
   case NSFindFunctionKey:
     break;
+  case 0x05:
   case NSHelpFunctionKey:
     return KeyboardButton::help();
   case NSModeSwitchFunctionKey:
@@ -1873,7 +1939,7 @@ map_key(unsigned short c) {
 //  Description: Maps a keycode to a ButtonHandle.
 ////////////////////////////////////////////////////////////////////
 ButtonHandle CocoaGraphicsWindow::
-map_raw_key(unsigned short keycode) {
+map_raw_key(unsigned short keycode) const {
   if (keycode > 0x7f) {
     return ButtonHandle::none();
   }

+ 11 - 0
panda/src/display/config_display.cxx

@@ -172,6 +172,17 @@ ConfigVariableInt max_texture_stages
           "this number of texture stages simultaneously, regardless of "
           "what the GSG says it can do."));
 
+ConfigVariableInt max_color_targets
+("max-color-targets", -1,
+ PRC_DESC("Set this to a positive integer to limit the number of "
+          "color targets reported by the GSG.  This can be used to limit "
+          "the amount of render targets Panda will attempt to use.  "
+          "If this is zero or less, the GSG will report its honest number "
+          "of color targets, allowing Panda the full use of the graphics "
+          "card; if it is 1 or more, then Panda will never allow more than "
+          "this number of color targets simultaneously, regardless of "
+          "what the GSG says it can do."));
+
 ConfigVariableBool support_render_texture
 ("support-render-texture", true,
  PRC_DESC("Set this true allow use of the render-to-a-texture feature, if it "

+ 1 - 0
panda/src/display/config_display.h

@@ -52,6 +52,7 @@ extern EXPCL_PANDA_DISPLAY ConfigVariableBool force_parasite_buffer;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool prefer_single_buffer;
 
 extern EXPCL_PANDA_DISPLAY ConfigVariableInt max_texture_stages;
+extern EXPCL_PANDA_DISPLAY ConfigVariableInt max_color_targets;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool support_render_texture;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool support_rescale_normal;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool support_stencil;

+ 1 - 28
panda/src/display/displayRegion.I

@@ -245,7 +245,7 @@ get_texture_reload_priority() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void DisplayRegion::
 set_cube_map_index(int cube_map_index) {
-  set_target_tex_page(cube_map_index, 0);
+  set_target_tex_page(cube_map_index);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -262,20 +262,6 @@ get_target_tex_page() const {
   return cdata->_target_tex_page;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: DisplayRegion::get_target_tex_view
-//       Access: Published
-//  Description: Returns the target view number associated with this
-//               particular DisplayRegion, or -1 if it is not
-//               associated with a view.  See
-//               set_target_tex_page().
-////////////////////////////////////////////////////////////////////
-INLINE int DisplayRegion::
-get_target_tex_view() const {
-  CDReader cdata(_cycler);
-  return cdata->_target_tex_view;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: DisplayRegion::set_cull_callback
 //       Access: Published
@@ -812,19 +798,6 @@ get_target_tex_page() const {
   return _cdata->_target_tex_page;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: DisplayRegionPipelineReader::get_target_tex_view
-//       Access: Published
-//  Description: Returns the target view number associated with this
-//               particular DisplayRegion, or -1 if it is not
-//               associated with a view.  See
-//               set_target_tex_page().
-////////////////////////////////////////////////////////////////////
-INLINE int DisplayRegionPipelineReader::
-get_target_tex_view() const {
-  return _cdata->_target_tex_view;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: DisplayRegionPipelineReader::get_draw_callback
 //       Access: Published

+ 4 - 7
panda/src/display/displayRegion.cxx

@@ -404,12 +404,11 @@ get_cull_traverser() {
 //               and/or stereo textures.
 ////////////////////////////////////////////////////////////////////
 void DisplayRegion::
-set_target_tex_page(int page, int view) {
+set_target_tex_page(int page) {
   int pipeline_stage = Thread::get_current_pipeline_stage();
   nassertv(pipeline_stage == 0);
   CDWriter cdata(_cycler);
   cdata->_target_tex_page = page;
-  cdata->_target_tex_view = view;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -587,7 +586,7 @@ get_screenshot() {
   
   RenderBuffer buffer = gsg->get_render_buffer(get_screenshot_buffer_type(),
                                                _window->get_fb_properties());
-  if (!gsg->framebuffer_copy_to_ram(tex, -1, this, buffer)) {
+  if (!gsg->framebuffer_copy_to_ram(tex, 0, -1, this, buffer)) {
     return NULL;
   }
   
@@ -823,8 +822,7 @@ CData() :
   _sort(0),
   _stereo_channel(Lens::SC_mono),
   _tex_view_offset(0),
-  _target_tex_page(-1),
-  _target_tex_view(-1)
+  _target_tex_page(-1)
 {
 }
 
@@ -849,8 +847,7 @@ CData(const DisplayRegion::CData &copy) :
   _sort(copy._sort),
   _stereo_channel(copy._stereo_channel),
   _tex_view_offset(copy._tex_view_offset),
-  _target_tex_page(copy._target_tex_page),
-  _target_tex_view(copy._target_tex_view)
+  _target_tex_page(copy._target_tex_page)
 {
 }
 

+ 1 - 4
panda/src/display/displayRegion.h

@@ -112,9 +112,8 @@ PUBLISHED:
   CullTraverser *get_cull_traverser();
 
   INLINE void set_cube_map_index(int cube_map_index);
-  virtual void set_target_tex_page(int page, int view);
+  virtual void set_target_tex_page(int page);
   INLINE int get_target_tex_page() const;
-  INLINE int get_target_tex_view() const;
 
   INLINE void set_cull_callback(CallbackObject *object);
   INLINE void clear_cull_callback();
@@ -213,7 +212,6 @@ private:
     Lens::StereoChannel _stereo_channel;
     int _tex_view_offset;
     int _target_tex_page;
-    int _target_tex_view;
 
     PT(CallbackObject) _cull_callback;
     PT(CallbackObject) _draw_callback;
@@ -312,7 +310,6 @@ public:
   INLINE int get_tex_view_offset();
   INLINE bool get_clear_depth_between_eyes() const;
   INLINE int get_target_tex_page() const;
-  INLINE int get_target_tex_view() const;
   INLINE CallbackObject *get_draw_callback() const;
 
   INLINE void get_pixels(int &pl, int &pr, int &pb, int &pt) const;

+ 1 - 2
panda/src/display/graphicsOutput.I

@@ -863,12 +863,11 @@ end_frame_spam(FrameMode mode) {
 //     Function: GraphicsOutput::clear_cube_map_selection
 //       Access: Public
 //  Description: Clear the variables that select a cube-map face (or
-//               other multipage or multiview texture face).
+//               other multipage texture face).
 ////////////////////////////////////////////////////////////////////
 INLINE void GraphicsOutput::
 clear_cube_map_selection() {
   _target_tex_page = -1;
-  _target_tex_view = -1;
   _prev_page_dr = NULL;
 }
 

+ 63 - 35
panda/src/display/graphicsOutput.cxx

@@ -107,7 +107,6 @@ GraphicsOutput(GraphicsEngine *engine, GraphicsPipe *pipe,
   _is_valid = false;
   _flip_ready = false;
   _target_tex_page = -1;
-  _target_tex_view = -1;
   _prev_page_dr = NULL;
   _sort = 0;
   _child_sort = 0;
@@ -373,6 +372,12 @@ add_render_texture(Texture *tex, RenderTextureMode mode,
   // which has system-imposed restrictions on size).
   tex->set_size_padded(get_x_size(), get_y_size(), tex->get_z_size());
 
+  if (_fb_properties.is_stereo() && plane == RTP_color) {
+    if (tex->get_num_views() < 2) {
+      tex->set_num_views(2);
+    }
+  }
+
   if (!support_render_texture || !get_supports_render_texture()) {
     // Binding is not supported or it is disabled, so just fall back
     // to copy instead.
@@ -1069,7 +1074,7 @@ make_cube_map(const string &name, int size, NodePath &camera_rig,
     DisplayRegion *dr;
     dr = buffer->make_display_region();
 
-    dr->set_target_tex_page(i, 0);
+    dr->set_target_tex_page(i);
     dr->copy_clear_settings(*this);
     dr->set_camera(camera_np);
   }
@@ -1342,38 +1347,34 @@ end_frame(FrameMode mode, Thread *current_thread) {
 void GraphicsOutput::
 change_scenes(DisplayRegionPipelineReader *new_dr) {
   int new_target_tex_page = new_dr->get_target_tex_page();
-  int new_target_tex_view = new_dr->get_target_tex_view();
-  if ((new_target_tex_page != -1 && new_target_tex_page != _target_tex_page) ||
-      new_target_tex_view != _target_tex_view) {
+
+  if (new_target_tex_page != -1 && new_target_tex_page != _target_tex_page) {
+
+    if (new_target_tex_page == -1) {
+      new_target_tex_page = 0;
+    }
     int old_target_tex_page = _target_tex_page;
-    int old_target_tex_view = _target_tex_view;
     DisplayRegion *old_page_dr = _prev_page_dr;
     _target_tex_page = new_target_tex_page;
-    _target_tex_view = new_target_tex_view;
     _prev_page_dr = new_dr->get_object();
 
     CDReader cdata(_cycler);
     RenderTextures::const_iterator ri;
     for (ri = cdata->_textures.begin(); ri != cdata->_textures.end(); ++ri) {
       RenderTextureMode rtm_mode = (*ri)._rtm_mode;
+      RenderTexturePlane plane = (*ri)._plane;
       Texture *texture = (*ri)._texture;
       if (rtm_mode != RTM_none) {
         if (rtm_mode == RTM_bind_or_copy || rtm_mode == RTM_bind_layered) {
           // In render-to-texture mode, switch the rendering backend
           // to the new page, so that the subsequent frame will be
           // rendered to the correct page.
-          select_target_tex_page(_target_tex_page, _target_tex_view);
+          select_target_tex_page(_target_tex_page);
 
         } else if (old_target_tex_page != -1) {
           // In copy-to-texture mode, copy the just-rendered framebuffer
           // to the old texture page.
 
-          // TODO: we should probably pass the view parameter into
-          // framebuffer_copy_to_xxx(), as we do the page parameter.
-          // Instead these methods draw the view parameter from
-          // dr->get_target_tex_view(), which is not altogether wrong
-          // but is a strange approach.
-
           nassertv(old_page_dr != (DisplayRegion *)NULL);
           if (display_cat.is_debug()) {
             display_cat.debug()
@@ -1383,12 +1384,31 @@ change_scenes(DisplayRegionPipelineReader *new_dr) {
           }
           RenderBuffer buffer = _gsg->get_render_buffer(get_draw_buffer_type(),
                                                         get_fb_properties());
-          if (rtm_mode == RTM_copy_ram) {
-            _gsg->framebuffer_copy_to_ram(texture, old_target_tex_page,
-                                          old_page_dr, buffer);
+
+          if (plane == RTP_color && _fb_properties.is_stereo()) {
+            // We've got two texture views to copy.
+            RenderBuffer left(_gsg, buffer._buffer_type & ~RenderBuffer::T_right);
+            RenderBuffer right(_gsg, buffer._buffer_type & ~RenderBuffer::T_left);
+
+            if (rtm_mode == RTM_copy_ram) {
+              _gsg->framebuffer_copy_to_ram(texture, 0, old_target_tex_page,
+                                            old_page_dr, left);
+              _gsg->framebuffer_copy_to_ram(texture, 1, old_target_tex_page,
+                                            old_page_dr, right);
+            } else {
+              _gsg->framebuffer_copy_to_texture(texture, 0, old_target_tex_page,
+                                                old_page_dr, left);
+              _gsg->framebuffer_copy_to_texture(texture, 1, old_target_tex_page,
+                                                old_page_dr, right);
+            }
           } else {
-            _gsg->framebuffer_copy_to_texture(texture, old_target_tex_page,
-                                              old_page_dr, buffer);
+            if (rtm_mode == RTM_copy_ram) {
+              _gsg->framebuffer_copy_to_ram(texture, 0, old_target_tex_page,
+                                            old_page_dr, buffer);
+            } else {
+              _gsg->framebuffer_copy_to_texture(texture, 0, old_target_tex_page,
+                                                old_page_dr, buffer);
+            }
           }
         }
       }
@@ -1402,12 +1422,11 @@ change_scenes(DisplayRegionPipelineReader *new_dr) {
 //  Description: Called internally when the window is in
 //               render-to-a-texture mode and we are in the process of
 //               rendering the six faces of a cube map, or any other
-//               multi-page and/or multi-view texture.  This should do
-//               whatever needs to be done to switch the buffer to the
-//               indicated page and view.
+//               multi-page texture.  This should do whatever needs
+//               to be done to switch the buffer to the indicated page.
 ////////////////////////////////////////////////////////////////////
 void GraphicsOutput::
-select_target_tex_page(int, int) {
+select_target_tex_page(int) {
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1588,25 +1607,34 @@ copy_to_textures() {
       }
 
       bool copied = false;
+      DisplayRegion *dr = _overlay_display_region;
       if (_prev_page_dr != (DisplayRegion *)NULL) {
+        dr = _prev_page_dr;
+      }
+
+      if (plane == RTP_color && _fb_properties.is_stereo()) {
+        // We've got two texture views to copy.
+        RenderBuffer left(_gsg, buffer._buffer_type & ~RenderBuffer::T_right);
+        RenderBuffer right(_gsg, buffer._buffer_type & ~RenderBuffer::T_left);
+
         if ((rtm_mode == RTM_copy_ram)||(rtm_mode == RTM_triggered_copy_ram)) {
-          copied =
-            _gsg->framebuffer_copy_to_ram(texture, _target_tex_page,
-                                          _prev_page_dr, buffer);
+          copied = _gsg->framebuffer_copy_to_ram(texture, 0, _target_tex_page,
+                                                 dr, left);
+          copied = _gsg->framebuffer_copy_to_ram(texture, 1, _target_tex_page,
+                                                 dr, right) && copied;
         } else {
-          copied =
-            _gsg->framebuffer_copy_to_texture(texture, _target_tex_page,
-                                              _prev_page_dr, buffer);
+          copied = _gsg->framebuffer_copy_to_texture(texture, 0, _target_tex_page,
+                                                     dr, left);
+          copied = _gsg->framebuffer_copy_to_texture(texture, 1, _target_tex_page,
+                                                     dr, right) && copied;
         }
       } else {
         if ((rtm_mode == RTM_copy_ram)||(rtm_mode == RTM_triggered_copy_ram)) {
-          copied =
-            _gsg->framebuffer_copy_to_ram(texture, _target_tex_page,
-                                          _overlay_display_region, buffer);
+          copied = _gsg->framebuffer_copy_to_ram(texture, 0, _target_tex_page,
+                                                 dr, buffer);
         } else {
-          copied =
-            _gsg->framebuffer_copy_to_texture(texture, _target_tex_page,
-                                              _overlay_display_region, buffer);
+          copied = _gsg->framebuffer_copy_to_texture(texture, 0, _target_tex_page,
+                                                     dr, buffer);
         }
       }
       if (!copied) {

+ 1 - 1
panda/src/display/graphicsOutput.h

@@ -260,7 +260,7 @@ public:
   virtual void end_frame(FrameMode mode, Thread *current_thread);
 
   void change_scenes(DisplayRegionPipelineReader *new_dr);
-  virtual void select_target_tex_page(int page, int view);
+  virtual void select_target_tex_page(int page);
 
   // These methods will be called within the app (main) thread.
   virtual void begin_flip();

+ 26 - 3
panda/src/display/graphicsStateGuardian.I

@@ -702,15 +702,38 @@ get_supports_geometry_instancing() const {
   return _supports_geometry_instancing;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_max_color_targets
+//       Access: Published
+//  Description: Returns the maximum number of simultaneous color
+//               textures that may be attached for render-to-texture,
+//               as supported by this particular GSG.  If you exceed
+//               this number, the lowest-priority render targets will
+//               not be applied.  Use RenderTarget::set_priority() to
+//               adjust the relative importance of the different
+//               render targets.
+//
+//               The value returned may not be meaningful until after
+//               the graphics context has been fully created (e.g. the
+//               window has been opened).
+////////////////////////////////////////////////////////////////////
+INLINE int GraphicsStateGuardian::
+get_max_color_targets() const {
+  if (max_color_targets > 0) {
+    return min(_max_color_targets, (int)max_color_targets);
+  }
+  return _max_color_targets;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::get_maximum_simultaneous_render_targets
 //       Access: Published
-//  Description: Returns the maximum simultaneous render targets 
-//               supported.
+//  Description: Deprecated.  Use get_max_color_targets() instead,
+//               which returns the exact same value.
 ////////////////////////////////////////////////////////////////////
 INLINE int GraphicsStateGuardian::
 get_maximum_simultaneous_render_targets() const {
-  return _maximum_simultaneous_render_targets;
+  return get_max_color_targets();
 }
 
 ////////////////////////////////////////////////////////////////////

+ 7 - 6
panda/src/display/graphicsStateGuardian.cxx

@@ -218,7 +218,8 @@ GraphicsStateGuardian(CoordinateSystem internal_coordinate_system,
   _supports_two_sided_stencil = false;
   _supports_geometry_instancing = false;
 
-  _maximum_simultaneous_render_targets = 1;
+  // Assume a maximum of 1 render target in absence of MRT.
+  _max_color_targets = 1;
 
   _supported_geom_rendering = 0;
 
@@ -1317,14 +1318,14 @@ prepare_display_region(DisplayRegionPipelineReader *dr) {
   case Lens::SC_left:
     _color_write_mask = dr->get_window()->get_left_eye_color_mask();
     if (_current_properties->is_stereo()) {
-      _stereo_buffer_mask = ~(RenderBuffer::T_front_right | RenderBuffer::T_back_right);
+      _stereo_buffer_mask = ~RenderBuffer::T_right;
     }
     break;
 
   case Lens::SC_right:
     _color_write_mask = dr->get_window()->get_right_eye_color_mask();
     if (_current_properties->is_stereo()) {
-      _stereo_buffer_mask = ~(RenderBuffer::T_front_left | RenderBuffer::T_back_left);
+      _stereo_buffer_mask = ~RenderBuffer::T_left;
     }
     break;
 
@@ -2150,7 +2151,7 @@ do_issue_light() {
 //               copy.
 ////////////////////////////////////////////////////////////////////
 bool GraphicsStateGuardian::
-framebuffer_copy_to_texture(Texture *, int, const DisplayRegion *,
+framebuffer_copy_to_texture(Texture *, int, int, const DisplayRegion *,
                             const RenderBuffer &) {
   return false;
 }
@@ -2167,7 +2168,7 @@ framebuffer_copy_to_texture(Texture *, int, const DisplayRegion *,
 //               indicated texture.
 ////////////////////////////////////////////////////////////////////
 bool GraphicsStateGuardian::
-framebuffer_copy_to_ram(Texture *, int, const DisplayRegion *,
+framebuffer_copy_to_ram(Texture *, int, int, const DisplayRegion *,
                         const RenderBuffer &) {
   return false;
 }
@@ -2722,7 +2723,7 @@ make_shadow_buffer(const NodePath &light_np, GraphicsOutputBase *host) {
     for (int i = 0; i < 6; ++i) {
       PT(DisplayRegion) dr = sbuffer->make_mono_display_region(0, 1, 0, 1);
       dr->set_lens_index(i);
-      dr->set_target_tex_page(i, 0);
+      dr->set_target_tex_page(i);
       dr->set_camera(light_np);
       dr->set_clear_depth_active(true);
     }

+ 4 - 3
panda/src/display/graphicsStateGuardian.h

@@ -150,6 +150,7 @@ PUBLISHED:
   INLINE bool get_supports_two_sided_stencil() const;
   INLINE bool get_supports_geometry_instancing() const;
 
+  INLINE int get_max_color_targets() const;
   INLINE int get_maximum_simultaneous_render_targets() const;
 
   INLINE int get_shader_model() const;
@@ -308,9 +309,9 @@ public:
   virtual void do_issue_light();
 
   virtual bool framebuffer_copy_to_texture
-  (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb);
+  (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
   virtual bool framebuffer_copy_to_ram
-  (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb);
+  (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
 
   virtual void bind_light(PointLight *light_obj, const NodePath &light,
                           int light_id);
@@ -495,7 +496,7 @@ protected:
   bool _supports_two_sided_stencil;
   bool _supports_geometry_instancing;
 
-  int _maximum_simultaneous_render_targets;
+  int _max_color_targets;
 
   int  _supported_geom_rendering;
   bool _color_scale_via_lighting;

+ 18 - 5
panda/src/display/graphicsWindow.cxx

@@ -77,8 +77,8 @@ GraphicsWindow::
   // Clean up python event handlers.
 #ifdef HAVE_PYTHON
   PythonWinProcClasses::iterator iter;
-  for (iter = _python_window_proc_classes.begin(); 
-       iter != _python_window_proc_classes.end(); 
+  for (iter = _python_window_proc_classes.begin();
+       iter != _python_window_proc_classes.end();
        ++iter) {
     delete *iter;
   }
@@ -340,6 +340,17 @@ has_keyboard(int device) const {
   return result;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: x11GraphicsWindow::get_keyboard_map
+//       Access: Published, Virtual
+//  Description: Returns a ButtonMap containing the association
+//               between raw buttons and virtual buttons.
+////////////////////////////////////////////////////////////////////
+ButtonMap *GraphicsWindow::
+get_keyboard_map() const {
+  return NULL;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsWindow::enable_pointer_events
 //       Access: Published
@@ -391,8 +402,10 @@ disable_pointer_mode(int device) {
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsWindow::get_pointer
 //       Access: Published
-//  Description: Returns the MouseData associated with the nth input
-//               device's pointer.
+//  Description: Returns the MouseData associated with the nth
+//               input device's pointer.  This is deprecated; use
+//               get_pointer_device().get_pointer() instead, or for
+//               raw mice, use the InputDeviceManager interface.
 ////////////////////////////////////////////////////////////////////
 MouseData GraphicsWindow::
 get_pointer(int device) const {
@@ -409,7 +422,7 @@ get_pointer(int device) const {
 //     Function: GraphicsWindow::move_pointer
 //       Access: Published, Virtual
 //  Description: Forces the pointer to the indicated position within
-//               the window, if possible.  
+//               the window, if possible.
 //
 //               Returns true if successful, false on failure.  This
 //               may fail if the mouse is not currently within the

+ 2 - 1
panda/src/display/graphicsWindow.h

@@ -29,6 +29,7 @@
 #include "modifierButtons.h"
 #include "buttonEvent.h"
 #include "keyboardButton.h"
+#include "buttonMap.h"
 #include "pnotify.h"
 #include "lightMutex.h"
 #include "lightReMutex.h"
@@ -82,7 +83,7 @@ PUBLISHED:
   MAKE_SEQ(get_input_device_names, get_num_input_devices, get_input_device_name);
   bool has_pointer(int device) const;
   bool has_keyboard(int device) const;
-  
+  virtual ButtonMap *get_keyboard_map() const;
 
   void enable_pointer_events(int device);
   void disable_pointer_events(int device);

+ 16 - 8
panda/src/display/stereoDisplayRegion.cxx

@@ -52,9 +52,17 @@ StereoDisplayRegion::
 ////////////////////////////////////////////////////////////////////
 void StereoDisplayRegion::
 set_clear_active(int n, bool clear_active) {
-  //DisplayRegion::set_clear_active(n, clear_active);
-  _left_eye->set_clear_active(n, clear_active);
-  _right_eye->set_clear_active(n, clear_active);
+  // The clear_active flag gets set only on the parent, stereo display
+  // region.
+  DisplayRegion::set_clear_active(n, clear_active);
+
+  // Except for non-color buffers.  These also get set on the
+  // right display region by default, on the assumption that we want
+  // to clear these buffers between drawing the eyes, and that the
+  // right eye is the second of the pair.
+  if (n != RTP_color) {
+    _right_eye->set_clear_active(n, clear_active);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -64,7 +72,7 @@ set_clear_active(int n, bool clear_active) {
 ////////////////////////////////////////////////////////////////////
 void StereoDisplayRegion::
 set_clear_value(int n, const LColor &clear_value) {
-  //DisplayRegion::set_clear_value(n, clear_value);
+  DisplayRegion::set_clear_value(n, clear_value);
   _left_eye->set_clear_value(n, clear_value);
   _right_eye->set_clear_value(n, clear_value);
 }
@@ -290,10 +298,10 @@ set_cull_traverser(CullTraverser *trav) {
 //               right DisplayRegions to the indicated value.
 ////////////////////////////////////////////////////////////////////
 void StereoDisplayRegion::
-set_target_tex_page(int page, int view) {
-  DisplayRegion::set_target_tex_page(page, view);
-  _left_eye->set_target_tex_page(page, view);
-  _right_eye->set_target_tex_page(page, view);
+set_target_tex_page(int page) {
+  DisplayRegion::set_target_tex_page(page);
+  _left_eye->set_target_tex_page(page);
+  _right_eye->set_target_tex_page(page);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/display/stereoDisplayRegion.h

@@ -61,7 +61,7 @@ PUBLISHED:
   virtual void set_incomplete_render(bool incomplete_render);
   virtual void set_texture_reload_priority(int texture_reload_priority);
   virtual void set_cull_traverser(CullTraverser *trav);
-  virtual void set_target_tex_page(int page, int view);
+  virtual void set_target_tex_page(int page);
 
   virtual void output(ostream &out) const;
   virtual PT(PandaNode) make_cull_result_graph();

+ 1 - 1
panda/src/display/subprocessWindow.cxx

@@ -191,7 +191,7 @@ begin_flip() {
                                    _buffer->get_fb_properties());
 
   bool copied = 
-    _gsg->framebuffer_copy_to_ram(_texture, -1,
+    _gsg->framebuffer_copy_to_ram(_texture, 0, -1,
                                   _overlay_display_region, buffer);
 
   if (copied) {

+ 8 - 2
panda/src/downloadertools/multify.cxx

@@ -202,8 +202,8 @@ help() {
 
     "  -C <extract_dir>\n"
 
-    "      With -x, change to the named directory before extracting files;\n"
-    "      that is, extract subfiles into the named directory.\n\n"
+    "      Change to the named directory before working on files;\n"
+    "      that is, extraction/creation/update and replace will be based on this path\n\n"
 
     "  -O\n"
     "      With -x, extract subfiles to standard output instead of to disk.\n\n"
@@ -419,6 +419,12 @@ add_files(const vector_string &params) {
     filenames.push_back(subfile_name);
   }
 
+  // Change current working directory, if requested.
+  if (got_chdir_to && !chdir_to.chdir()) {
+    cout << "Failed to chdir to " << chdir_to << ": " << strerror(errno) << endl;
+    return false;
+  }
+
   bool okflag = do_add_files(multifile, filenames);
 
   bool needs_repack = multifile->needs_repack();

+ 14 - 12
panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx

@@ -1493,8 +1493,8 @@ end_draw_primitives() {
 //               copy.
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian8::
-framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
-                            const RenderBuffer &rb) {
+framebuffer_copy_to_texture(Texture *tex, int view, int z,
+                            const DisplayRegion *dr, const RenderBuffer &rb) {
   set_read_buffer(rb);
 
   int orig_x = tex->get_x_size();
@@ -1504,8 +1504,8 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
   int xo, yo, w, h;
   dr->get_region_pixels_i(xo, yo, w, h);
   tex->set_size_padded(w, h);
-  
-  TextureContext *tc = tex->prepare_now(0, get_prepared_objects(), this);
+
+  TextureContext *tc = tex->prepare_now(view, get_prepared_objects(), this);
   if (tc == (TextureContext *)NULL) {
     return false;
   }
@@ -1520,7 +1520,7 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
   if (tex->get_texture_type() != Texture::TT_2d_texture) {
     // For a specialty texture like a cube map, go the slow route
     // through RAM for now.
-    return do_framebuffer_copy_to_ram(tex, z, dr, rb, true);
+    return do_framebuffer_copy_to_ram(tex, view, z, dr, rb, true);
   }
   nassertr(dtc->get_d3d_2d_texture() != NULL, false);
 
@@ -1592,7 +1592,7 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
   if (FAILED(hr)) {
     dxgsg8_cat.info()
       << "CopyRects failed in copy_texture " << D3DERRORSTRING(hr);
-    okflag = framebuffer_copy_to_ram(tex, z, dr, rb);
+    okflag = framebuffer_copy_to_ram(tex, view, z, dr, rb);
   }
 
   SAFE_RELEASE(render_target);
@@ -1605,7 +1605,7 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
   } else {
     // The copy failed.  Fall back to copying it to RAM and back.
     // Terribly slow, but what are you going to do?
-    return do_framebuffer_copy_to_ram(tex, z, dr, rb, true);
+    return do_framebuffer_copy_to_ram(tex, view, z, dr, rb, true);
   }
 
   return true;
@@ -1623,8 +1623,9 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
 //               indicated texture.
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian8::
-framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb) {
-  return do_framebuffer_copy_to_ram(tex, z, dr, rb, false);
+framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                        const DisplayRegion *dr, const RenderBuffer &rb) {
+  return do_framebuffer_copy_to_ram(tex, view, z, dr, rb, false);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1637,8 +1638,9 @@ framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr, const Rend
 //               copies to texture memory).
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian8::
-do_framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr, 
-                           const RenderBuffer &rb, bool inverted) {
+do_framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                           const DisplayRegion *dr, const RenderBuffer &rb,
+                           bool inverted) {
   set_read_buffer(rb);
 
   RECT rect;
@@ -1779,7 +1781,7 @@ do_framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
     copy_inverted = !copy_inverted;
   }
   DXTextureContext8::d3d_surface_to_texture(rect, temp_surface,
-                                            copy_inverted, tex, z);
+                                            copy_inverted, tex, view, z);
 
   RELEASE(temp_surface, dxgsg8, "temp_surface", RELEASE_ONCE);
 

+ 8 - 4
panda/src/dxgsg8/dxGraphicsStateGuardian8.h

@@ -98,12 +98,16 @@ public:
                            bool force);
   virtual void end_draw_primitives();
 
-  virtual bool framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
+  virtual bool framebuffer_copy_to_texture(Texture *tex, int view, int z,
+                                           const DisplayRegion *dr,
                                            const RenderBuffer &rb);
-  virtual bool framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
+  virtual bool framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                                       const DisplayRegion *dr,
                                        const RenderBuffer &rb);
-  bool do_framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
-                                  const RenderBuffer &rb, bool inverted);
+  bool do_framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                                  const DisplayRegion *dr,
+                                  const RenderBuffer &rb,
+                                  bool inverted);
 
   virtual void reset();
 

+ 5 - 1
panda/src/dxgsg8/dxTextureContext8.cxx

@@ -1062,7 +1062,8 @@ extract_texture_data() {
 ////////////////////////////////////////////////////////////////////
 HRESULT DXTextureContext8::
 d3d_surface_to_texture(RECT &source_rect, IDirect3DSurface8 *d3d_surface,
-           bool inverted, Texture *result, int z) {
+           bool inverted, Texture *result, int view, int z) {
+
   // still need custom conversion since d3d/d3dx has no way to convert
   // arbitrary fmt to ARGB in-memory user buffer
 
@@ -1079,6 +1080,9 @@ d3d_surface_to_texture(RECT &source_rect, IDirect3DSurface8 *d3d_surface,
     nassertr(z < result->get_z_size(), E_FAIL);
     buf += z * result->get_expected_ram_page_size();
   }
+  if (view > 0) {
+    buf += (view * result->get_z_size()) * result->get_expected_ram_page_size();
+  }
 
   if (IsBadWritePtr(d3d_surface, sizeof(DWORD))) {
     dxgsg8_cat.error()

+ 1 - 1
panda/src/dxgsg8/dxTextureContext8.h

@@ -44,7 +44,7 @@ public:
   static HRESULT d3d_surface_to_texture(RECT &source_rect,
           IDirect3DSurface8 *d3d_surface,
           bool inverted, Texture *result,
-          int z);
+          int view, int z);
 
 private:
   HRESULT fill_d3d_texture_mipmap_pixels(int mip_level, int depth_index, D3DFORMAT source_format);

+ 1 - 1
panda/src/dxgsg8/wdxGraphicsBuffer8.cxx

@@ -403,7 +403,7 @@ rebuild_bitplanes() {
 //               the indicated face.
 ////////////////////////////////////////////////////////////////////
 void wdxGraphicsBuffer8::
-select_target_tex_page(int page, int view) {
+select_target_tex_page(int page) {
   _cube_map_index = page;
 
   HRESULT hr;

+ 2 - 2
panda/src/dxgsg8/wdxGraphicsBuffer8.h

@@ -45,8 +45,8 @@ public:
 
   virtual bool begin_frame(FrameMode mode, Thread *current_thread);
   virtual void end_frame(FrameMode mode, Thread *current_thread);
-  
-  virtual void select_target_tex_page(int page, int view);
+
+  virtual void select_target_tex_page(int page);
 
   virtual void process_events();
 

+ 12 - 12
panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx

@@ -1887,8 +1887,8 @@ end_draw_primitives() {
 //               copy.
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian9::
-framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
-                            const RenderBuffer &rb) {
+framebuffer_copy_to_texture(Texture *tex, int view, int z,
+                            const DisplayRegion *dr, const RenderBuffer &rb) {
   set_read_buffer(rb);
 
   int orig_x = tex->get_x_size();
@@ -1902,7 +1902,6 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
   // must use a render target type texture for StretchRect
   tex->set_render_to_texture(true);
 
-  int view = dr->get_target_tex_view();
   TextureContext *tc = tex->prepare_now(view, get_prepared_objects(), this);
   if (tc == (TextureContext *)NULL) {
     return false;
@@ -1918,7 +1917,7 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
   if (tex->get_texture_type() != Texture::TT_2d_texture) {
     // For a specialty texture like a cube map, go the slow route
     // through RAM for now.
-    return do_framebuffer_copy_to_ram(tex, z, dr, rb, true);
+    return do_framebuffer_copy_to_ram(tex, view, z, dr, rb, true);
   }
   nassertr(dtc->get_d3d_2d_texture() != NULL, false);
 
@@ -2020,7 +2019,7 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
   } else {
     // The copy failed.  Fall back to copying it to RAM and back.
     // Terribly slow, but what are you going to do?
-    return do_framebuffer_copy_to_ram(tex, z, dr, rb, true);
+    return do_framebuffer_copy_to_ram(tex, view, z, dr, rb, true);
   }
 
   return true;
@@ -2038,9 +2037,9 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
 //               indicated texture.
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian9::
-framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr, 
-                        const RenderBuffer &rb) {
-  return do_framebuffer_copy_to_ram(tex, z, dr, rb, false);
+framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                        const DisplayRegion *dr, const RenderBuffer &rb) {
+  return do_framebuffer_copy_to_ram(tex, view, z, dr, rb, false);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -2053,8 +2052,9 @@ framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
 //               copies to texture memory).
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian9::
-do_framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr, 
-                           const RenderBuffer &rb, bool inverted) {
+do_framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                           const DisplayRegion *dr, const RenderBuffer &rb,
+                           bool inverted) {
   set_read_buffer(rb);
 
   RECT rect;
@@ -2212,7 +2212,7 @@ do_framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
     copy_inverted = !copy_inverted;
   }
   DXTextureContext9::d3d_surface_to_texture(rect, temp_surface,
-                                            copy_inverted, tex, z);
+                                            copy_inverted, tex, view, z);
 
   RELEASE(temp_surface, dxgsg9, "temp_surface", RELEASE_ONCE);
 
@@ -2445,7 +2445,7 @@ reset() {
   _supports_stencil_wrap = (d3d_caps.StencilCaps & D3DSTENCILCAPS_INCR) && (d3d_caps.StencilCaps & D3DSTENCILCAPS_DECR);
   _supports_two_sided_stencil = ((d3d_caps.StencilCaps & D3DSTENCILCAPS_TWOSIDED) != 0);
 
-  _maximum_simultaneous_render_targets = d3d_caps.NumSimultaneousRTs;
+  _max_color_targets = d3d_caps.NumSimultaneousRTs;
 
   _supports_depth_bias = ((d3d_caps.RasterCaps & (D3DPRASTERCAPS_DEPTHBIAS | D3DPRASTERCAPS_SLOPESCALEDEPTHBIAS)) == (D3DPRASTERCAPS_DEPTHBIAS | D3DPRASTERCAPS_SLOPESCALEDEPTHBIAS));
 

+ 8 - 4
panda/src/dxgsg9/dxGraphicsStateGuardian9.h

@@ -126,12 +126,16 @@ public:
                            bool force);
   virtual void end_draw_primitives();
 
-  virtual bool framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
+  virtual bool framebuffer_copy_to_texture(Texture *tex, int view, int z,
+                                           const DisplayRegion *dr,
                                            const RenderBuffer &rb);
-  virtual bool framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
+  virtual bool framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                                       const DisplayRegion *dr,
                                        const RenderBuffer &rb);
-  bool do_framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
-                                  const RenderBuffer &rb, bool inverted);
+  bool do_framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                                  const DisplayRegion *dr,
+                                  const RenderBuffer &rb,
+                                  bool inverted);
 
   void reset_render_states (void);
   virtual void reset();

+ 5 - 1
panda/src/dxgsg9/dxTextureContext9.cxx

@@ -1303,7 +1303,8 @@ extract_texture_data(DXScreenData &screen) {
 ////////////////////////////////////////////////////////////////////
 HRESULT DXTextureContext9::
 d3d_surface_to_texture(RECT &source_rect, IDirect3DSurface9 *d3d_surface,
-           bool inverted, Texture *result, int z) {
+           bool inverted, Texture *result, int view, int z) {
+
   // still need custom conversion since d3d/d3dx has no way to convert
   // arbitrary fmt to ARGB in-memory user buffer
 
@@ -1320,6 +1321,9 @@ d3d_surface_to_texture(RECT &source_rect, IDirect3DSurface9 *d3d_surface,
     nassertr(z < result->get_z_size(), E_FAIL);
     buf += z * result->get_expected_ram_page_size();
   }
+  if (view > 0) {
+    buf += (view * result->get_z_size()) * result->get_expected_ram_page_size();
+  }
 
   if (IsBadWritePtr(d3d_surface, sizeof(DWORD))) {
     dxgsg9_cat.error()

+ 1 - 1
panda/src/dxgsg9/dxTextureContext9.h

@@ -46,7 +46,7 @@ public:
   static HRESULT d3d_surface_to_texture(RECT &source_rect,
           IDirect3DSurface9 *d3d_surface,
           bool inverted, Texture *result,
-          int z);
+          int view, int z);
 
 private:
   HRESULT fill_d3d_texture_mipmap_pixels(int mip_level, int depth_index, D3DFORMAT source_format);

+ 1 - 1
panda/src/dxgsg9/wdxGraphicsBuffer9.cxx

@@ -591,7 +591,7 @@ rebuild_bitplanes() {
 //               the indicated face.
 ////////////////////////////////////////////////////////////////////
 void wdxGraphicsBuffer9::
-select_target_tex_page(int page, int view) {
+select_target_tex_page(int page) {
 
   DWORD render_target_index;
   

+ 2 - 2
panda/src/dxgsg9/wdxGraphicsBuffer9.h

@@ -46,8 +46,8 @@ public:
   virtual bool begin_frame(FrameMode mode, Thread *current_thread);
   virtual void end_frame(FrameMode mode, Thread *current_thread);
 
-  virtual void select_target_tex_page(int page, int view);
-  
+  virtual void select_target_tex_page(int page);
+
   virtual void process_events();
 
   virtual bool share_depth_buffer(GraphicsOutput *graphics_output);

+ 2 - 2
panda/src/egg/Sources.pp

@@ -24,7 +24,7 @@
      eggCurve.I eggCurve.h eggData.I eggData.h  \
      eggExternalReference.I eggExternalReference.h  \
      eggFilenameNode.I eggFilenameNode.h eggGroup.I eggGroup.h  \
-     eggGroupNode_ext.h \
+     eggGroupNode_ext.cxx eggGroupNode_ext.h \
      eggGroupNode.I eggGroupNode.h eggGroupUniquifier.h  \
      eggLine.I eggLine.h \
      eggMaterial.I eggMaterial.h eggMaterialCollection.I  \
@@ -71,7 +71,7 @@
      eggCoordinateSystem.cxx  \
      eggCurve.cxx eggData.cxx eggExternalReference.cxx  \
      eggFilenameNode.cxx eggGroup.cxx  \
-     eggGroupNode_ext.cxx eggGroupNode.cxx  \
+     eggGroupNode.cxx  \
      eggGroupUniquifier.cxx eggLine.cxx eggMaterial.cxx  \
      eggMaterialCollection.cxx \
      eggMesher.cxx \

+ 2 - 0
panda/src/egg/eggGroupNode_ext.cxx

@@ -16,7 +16,9 @@
 
 #ifdef HAVE_PYTHON
 
+#ifndef CPPPARSER
 IMPORT_THIS struct Dtool_PyTypedObject Dtool_EggNode;
+#endif
 
 ////////////////////////////////////////////////////////////////////
 //     Function: EggGroupNode::get_children

+ 2 - 1
panda/src/ffmpeg/ffmpegAudioCursor.cxx

@@ -56,7 +56,8 @@ FfmpegAudioCursor(FfmpegAudio *src) :
   _resample_ctx(0),
 #endif
   _buffer(0),
-  _buffer_alloc(0)
+  _buffer_alloc(0),
+  _frame(0)
 {
   if (!_ffvfile.open_vfs(_filename)) {
     cleanup();

+ 13 - 10
panda/src/ffmpeg/ffmpegVirtualFile.cxx

@@ -38,7 +38,6 @@ FfmpegVirtualFile() :
   _format_context(NULL),
   _in(NULL),
   _owns_in(false),
-  _buffer(NULL),
   _buffer_size(ffmpeg_read_buffer_size)
 {
 }
@@ -106,8 +105,11 @@ open_vfs(const Filename &filename) {
   _start = 0;
   _size = vfile->get_file_size(_in);
 
-  _buffer = (unsigned char*) av_malloc(_buffer_size);
-  _io_context = avio_alloc_context(_buffer, _buffer_size, 0, (void*) this,
+  // NOTE: The AVIO system owns the buffer after allocation and may realloc it
+  // internally. Therefore, when we're done with the buffer, we use
+  // _io_context->buffer to deallocate it rather than holding on to this pointer.
+  unsigned char *buffer = (unsigned char*) av_malloc(_buffer_size);
+  _io_context = avio_alloc_context(buffer, _buffer_size, 0, (void*) this,
                                    &read_packet, 0, &seek);
 
   _format_context = avformat_alloc_context();
@@ -157,8 +159,11 @@ open_subfile(const SubfileInfo &info) {
 
   _in->seekg(_start);
 
-  _buffer = (unsigned char*) av_malloc(_buffer_size);
-  _io_context = avio_alloc_context(_buffer, _buffer_size, 0, (void*) this,
+  // NOTE: The AVIO system owns the buffer after allocation and may realloc it
+  // internally. Therefore, when we're done with the buffer, we use
+  // _io_context->buffer to deallocate it rather than holding on to this pointer.
+  unsigned char *buffer = (unsigned char*) av_malloc(_buffer_size);
+  _io_context = avio_alloc_context(buffer, _buffer_size, 0, (void*) this,
                                    &read_packet, 0, &seek);
 
   _format_context = avformat_alloc_context();
@@ -197,15 +202,13 @@ close() {
   }
 
   if (_io_context != NULL) {
+    if (_io_context->buffer != NULL) {
+      av_free(_io_context->buffer);
+    }
     av_free(_io_context);
     _io_context = NULL;
   }
 
-  if (_buffer != NULL) {
-    av_free(_buffer);
-    _buffer = NULL;
-  }
-
   if (_owns_in) {
     nassertv(_in != NULL);
     VirtualFileSystem::close_read_file(_in);

+ 0 - 1
panda/src/ffmpeg/ffmpegVirtualFile.h

@@ -67,7 +67,6 @@ private:
   istream *_in;
   pifstream _file_in;
   bool _owns_in;
-  unsigned char *_buffer;
   int _buffer_size;
 };
 

+ 4 - 4
panda/src/framework/windowFramework.cxx

@@ -145,7 +145,8 @@ WindowFramework::
 ////////////////////////////////////////////////////////////////////
 GraphicsOutput *WindowFramework::
 open_window(const WindowProperties &props, int flags, GraphicsEngine *engine,
-            GraphicsPipe *pipe, GraphicsStateGuardian *gsg) {
+            GraphicsPipe *pipe, GraphicsStateGuardian *gsg,
+            const FrameBufferProperties &fbprops) {
   nassertr(_window == (GraphicsOutput *)NULL, _window);
 
   static int next_window_index = 1;
@@ -155,9 +156,8 @@ open_window(const WindowProperties &props, int flags, GraphicsEngine *engine,
   string name = stream.str();
 
   _window = 0;
-  GraphicsOutput *winout = 
-    engine->make_output(pipe, name, 0,
-                        FrameBufferProperties::get_default(),
+  GraphicsOutput *winout =
+    engine->make_output(pipe, name, 0, fbprops,
                         props, flags, gsg, NULL);
   if (winout != (GraphicsOutput *)NULL) {
     _window = winout;

+ 3 - 1
panda/src/framework/windowFramework.h

@@ -63,7 +63,9 @@ public:
 protected:
   GraphicsOutput *open_window(const WindowProperties &props, int flags,
                               GraphicsEngine *engine, GraphicsPipe *pipe,
-                              GraphicsStateGuardian *gsg = NULL);
+                              GraphicsStateGuardian *gsg = NULL,
+                              const FrameBufferProperties &fbprops =
+                                    FrameBufferProperties::get_default());
   void close_window();
 
 public:

+ 56 - 48
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -81,7 +81,6 @@ CLP(GraphicsBuffer)(GraphicsEngine *engine, GraphicsPipe *pipe,
 
   _shared_depth_buffer = 0;
   _bound_tex_page = -1;
-  _bound_tex_view = 0;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -431,6 +430,17 @@ rebuild_bitplanes() {
     int next = GL_COLOR_ATTACHMENT0_EXT;
     if (attach[RTP_color] || _fb_properties.get_color_bits() > 0) {
       bind_slot(layer, rb_resize, attach, RTP_color, next++);
+
+      if (_fb_properties.is_stereo()) {
+        // The texture has already been initialized, so bind it straight away.
+        if (attach[RTP_color] != NULL) {
+          attach_tex(layer, 1, attach[RTP_color], next++);
+        } else {
+          //XXX hack: I needed a slot to use, and we don't currently use RTP_stencil
+          // which is treated as a color attachment below, so this fits the bill.
+          bind_slot(layer, rb_resize, attach, RTP_stencil, next++);
+        }
+      }
       _have_any_color = true;
     }
 
@@ -471,6 +481,9 @@ rebuild_bitplanes() {
     int next = GL_COLOR_ATTACHMENT0_EXT;
     if (attach[RTP_color] || _fb_properties.get_color_bits() > 0) {
       bind_slot_multisample(rb_resize, attach, RTP_color, next++);
+      if (_fb_properties.is_stereo()) {
+        //TODO: figure out how multisample is supposed to work with stereo buffers.
+      }
     }
 
     for (int i=0; i<_fb_properties.get_aux_rgba(); i++) {
@@ -498,7 +511,6 @@ rebuild_bitplanes() {
     _fb_properties.set_alpha_bits(0);
   }
 
-  _bound_tex_view = 0;
   _initial_clear = false;
   report_my_gl_errors();
 
@@ -763,6 +775,7 @@ bind_slot(int layer, bool rb_resize, Texture **attach, RenderTexturePlane slot,
 
     // Allocate and bind the renderbuffer.
     glgsg->_glBindRenderbuffer(GL_RENDERBUFFER_EXT, _rb[slot]);
+
     if (slot == RTP_depth_stencil) {
       GLCAT.debug() << "Creating depth stencil renderbuffer.\n";
       // Allocate renderbuffer storage for depth stencil.
@@ -811,13 +824,16 @@ bind_slot(int layer, bool rb_resize, Texture **attach, RenderTexturePlane slot,
     } else {
       GLCAT.debug() << "Creating color renderbuffer.\n";
       glgsg->_glRenderbufferStorage(GL_RENDERBUFFER_EXT, gl_format, _rb_size_x, _rb_size_y);
-      GLint red_size = 0, green_size = 0, blue_size = 0, alpha_size = 0;
-      glgsg->_glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_RED_SIZE_EXT, &red_size);
-      glgsg->_glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_GREEN_SIZE_EXT, &green_size);
-      glgsg->_glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_BLUE_SIZE_EXT, &blue_size);
-      glgsg->_glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_ALPHA_SIZE_EXT, &alpha_size);
-      _fb_properties.set_color_bits(red_size + green_size + blue_size);
-      _fb_properties.set_alpha_bits(alpha_size);
+
+      if (attachpoint == GL_COLOR_ATTACHMENT0_EXT) {
+        GLint red_size = 0, green_size = 0, blue_size = 0, alpha_size = 0;
+        glgsg->_glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_RED_SIZE_EXT, &red_size);
+        glgsg->_glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_GREEN_SIZE_EXT, &green_size);
+        glgsg->_glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_BLUE_SIZE_EXT, &blue_size);
+        glgsg->_glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_ALPHA_SIZE_EXT, &alpha_size);
+        _fb_properties.set_color_bits(red_size + green_size + blue_size);
+        _fb_properties.set_alpha_bits(alpha_size);
+      }
       glgsg->_glBindRenderbuffer(GL_RENDERBUFFER_EXT, 0);
       glgsg->_glFramebufferRenderbuffer(GL_FRAMEBUFFER_EXT, attachpoint,
                                         GL_RENDERBUFFER_EXT, _rb[slot]);
@@ -962,6 +978,10 @@ attach_tex(int layer, int view, Texture *attach, GLenum attachpoint) {
   CLP(GraphicsStateGuardian) *glgsg;
   DCAST_INTO_V(glgsg, _gsg);
 
+  if (view >= attach->get_num_views()) {
+    attach->set_num_views(view + 1);
+  }
+
   // Create the OpenGL texture object.
   TextureContext *tc = attach->prepare_now(view, glgsg->get_prepared_objects(), glgsg);
   nassertv(tc != (TextureContext *)NULL);
@@ -985,7 +1005,7 @@ attach_tex(int layer, int view, Texture *attach, GLenum attachpoint) {
   GLenum target = glgsg->get_texture_target(attach->get_texture_type());
   if (target == GL_TEXTURE_CUBE_MAP) {
     target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer;
-  } 
+  }
 
   switch (target) {
 #ifndef OPENGLES_1
@@ -1097,19 +1117,17 @@ set_size(int x, int y) {
 //  Description: Called internally when the window is in
 //               render-to-a-texture mode and we are in the process of
 //               rendering the six faces of a cube map, or any other
-//               multi-page and/or multi-view texture.  This should do
-//               whatever needs to be done to switch the buffer to the
-//               indicated page and view.
+//               multi-page texture.  This should do whatever needs
+//               to be done to switch the buffer to the indicated page.
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsBuffer)::
-select_target_tex_page(int page, int view) {
+select_target_tex_page(int page) {
   nassertv(page >= 0 && page < _fbo.size());
 
   CLP(GraphicsStateGuardian) *glgsg;
   DCAST_INTO_V(glgsg, _gsg);
 
   bool switched_page = (_bound_tex_page != page);
-  bool switched_view = (_bound_tex_view != view);
 
   if (switched_page) {
     if (_bound_tex_page != -1) {
@@ -1123,32 +1141,6 @@ select_target_tex_page(int page, int view) {
     _bound_tex_page = page;
   }
 
-  if (switched_view || switched_page) {
-    // We assume that we've already configured the texture earlier
-    // in bind_bitplanes.  Therefore, since we can safely assume that
-    // all texture views have the same format, we can just bind the
-    // new view here.
-
-    Texture *tex = _tex[RTP_color];
-    if (tex != NULL) {
-      if (view >= tex->get_num_views()) {
-        tex->set_num_views(view + 1);
-      }
-
-      if (GLCAT.is_spam()) {
-        GLCAT.spam()
-          << "Binding texture " << *tex
-          << " view " << view << " to color attachment.\n";
-      }
-
-      attach_tex(_bound_tex_page, view, tex, GL_COLOR_ATTACHMENT0_EXT);
-
-      report_my_gl_errors();
-    }
-
-    _bound_tex_view = view;
-  }
-
   report_my_gl_errors();
 }
 
@@ -1167,7 +1159,8 @@ open_buffer() {
   nassertr(_host != 0, false);
 
   // Count total color buffers.
-  int totalcolor = 1 +
+  int totalcolor =
+   (_fb_properties.is_stereo() ? 2 : 1) +
     _fb_properties.get_aux_rgba() +
     _fb_properties.get_aux_hrgba() +
     _fb_properties.get_aux_float();
@@ -1229,8 +1222,10 @@ open_buffer() {
   _fb_properties.set_accum_bits(0);
   _fb_properties.set_multisamples(_host->get_fb_properties().get_multisamples());
 
-  // Update aux settings to reflect the GL_MAX_DRAW_BUFFERS limit.
-  int availcolor = glgsg->_max_draw_buffers;
+  // Update aux settings to reflect the GL_MAX_DRAW_BUFFERS limit,
+  // if we exceed it, that is.
+  int availcolor = glgsg->_max_color_targets;
+
   if (totalcolor > availcolor) {
     int aux_rgba = _fb_properties.get_aux_rgba();
     int aux_hrgba = _fb_properties.get_aux_hrgba();
@@ -1238,6 +1233,13 @@ open_buffer() {
 
     if (_fb_properties.get_color_bits() > 0 && availcolor > 0) {
       --availcolor;
+      if (_fb_properties.is_stereo()) {
+        if (availcolor > 0) {
+          --availcolor;
+        } else {
+          _fb_properties.set_stereo(0);
+        }
+      }
     }
     aux_rgba = min(aux_rgba, availcolor);
     availcolor -= aux_rgba;
@@ -1254,7 +1256,6 @@ open_buffer() {
   _fb_properties.set_back_buffers(0);
   _fb_properties.set_indexed_color(0);
   _fb_properties.set_rgb_color(1);
-  //_fb_properties.set_stereo(0);
   _fb_properties.set_force_hardware(_host->get_fb_properties().get_force_hardware());
   _fb_properties.set_force_software(_host->get_fb_properties().get_force_software());
 
@@ -1537,21 +1538,28 @@ resolve_multisamples() {
 #ifndef OPENGLES
   // Now handle the other color buffers.
   int next = GL_COLOR_ATTACHMENT1_EXT;
-  for (int i=0; i<_fb_properties.get_aux_rgba(); i++) {
+  if (_fb_properties.is_stereo()) {
+    glReadBuffer(next);
+    glDrawBuffer(next);
+    glgsg->_glBlitFramebuffer(0, 0, _rb_size_x, _rb_size_y, 0, 0, _rb_size_x, _rb_size_y,
+                              GL_COLOR_BUFFER_BIT, GL_NEAREST);
+    next += 1;
+  }
+  for (int i = 0; i < _fb_properties.get_aux_rgba(); ++i) {
     glReadBuffer(next);
     glDrawBuffer(next);
     glgsg->_glBlitFramebuffer(0, 0, _rb_size_x, _rb_size_y, 0, 0, _rb_size_x, _rb_size_y,
                               GL_COLOR_BUFFER_BIT, GL_NEAREST);
     next += 1;
   }
-  for (int i=0; i<_fb_properties.get_aux_hrgba(); i++) {
+  for (int i = 0; i < _fb_properties.get_aux_hrgba(); ++i) {
     glReadBuffer(next);
     glDrawBuffer(next);
     glgsg->_glBlitFramebuffer(0, 0, _rb_size_x, _rb_size_y, 0, 0, _rb_size_x, _rb_size_y,
                               GL_COLOR_BUFFER_BIT, GL_NEAREST);
     next += 1;
   }
-  for (int i=0; i<_fb_properties.get_aux_float(); i++) {
+  for (int i = 0; i < _fb_properties.get_aux_float(); ++i) {
     glReadBuffer(next);
     glDrawBuffer(next);
     glgsg->_glBlitFramebuffer(0, 0, _rb_size_x, _rb_size_y, 0, 0, _rb_size_x, _rb_size_y,

+ 2 - 3
panda/src/glstuff/glGraphicsBuffer_src.h

@@ -74,7 +74,7 @@ public:
 
   virtual void set_size(int x, int y);
 
-  virtual void select_target_tex_page(int page, int view);
+  virtual void select_target_tex_page(int page);
 
   virtual bool share_depth_buffer(GraphicsOutput *graphics_output);
   virtual void unshare_depth_buffer();
@@ -94,7 +94,7 @@ protected:
 
 private:
   
-  void bind_slot(int face, bool rb_resize, Texture **attach,
+  void bind_slot(int layer, bool rb_resize, Texture **attach,
                  RenderTexturePlane plane, GLenum attachpoint);
   void bind_slot_multisample(bool rb_resize, Texture **attach,
                  RenderTexturePlane plane, GLenum attachpoint);
@@ -130,7 +130,6 @@ private:
   // The cube map face we are currently drawing to or have just
   // finished drawing to, or -1 if we are not drawing to a cube map.
   int _bound_tex_page;
-  int _bound_tex_view;
 
   bool _initial_clear;
   bool _needs_rebuild;

+ 55 - 45
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -49,20 +49,19 @@
 #include "load_prc_file.h"
 #include "bamCache.h"
 #include "bamCacheRecord.h"
-#include "colorWriteAttrib.h"
-#include "depthWriteAttrib.h"
-#include "shadeModelAttrib.h"
-#include "rescaleNormalAttrib.h"
-#include "clipPlaneAttrib.h"
 #include "alphaTestAttrib.h"
+#include "clipPlaneAttrib.h"
+#include "colorWriteAttrib.h"
 #include "cullFaceAttrib.h"
-#include "fogAttrib.h"
 #include "depthOffsetAttrib.h"
-#include "materialAttrib.h"
-#include "stencilAttrib.h"
+#include "depthWriteAttrib.h"
+#include "fogAttrib.h"
 #include "lightAttrib.h"
+#include "materialAttrib.h"
+#include "rescaleNormalAttrib.h"
 #include "scissorAttrib.h"
-#include "clipPlaneAttrib.h"
+#include "shadeModelAttrib.h"
+#include "stencilAttrib.h"
 #include "graphicsEngine.h"
 #include "shaderGenerator.h"
 
@@ -955,21 +954,23 @@ reset() {
 
       // cgGLGetLatestProfile doesn't seem to return anything other
       // arbvp1/arbfp1 on non-NVIDIA cards, which is severely limiting.
-      if ((_shader_caps._active_vprofile == CG_PROFILE_ARBVP1 ||
-           _shader_caps._active_fprofile == CG_PROFILE_ARBFP1) &&
-          cgGLIsProfileSupported(CG_PROFILE_GLSLV) &&
-          cgGLIsProfileSupported(CG_PROFILE_GLSLF)) {
-
-        // So, if this happens, we set it to GLSL, which is
-        // usually supported on all cards.
-        _shader_caps._active_vprofile = (int)CG_PROFILE_GLSLV;
-        _shader_caps._active_fprofile = (int)CG_PROFILE_GLSLF;
+      // Actually, it seems that these profiles are horribly broken on these
+      // cards.  Let's not do this.
+      //if ((_shader_caps._active_vprofile == CG_PROFILE_ARBVP1 ||
+      //     _shader_caps._active_fprofile == CG_PROFILE_ARBFP1) &&
+      //    cgGLIsProfileSupported(CG_PROFILE_GLSLV) &&
+      //    cgGLIsProfileSupported(CG_PROFILE_GLSLF)) {
+
+      //  // So, if this happens, we set it to GLSL, which is
+      //  // usually supported on all cards.
+      //  _shader_caps._active_vprofile = (int)CG_PROFILE_GLSLV;
+      //  _shader_caps._active_fprofile = (int)CG_PROFILE_GLSLF;
 #if CG_VERSION_NUM >= 2200
-        if (cgGLIsProfileSupported(CG_PROFILE_GLSLG)) {
-          _shader_caps._active_gprofile = (int)CG_PROFILE_GLSLG;
-        }
+      //  if (cgGLIsProfileSupported(CG_PROFILE_GLSLG)) {
+      //    _shader_caps._active_gprofile = (int)CG_PROFILE_GLSLG;
+      //  }
 #endif
-      }
+      //}
     }
     _shader_caps._ultimate_vprofile = (int)CG_PROFILE_VP40;
     _shader_caps._ultimate_fprofile = (int)CG_PROFILE_FP40;
@@ -1254,12 +1255,12 @@ reset() {
     _glDrawBuffers = (PFNGLDRAWBUFFERSPROC)
       get_extension_func(GLPREFIX_QUOTED, "DrawBuffersARB");
   }
-  _max_draw_buffers = 1;
+
+  _max_color_targets = 1;
   if (_glDrawBuffers != 0) {
     GLint max_draw_buffers = 0;
     GLP(GetIntegerv)(GL_MAX_DRAW_BUFFERS, &max_draw_buffers);
-    _max_draw_buffers = max_draw_buffers;
-    _maximum_simultaneous_render_targets = max_draw_buffers;
+    _max_color_targets = max_draw_buffers;
   }
 #endif  // OPENGLES
 
@@ -4200,8 +4201,8 @@ make_geom_munger(const RenderState *state, Thread *current_thread) {
 //               into which to copy.
 ////////////////////////////////////////////////////////////////////
 bool CLP(GraphicsStateGuardian)::
-framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
-                            const RenderBuffer &rb) {
+framebuffer_copy_to_texture(Texture *tex, int view, int z,
+                            const DisplayRegion *dr, const RenderBuffer &rb) {
   nassertr(tex != NULL && dr != NULL, false);
   set_read_buffer(rb._buffer_type);
   if (CLP(color_mask)) {
@@ -4282,7 +4283,6 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
     }
   }
 
-  int view = dr->get_target_tex_view();
   TextureContext *tc = tex->prepare_now(view, get_prepared_objects(), this);
   nassertr(tc != (TextureContext *)NULL, false);
   CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
@@ -4390,8 +4390,8 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
 //               indicated texture.
 ////////////////////////////////////////////////////////////////////
 bool CLP(GraphicsStateGuardian)::
-framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
-                        const RenderBuffer &rb) {
+framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                        const DisplayRegion *dr, const RenderBuffer &rb) {
   nassertr(tex != NULL && dr != NULL, false);
   set_read_buffer(rb._buffer_type);
   GLP(PixelStorei)(GL_PACK_ALIGNMENT, 1);
@@ -4465,11 +4465,6 @@ framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
 
   nassertr(z < tex->get_z_size(), false);
 
-  int view = dr->get_target_tex_view();
-  if (view >= tex->get_num_views()) {
-    tex->set_num_views(view + 1);
-  }
-
   GLenum external_format = get_external_image_format(tex);
 
   if (GLCAT.is_spam()) {
@@ -5853,27 +5848,37 @@ set_draw_buffer(int rbtype) {
   if (_current_fbo) {
 
     GLuint buffers[16];
-    int nbuffers=0;
+    int nbuffers = 0;
     int index = 0;
     if (_current_properties->get_color_bits() > 0) {
-      if (rbtype & RenderBuffer::T_color) {
-        buffers[nbuffers++] = GL_COLOR_ATTACHMENT0_EXT + (index++);
+      if (rbtype & RenderBuffer::T_left) {
+        buffers[nbuffers++] = GL_COLOR_ATTACHMENT0_EXT + index;
+      }
+      ++index;
+      if (_current_properties->is_stereo()) {
+        if (rbtype & RenderBuffer::T_right) {
+          buffers[nbuffers++] = GL_COLOR_ATTACHMENT0_EXT + index;
+        }
+        ++index;
       }
     }
     for (int i=0; i<_current_properties->get_aux_rgba(); i++) {
       if (rbtype & (RenderBuffer::T_aux_rgba_0 << i)) {
-        buffers[nbuffers++] = GL_COLOR_ATTACHMENT0_EXT + (index++);
+        buffers[nbuffers++] = GL_COLOR_ATTACHMENT0_EXT + index;
       }
+      ++index;
     }
     for (int i=0; i<_current_properties->get_aux_hrgba(); i++) {
       if (rbtype & (RenderBuffer::T_aux_hrgba_0 << i)) {
-        buffers[nbuffers++] = GL_COLOR_ATTACHMENT0_EXT + (index++);
+        buffers[nbuffers++] = GL_COLOR_ATTACHMENT0_EXT + index;
       }
+      ++index;
     }
     for (int i=0; i<_current_properties->get_aux_float(); i++) {
       if (rbtype & (RenderBuffer::T_aux_float_0 << i)) {
-        buffers[nbuffers++] = GL_COLOR_ATTACHMENT0_EXT + (index++);
+        buffers[nbuffers++] = GL_COLOR_ATTACHMENT0_EXT + index;
       }
+      ++index;
     }
     _glDrawBuffers(nbuffers, buffers);
 
@@ -5950,26 +5955,31 @@ set_read_buffer(int rbtype) {
   }
 
   if (_current_fbo) {
-
     GLuint buffer = GL_COLOR_ATTACHMENT0_EXT;
     int index = 1;
+    if (_current_properties->is_stereo()) {
+      if (rbtype & RenderBuffer::T_right) {
+        buffer = GL_COLOR_ATTACHMENT1_EXT;
+      }
+      ++index;
+    }
     for (int i=0; i<_current_properties->get_aux_rgba(); i++) {
       if (rbtype & (RenderBuffer::T_aux_rgba_0 << i)) {
         buffer = GL_COLOR_ATTACHMENT0_EXT + index;
       }
-      index += 1;
+      ++index;
     }
     for (int i=0; i<_current_properties->get_aux_hrgba(); i++) {
       if (rbtype & (RenderBuffer::T_aux_hrgba_0 << i)) {
         buffer = GL_COLOR_ATTACHMENT0_EXT + index;
       }
-      index += 1;
+      ++index;
     }
     for (int i=0; i<_current_properties->get_aux_float(); i++) {
       if (rbtype & (RenderBuffer::T_aux_float_0 << i)) {
         buffer = GL_COLOR_ATTACHMENT0_EXT + index;
       }
-      index += 1;
+      ++index;
     }
     GLP(ReadBuffer)(buffer);
 

+ 2 - 3
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -266,9 +266,9 @@ public:
   virtual void clear(DrawableRegion *region);
   
   virtual bool framebuffer_copy_to_texture
-    (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb);
+    (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
   virtual bool framebuffer_copy_to_ram
-    (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb);
+    (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
 
   void apply_fog(Fog *fog);
 
@@ -637,7 +637,6 @@ public:
   INLINE bool get_supports_framebuffer_blit();
   PFNGLBLITFRAMEBUFFEREXTPROC _glBlitFramebuffer;
   PFNGLDRAWBUFFERSPROC _glDrawBuffers;
-  int _max_draw_buffers;
   int _max_fb_samples;
 
   PFNGLGENQUERIESPROC _glGenQueries;

+ 45 - 2
panda/src/grutil/pfmVizzer.I

@@ -17,7 +17,7 @@
 //     Function: PfmVizzer::get_pfm
 //       Access: Published
 //  Description: Returns the reference to the PfmFile manipulated by
-//               thiz PfmVizzer.
+//               this PfmVizzer.
 ////////////////////////////////////////////////////////////////////
 INLINE PfmFile &PfmVizzer::
 get_pfm() {
@@ -28,7 +28,7 @@ get_pfm() {
 //     Function: PfmVizzer::get_pfm
 //       Access: Published
 //  Description: Returns the reference to the PfmFile manipulated by
-//               thiz PfmVizzer.
+//               this PfmVizzer.
 ////////////////////////////////////////////////////////////////////
 INLINE const PfmFile &PfmVizzer::
 get_pfm() const {
@@ -206,6 +206,49 @@ get_vis_blend() const {
   return _vis_blend;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PfmVizzer::set_aux_pfm
+//       Access: Published
+//  Description: Assigns an auxiliary PfmFile to this PfmVizzer.  This
+//               file will be queried by column types
+//               CT_aux_vertex1/2/3, but has no other meaning to the
+//               vizzer.  This size of this PfmFile should exactly
+//               match the base PfmFile.  No reference count is held
+//               and no copy is made; the caller is responsible for
+//               ensuring that the auxiliary PfmFile will persist
+//               throughout the lifetime of the PfmVizzer it is
+//               assigned to.
+////////////////////////////////////////////////////////////////////
+INLINE void PfmVizzer::
+set_aux_pfm(const PfmFile *pfm) {
+  assert(pfm == NULL || (pfm->get_x_size() == _pfm.get_x_size() &&
+                         pfm->get_y_size() == _pfm.get_y_size()));
+  _aux_pfm = pfm;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmVizzer::clear_aux_pfm
+//       Access: Published
+//  Description: Removes the auxiliary PfmFile from this PfmVizzer.
+////////////////////////////////////////////////////////////////////
+INLINE void PfmVizzer::
+clear_aux_pfm() {
+  _aux_pfm = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmVizzer::get_aux_pfm
+//       Access: Published
+//  Description: Returns the reference to the auxiliary PfmFile
+//               queried by this PfmVizzer.  This contains the values
+//               that will be reflected in CT_aux_vertex3 etc.  See
+//               set_aux_pfm().
+////////////////////////////////////////////////////////////////////
+INLINE const PfmFile *PfmVizzer::
+get_aux_pfm() const {
+  return _aux_pfm;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PfmVizzer::VisColumn::Constructor
 //       Access: Public

+ 78 - 1
panda/src/grutil/pfmVizzer.cxx

@@ -39,6 +39,7 @@ PfmVizzer(PfmFile &pfm) : _pfm(pfm) {
   _vis_2d = false;
   _keep_beyond_lens = false;
   _vis_blend = NULL;
+  _aux_pfm = NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -254,6 +255,9 @@ NodePath PfmVizzer::
 generate_vis_points() const {
   nassertr(_pfm.is_valid(), NodePath());
 
+  bool check_aux_pfm = uses_aux_pfm();
+  nassertr(!check_aux_pfm || (_aux_pfm != NULL && _aux_pfm->is_valid()), NodePath());
+
   CPT(GeomVertexFormat) format;
   if (_vis_inverse) {
     if (_vis_2d) {
@@ -292,6 +296,9 @@ generate_vis_points() const {
       if (!_pfm.has_point(xi, yi)) {
         continue;
       }
+      if (check_aux_pfm && !_aux_pfm->has_point(xi, yi)) {
+        continue;
+      }
 
       const LPoint3f &point = _pfm.get_point(xi, yi);
       LPoint2f uv((PN_float32(xi) + 0.5) * uv_scale[0],
@@ -331,6 +338,8 @@ generate_vis_points() const {
 NodePath PfmVizzer::
 generate_vis_mesh(MeshFace face) const {
   nassertr(_pfm.is_valid(), NodePath());
+  bool check_aux_pfm = uses_aux_pfm();
+  nassertr(!check_aux_pfm || (_aux_pfm != NULL && _aux_pfm->is_valid()), NodePath());
   nassertr(face != 0, NodePath());
 
   if (_pfm.get_num_channels() == 1 && _vis_columns.empty()) {
@@ -513,6 +522,31 @@ make_displacement(PNMImage &result, double max_u, double max_v) const {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PfmVizzer::uses_aux_pfm
+//       Access: Private
+//  Description: Returns true if any of the vis_column tokens
+//               reference the aux_pfm file, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool PfmVizzer::
+uses_aux_pfm() const {
+  for (VisColumns::const_iterator vci = _vis_columns.begin();
+       vci != _vis_columns.end();
+       ++vci) {
+    const VisColumn &column = *vci;
+    switch (column._source) {
+    case CT_aux_vertex1:
+    case CT_aux_vertex2:
+    case CT_aux_vertex3:
+      return true;
+    default:
+      break;
+    }
+  }
+
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PfmVizzer::r_fill_displacement
 //       Access: Private
@@ -617,6 +651,7 @@ make_vis_mesh_geom(GeomNode *gnode, bool inverted) const {
   if (vis_columns.empty()) {
     build_auto_vis_columns(vis_columns, true);
   }
+  bool check_aux_pfm = uses_aux_pfm();
 
   CPT(GeomVertexFormat) format = make_array_format(vis_columns);
 
@@ -686,7 +721,13 @@ make_vis_mesh_geom(GeomNode *gnode, bool inverted) const {
               !_pfm.has_point(xi + 1, yi)) {
             continue;
           }
-
+          if (check_aux_pfm && (!_aux_pfm->has_point(xi, yi) ||
+                                !_aux_pfm->has_point(xi, yi + 1) ||
+                                !_aux_pfm->has_point(xi + 1, yi + 1) ||
+                                !_aux_pfm->has_point(xi + 1, yi))) {
+            continue;
+          }
+          
           if (!keep_beyond_lens &&
               (skip_points[(yi - y_begin) * x_size + (xi - x_begin)] ||
                skip_points[(yi - y_begin + 1) * x_size + (xi - x_begin)] ||
@@ -834,18 +875,21 @@ make_array_format(const VisColumns &vis_columns) const {
       break;
 
     case CT_vertex1:
+    case CT_aux_vertex1:
       num_components = 1;
       numeric_type = GeomEnums::NT_float32;
       contents = GeomEnums::C_point;
       break;
 
     case CT_vertex2:
+    case CT_aux_vertex2:
       num_components = 2;
       numeric_type = GeomEnums::NT_float32;
       contents = GeomEnums::C_point;
       break;
 
     case CT_vertex3:
+    case CT_aux_vertex3:
       num_components = 3;
       numeric_type = GeomEnums::NT_float32;
       contents = GeomEnums::C_point;
@@ -918,6 +962,18 @@ add_data(const PfmVizzer &vizzer, GeomVertexWriter &vwriter, int xi, int yi, boo
     }
     break;
 
+  case CT_aux_vertex1:
+    {
+      nassertr(vizzer.get_aux_pfm() != NULL, false);
+      PN_float32 p = vizzer.get_aux_pfm()->get_point1(xi, yi);
+      LPoint2f point(p, 0.0);
+      if (!transform_point(point)) {
+        success = false;
+      }
+      vwriter.set_data2f(point);
+    }
+    break;
+
   case CT_vertex2:
     {
       LPoint2f point = pfm.get_point2(xi, yi);
@@ -928,6 +984,17 @@ add_data(const PfmVizzer &vizzer, GeomVertexWriter &vwriter, int xi, int yi, boo
     }
     break;
 
+  case CT_aux_vertex2:
+    {
+      nassertr(vizzer.get_aux_pfm() != NULL, false);
+      LPoint2f point = vizzer.get_aux_pfm()->get_point2(xi, yi);
+      if (!transform_point(point)) {
+        success = false;
+      }
+      vwriter.set_data2f(point);
+    }
+    break;
+
   case CT_vertex3:
     {
       LPoint3f point = pfm.get_point(xi, yi);
@@ -938,6 +1005,16 @@ add_data(const PfmVizzer &vizzer, GeomVertexWriter &vwriter, int xi, int yi, boo
     }
     break;
 
+  case CT_aux_vertex3:
+    {
+      LPoint3f point = vizzer.get_aux_pfm()->get_point(xi, yi);
+      if (!transform_point(point)) {
+        success = false;
+      }
+      vwriter.set_data3f(point);
+    }
+    break;
+
   case CT_normal3:
     {
       // Calculate the normal based on two neighboring vertices.

+ 9 - 0
panda/src/grutil/pfmVizzer.h

@@ -53,6 +53,10 @@ PUBLISHED:
   INLINE void clear_vis_blend();
   INLINE const PNMImage *get_vis_blend() const;
 
+  INLINE void set_aux_pfm(const PfmFile *pfm);
+  INLINE void clear_aux_pfm();
+  INLINE const PfmFile *get_aux_pfm() const;
+
   enum ColumnType {
     CT_texcoord2,
     CT_texcoord3,
@@ -61,6 +65,9 @@ PUBLISHED:
     CT_vertex3,
     CT_normal3,
     CT_blend1,
+    CT_aux_vertex1,
+    CT_aux_vertex2,
+    CT_aux_vertex3,
   };
   void clear_vis_columns();
   void add_vis_column(ColumnType source, ColumnType target,
@@ -82,6 +89,7 @@ PUBLISHED:
   BLOCKING void make_displacement(PNMImage &result, double max_u, double max_v) const;
 
 private:
+  bool uses_aux_pfm() const;
   void r_fill_displacement(PNMImage &result, int xi, int yi, 
                            double nxi, double nyi, double u_scale, double v_scale,
                            int distance) const;
@@ -117,6 +125,7 @@ private:
 
 private:
   PfmFile &_pfm;
+  const PfmFile *_aux_pfm;
 
   bool _vis_inverse;
   PT(InternalName) _flat_texcoord_name;

+ 3 - 3
panda/src/gsgbase/graphicsStateGuardianBase.h

@@ -208,10 +208,10 @@ public:
   virtual void end_draw_primitives()=0;
 
   virtual bool framebuffer_copy_to_texture
-  (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb)=0;
+  (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb)=0;
   virtual bool framebuffer_copy_to_ram
-  (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb)=0;
-  
+  (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb)=0;
+
   virtual CoordinateSystem get_internal_coordinate_system() const=0;
 
   virtual void bind_light(PointLight *light_obj, const NodePath &light, 

+ 5 - 1
panda/src/pgraph/Sources.pp

@@ -116,7 +116,8 @@
 
   #define COMBINED_SOURCES \
     $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx \
-    $[TARGET]_composite3.cxx $[TARGET]_composite4.cxx
+    $[TARGET]_composite3.cxx $[TARGET]_composite4.cxx \
+    $[TARGET]_ext_composite.cxx
   #define INCLUDED_SOURCES \
     accumulatedAttribs.cxx \
     alphaTestAttrib.cxx \  
@@ -277,6 +278,7 @@
     occluderEffect.I occluderEffect.h \
     occluderNode.I occluderNode.h \
     pandaNode.I pandaNode.h \
+    pandaNode_ext.h pandaNode_ext.cxx \
     pandaNodeChain.I pandaNodeChain.h \
     planeNode.I planeNode.h \
     polylightEffect.I polylightEffect.h \
@@ -289,6 +291,7 @@
     renderEffects.I renderEffects.h \
     renderModeAttrib.I renderModeAttrib.h \
     renderState.I renderState.h \
+    renderState_ext.h renderState_ext.cxx \
     rescaleNormalAttrib.I rescaleNormalAttrib.h \
     sceneGraphReducer.I sceneGraphReducer.h \
     sceneSetup.I sceneSetup.h \
@@ -307,6 +310,7 @@
     texGenAttrib.I texGenAttrib.h \
     textureStageCollection.I textureStageCollection.h \
     transformState.I transformState.h \
+    transformState_ext.h transformState_ext.cxx \
     transparencyAttrib.I transparencyAttrib.h \
     weakNodePath.I weakNodePath.h \
     workingNodePath.I workingNodePath.h

+ 9 - 7
panda/src/pgraph/nodePath_ext.I

@@ -12,6 +12,8 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+#include "pandaNode_ext.h"
+
 #ifndef CPPPARSER
 #ifdef STDFLOAT_DOUBLE
 IMPORT_THIS struct Dtool_PyTypedObject Dtool_LPoint3d;
@@ -33,7 +35,7 @@ IMPORT_THIS struct Dtool_PyTypedObject Dtool_LPoint3f;
 INLINE void Extension<NodePath>::
 get_python_tag_keys(vector_string &keys) const {
   nassertv_always(!_this->is_empty());
-  _this->node()->get_python_tag_keys(keys);
+  invoke_extension(_this->node()).get_python_tag_keys(keys);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -49,7 +51,7 @@ get_tag_keys() const {
     Py_INCREF(Py_None);
     return Py_None;
   }
-  return _this->node()->get_tag_keys();
+  return invoke_extension(_this->node()).get_tag_keys();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -66,7 +68,7 @@ get_python_tag_keys() const {
     Py_INCREF(Py_None);
     return Py_None;
   }
-  return _this->node()->get_python_tag_keys();
+  return invoke_extension(_this->node()).get_python_tag_keys();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -85,7 +87,7 @@ get_python_tag_keys() const {
 INLINE void Extension<NodePath>::
 set_python_tag(const string &key, PyObject *value) {
   nassertv_always(!_this->is_empty());
-  _this->node()->set_python_tag(key, value);
+  invoke_extension(_this->node()).set_python_tag(key, value);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -104,7 +106,7 @@ get_python_tag(const string &key) const {
     Py_INCREF(Py_None);
     return Py_None;
   }
-  return _this->node()->get_python_tag(key);
+  return invoke_extension(_this->node()).get_python_tag(key);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -122,7 +124,7 @@ has_python_tag(const string &key) const {
   if (_this->is_empty()) {
     return false;
   }
-  return _this->node()->has_python_tag(key);
+  return invoke_extension(_this->node()).has_python_tag(key);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -136,7 +138,7 @@ has_python_tag(const string &key) const {
 INLINE void Extension<NodePath>::
 clear_python_tag(const string &key) {
   nassertv_always(!_this->is_empty());
-  _this->node()->clear_python_tag(key);
+  invoke_extension(_this->node()).clear_python_tag(key);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/pgraph/nodePath_ext.cxx

@@ -40,7 +40,7 @@ __copy__() const {
 
   // If we do have a node, duplicate it, and wrap it in a new
   // NodePath.
-  return NodePath(_this->node()->__copy__());
+  return NodePath(invoke_extension(_this->node()).__copy__());
 }
 
 ////////////////////////////////////////////////////////////////////

+ 5 - 0
panda/src/pgraph/p3pgraph_ext_composite.cxx

@@ -0,0 +1,5 @@
+#include "nodePath_ext.cxx"
+#include "nodePathCollection_ext.cxx"
+#include "pandaNode_ext.cxx"
+#include "renderState_ext.cxx"
+#include "transformState_ext.cxx"

+ 0 - 253
panda/src/pgraph/pandaNode.cxx

@@ -632,73 +632,6 @@ copy_subgraph(Thread *current_thread) const {
   return r_copy_subgraph(inst_map, current_thread);
 }
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::__copy__
-//       Access: Published
-//  Description: A special Python method that is invoked by
-//               copy.copy(node).  Unlike the PandaNode copy
-//               constructor, which creates a new node without
-//               children, this shares child pointers (essentially
-//               making every child an instance).  This is intended to
-//               simulate the behavior of copy.copy() for other
-//               objects.
-////////////////////////////////////////////////////////////////////
-PT(PandaNode) PandaNode::
-__copy__() const {
-  Thread *current_thread = Thread::get_current_thread();
-
-  PT(PandaNode) node_dupe = make_copy();
-
-  Children children = get_children(current_thread);
-  int num_children = children.get_num_children();
-
-  for (int i = 0; i < num_children; ++i) {
-    node_dupe->add_child(children.get_child(i), children.get_child_sort(i));
-  }
-
-  return node_dupe;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::__deepcopy__
-//       Access: Published
-//  Description: A special Python method that is invoked by
-//               copy.deepcopy(node).  This calls copy_subgraph()
-//               unless the node is already present in the provided
-//               dictionary.
-////////////////////////////////////////////////////////////////////
-PyObject *PandaNode::
-__deepcopy__(PyObject *self, PyObject *memo) const {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_PandaNode;
-
-  // Borrowed reference.
-  PyObject *dupe = PyDict_GetItem(memo, self);
-  if (dupe != NULL) {
-    // Already in the memo dictionary.
-    Py_INCREF(dupe);
-    return dupe;
-  }
-
-  PT(PandaNode) node_dupe = copy_subgraph();
-
-  // DTool_CreatePyInstanceTyped() steals a C++ reference.
-  node_dupe->ref();
-  dupe = DTool_CreatePyInstanceTyped
-    ((void *)node_dupe.p(), Dtool_PandaNode, true, false, 
-     node_dupe->get_type_index());
-
-  if (PyDict_SetItem(memo, self, dupe) != 0) {
-    Py_DECREF(dupe);
-    return NULL;
-  }
-
-  return dupe;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::count_num_descendants
 //       Access: Published
@@ -1509,118 +1442,6 @@ clear_tag(const string &key, Thread *current_thread) {
   mark_bam_modified();
 }
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::set_python_tag
-//       Access: Published
-//  Description: Associates an arbitrary Python object with a
-//               user-defined key which is stored on the node.  This
-//               is similar to set_tag(), except it can store any
-//               Python object instead of just a string.  However, the
-//               Python object is not recorded to a bam file.
-//
-//               Each unique key stores a different string value.
-//               There is no effective limit on the number of
-//               different keys that may be stored or on the length of
-//               any one key's value.
-////////////////////////////////////////////////////////////////////
-void PandaNode::
-set_python_tag(const string &key, PyObject *value) {
-  Thread *current_thread = Thread::get_current_thread();
-  int pipeline_stage = current_thread->get_pipeline_stage();
-  nassertv(pipeline_stage == 0);
-
-  CDWriter cdata(_cycler);
-  Py_XINCREF(value);
-
-  pair<PythonTagData::iterator, bool> result;
-  result = cdata->_python_tag_data.insert(PythonTagData::value_type(key, value));
-
-  if (!result.second) {
-    // The insert was unsuccessful; that means the key was already
-    // present in the map.  In this case, we should decrement the
-    // original value's reference count and replace it with the new
-    // object.
-    PythonTagData::iterator ti = result.first;
-    PyObject *old_value = (*ti).second;
-    Py_XDECREF(old_value);
-    (*ti).second = value;
-  }
-
-  // Even though the python tag isn't recorded in the bam stream?
-  mark_bam_modified();
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::get_python_tag
-//       Access: Published
-//  Description: Retrieves the Python object that was previously
-//               set on this node for the particular key, if any.  If
-//               no value has been previously set, returns None.
-////////////////////////////////////////////////////////////////////
-PyObject *PandaNode::
-get_python_tag(const string &key) const {
-  CDReader cdata(_cycler);
-  PythonTagData::const_iterator ti;
-  ti = cdata->_python_tag_data.find(key);
-  if (ti != cdata->_python_tag_data.end()) {
-    PyObject *result = (*ti).second;
-    Py_XINCREF(result);
-    return result;
-  }
-  Py_INCREF(Py_None);
-  return Py_None;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::has_python_tag
-//       Access: Published
-//  Description: Returns true if a Python object has been defined on
-//               this node for the particular key (even if that object
-//               is None), or false if no object has been set.
-////////////////////////////////////////////////////////////////////
-bool PandaNode::
-has_python_tag(const string &key) const {
-  CDReader cdata(_cycler);
-  PythonTagData::const_iterator ti;
-  ti = cdata->_python_tag_data.find(key);
-  return (ti != cdata->_python_tag_data.end());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::clear_python_tag
-//       Access: Published
-//  Description: Removes the Python object defined for this key on
-//               this particular node.  After a call to
-//               clear_python_tag(), has_python_tag() will return
-//               false for the indicated key.
-////////////////////////////////////////////////////////////////////
-void PandaNode::
-clear_python_tag(const string &key) {
-  Thread *current_thread = Thread::get_current_thread();
-  int pipeline_stage = current_thread->get_pipeline_stage();
-  nassertv(pipeline_stage == 0);
-
-  CDWriter cdata(_cycler, current_thread);
-  PythonTagData::iterator ti;
-  ti = cdata->_python_tag_data.find(key);
-  if (ti != cdata->_python_tag_data.end()) {
-    PyObject *value = (*ti).second;
-    Py_XDECREF(value);
-    cdata->_python_tag_data.erase(ti);
-  }
-
-  // Even though the python tag isn't recorded in the bam stream?
-  mark_bam_modified();
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::copy_tags
 //       Access: Published
@@ -1743,80 +1564,6 @@ get_tag_keys(vector_string &keys) const {
   }
 }
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: Filename::get_python_tag_keys
-//       Access: Published
-//  Description: Fills the given vector up with the
-//               list of Python tags on this PandaNode.
-//
-//               It is the user's responsibility to ensure that the
-//               keys vector is empty before making this call;
-//               otherwise, the new files will be appended to it.
-////////////////////////////////////////////////////////////////////
-void PandaNode::
-get_python_tag_keys(vector_string &keys) const {
-  CDReader cdata(_cycler);
-  if (!cdata->_python_tag_data.empty()) {
-    PythonTagData::const_iterator ti = cdata->_python_tag_data.begin();
-    while (ti != cdata->_python_tag_data.end()) {
-      keys.push_back((*ti).first);
-      ++ti;
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Filename::get_tag_keys
-//       Access: Published
-//  Description: This variant on get_tag_keys returns
-//               a Python list of strings.
-////////////////////////////////////////////////////////////////////
-PyObject *PandaNode::
-get_tag_keys() const {
-  vector_string keys;
-  get_tag_keys(keys);
-
-  PyObject *result = PyList_New(keys.size());
-  for (size_t i = 0; i < keys.size(); ++i) {
-    const string &tag_name = keys[i];
-#if PY_MAJOR_VERSION >= 3
-    PyObject *str = PyUnicode_FromStringAndSize(tag_name.data(), tag_name.size());
-#else
-    PyObject *str = PyString_FromStringAndSize(tag_name.data(), tag_name.size());
-#endif
-    PyList_SET_ITEM(result, i, str);
-  }
-
-  return result;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Filename::get_python_tag_keys
-//       Access: Published
-//  Description: This variant on get_python_tag_keys returns
-//               a Python list of strings.
-////////////////////////////////////////////////////////////////////
-PyObject *PandaNode::
-get_python_tag_keys() const {
-  vector_string keys;
-  get_python_tag_keys(keys);
-
-  PyObject *result = PyList_New(keys.size());
-  for (size_t i = 0; i < keys.size(); ++i) {
-    const string &tag_name = keys[i];
-#if PY_MAJOR_VERSION >= 3
-    PyObject *str = PyUnicode_FromStringAndSize(tag_name.data(), tag_name.size());
-#else
-    PyObject *str = PyString_FromStringAndSize(tag_name.data(), tag_name.size());
-#endif
-    PyList_SET_ITEM(result, i, str);
-  }
-
-  return result;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::compare_tags
 //       Access: Published

+ 13 - 14
panda/src/pgraph/pandaNode.h

@@ -45,6 +45,7 @@
 #include "copyOnWriteObject.h"
 #include "copyOnWritePointer.h"
 #include "lightReMutex.h"
+#include "extension.h"
 
 #ifdef HAVE_PYTHON
 
@@ -118,10 +119,8 @@ PUBLISHED:
   virtual PandaNode *make_copy() const;
   PT(PandaNode) copy_subgraph(Thread *current_thread = Thread::get_current_thread()) const;
 
-#ifdef HAVE_PYTHON
-  PT(PandaNode) __copy__() const;
-  PyObject *__deepcopy__(PyObject *self, PyObject *memo) const;
-#endif
+  EXTENSION(PT(PandaNode) __copy__() const);
+  EXTENSION(PyObject *__deepcopy__(PyObject *self, PyObject *memo) const);
 
   INLINE int get_num_parents(Thread *current_thread = Thread::get_current_thread()) const;
   INLINE PandaNode *get_parent(int n, Thread *current_thread = Thread::get_current_thread()) const;
@@ -202,16 +201,15 @@ PUBLISHED:
   void clear_tag(const string &key,
                  Thread *current_thread = Thread::get_current_thread());
   void get_tag_keys(vector_string &keys) const;
-#ifdef HAVE_PYTHON
-  PyObject *get_tag_keys() const;
-
-  void set_python_tag(const string &key, PyObject *value);
-  PyObject *get_python_tag(const string &key) const;
-  bool has_python_tag(const string &key) const;
-  void clear_python_tag(const string &key);
-  void get_python_tag_keys(vector_string &keys) const;
-  PyObject *get_python_tag_keys() const;
-#endif  // HAVE_PYTHON
+
+  EXTENSION(PyObject *get_tag_keys() const);
+
+  EXTENSION(void set_python_tag(const string &key, PyObject *value));
+  EXTENSION(PyObject *get_python_tag(const string &key) const);
+  EXTENSION(bool has_python_tag(const string &key) const);
+  EXTENSION(void clear_python_tag(const string &key));
+  EXTENSION(void get_python_tag_keys(vector_string &keys) const);
+  EXTENSION(PyObject *get_python_tag_keys() const);
 
   INLINE bool has_tags() const;
   void copy_tags(PandaNode *other);
@@ -784,6 +782,7 @@ private:
   friend class WorkingNodePath;
   friend class PandaNodePipelineReader;
   friend class EggLoader;
+  friend class Extension<PandaNode>;
 };
 
 ////////////////////////////////////////////////////////////////////

+ 259 - 0
panda/src/pgraph/pandaNode_ext.cxx

@@ -0,0 +1,259 @@
+// Filename: pandaNode_ext.cxx
+// Created by:  CFSworks (30Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pandaNode_ext.h"
+
+#ifdef HAVE_PYTHON
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::__copy__
+//       Access: Published
+//  Description: A special Python method that is invoked by
+//               copy.copy(node).  Unlike the PandaNode copy
+//               constructor, which creates a new node without
+//               children, this shares child pointers (essentially
+//               making every child an instance).  This is intended to
+//               simulate the behavior of copy.copy() for other
+//               objects.
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) Extension<PandaNode>::
+__copy__() const {
+  Thread *current_thread = Thread::get_current_thread();
+
+  PT(PandaNode) node_dupe = _this->make_copy();
+
+  PandaNode::Children children = _this->get_children(current_thread);
+  int num_children = children.get_num_children();
+
+  for (int i = 0; i < num_children; ++i) {
+    node_dupe->add_child(children.get_child(i), children.get_child_sort(i));
+  }
+
+  return node_dupe;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::__deepcopy__
+//       Access: Published
+//  Description: A special Python method that is invoked by
+//               copy.deepcopy(node).  This calls copy_subgraph()
+//               unless the node is already present in the provided
+//               dictionary.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<PandaNode>::
+__deepcopy__(PyObject *self, PyObject *memo) const {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_PandaNode;
+
+  // Borrowed reference.
+  PyObject *dupe = PyDict_GetItem(memo, self);
+  if (dupe != NULL) {
+    // Already in the memo dictionary.
+    Py_INCREF(dupe);
+    return dupe;
+  }
+
+  PT(PandaNode) node_dupe = _this->copy_subgraph();
+
+  // DTool_CreatePyInstanceTyped() steals a C++ reference.
+  node_dupe->ref();
+  dupe = DTool_CreatePyInstanceTyped
+    ((void *)node_dupe.p(), Dtool_PandaNode, true, false, 
+     node_dupe->get_type_index());
+
+  if (PyDict_SetItem(memo, self, dupe) != 0) {
+    Py_DECREF(dupe);
+    return NULL;
+  }
+
+  return dupe;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::set_python_tag
+//       Access: Published
+//  Description: Associates an arbitrary Python object with a
+//               user-defined key which is stored on the node.  This
+//               is similar to set_tag(), except it can store any
+//               Python object instead of just a string.  However, the
+//               Python object is not recorded to a bam file.
+//
+//               Each unique key stores a different string value.
+//               There is no effective limit on the number of
+//               different keys that may be stored or on the length of
+//               any one key's value.
+////////////////////////////////////////////////////////////////////
+void Extension<PandaNode>::
+set_python_tag(const string &key, PyObject *value) {
+  Thread *current_thread = Thread::get_current_thread();
+  int pipeline_stage = current_thread->get_pipeline_stage();
+  nassertv(pipeline_stage == 0);
+
+  PandaNode::CDWriter cdata(_this->_cycler);
+  Py_XINCREF(value);
+
+  pair<PandaNode::PythonTagData::iterator, bool> result;
+  result = cdata->_python_tag_data.insert(PandaNode::PythonTagData::value_type(key, value));
+
+  if (!result.second) {
+    // The insert was unsuccessful; that means the key was already
+    // present in the map.  In this case, we should decrement the
+    // original value's reference count and replace it with the new
+    // object.
+    PandaNode::PythonTagData::iterator ti = result.first;
+    PyObject *old_value = (*ti).second;
+    Py_XDECREF(old_value);
+    (*ti).second = value;
+  }
+
+  // Even though the python tag isn't recorded in the bam stream?
+  _this->mark_bam_modified();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::get_python_tag
+//       Access: Published
+//  Description: Retrieves the Python object that was previously
+//               set on this node for the particular key, if any.  If
+//               no value has been previously set, returns None.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<PandaNode>::
+get_python_tag(const string &key) const {
+  PandaNode::CDReader cdata(_this->_cycler);
+  PandaNode::PythonTagData::const_iterator ti;
+  ti = cdata->_python_tag_data.find(key);
+  if (ti != cdata->_python_tag_data.end()) {
+    PyObject *result = (*ti).second;
+    Py_XINCREF(result);
+    return result;
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::has_python_tag
+//       Access: Published
+//  Description: Returns true if a Python object has been defined on
+//               this node for the particular key (even if that object
+//               is None), or false if no object has been set.
+////////////////////////////////////////////////////////////////////
+bool Extension<PandaNode>::
+has_python_tag(const string &key) const {
+  PandaNode::CDReader cdata(_this->_cycler);
+  PandaNode::PythonTagData::const_iterator ti;
+  ti = cdata->_python_tag_data.find(key);
+  return (ti != cdata->_python_tag_data.end());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::clear_python_tag
+//       Access: Published
+//  Description: Removes the Python object defined for this key on
+//               this particular node.  After a call to
+//               clear_python_tag(), has_python_tag() will return
+//               false for the indicated key.
+////////////////////////////////////////////////////////////////////
+void Extension<PandaNode>::
+clear_python_tag(const string &key) {
+  Thread *current_thread = Thread::get_current_thread();
+  int pipeline_stage = current_thread->get_pipeline_stage();
+  nassertv(pipeline_stage == 0);
+
+  PandaNode::CDWriter cdata(_this->_cycler, current_thread);
+  PandaNode::PythonTagData::iterator ti;
+  ti = cdata->_python_tag_data.find(key);
+  if (ti != cdata->_python_tag_data.end()) {
+    PyObject *value = (*ti).second;
+    Py_XDECREF(value);
+    cdata->_python_tag_data.erase(ti);
+  }
+
+  // Even though the python tag isn't recorded in the bam stream?
+  _this->mark_bam_modified();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::get_python_tag_keys
+//       Access: Published
+//  Description: Fills the given vector up with the
+//               list of Python tags on this PandaNode.
+//
+//               It is the user's responsibility to ensure that the
+//               keys vector is empty before making this call;
+//               otherwise, the new files will be appended to it.
+////////////////////////////////////////////////////////////////////
+void Extension<PandaNode>::
+get_python_tag_keys(vector_string &keys) const {
+  PandaNode::CDReader cdata(_this->_cycler);
+  if (!cdata->_python_tag_data.empty()) {
+    PandaNode::PythonTagData::const_iterator ti = cdata->_python_tag_data.begin();
+    while (ti != cdata->_python_tag_data.end()) {
+      keys.push_back((*ti).first);
+      ++ti;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::get_tag_keys
+//       Access: Published
+//  Description: This variant on get_tag_keys returns
+//               a Python list of strings.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<PandaNode>::
+get_tag_keys() const {
+  vector_string keys;
+  _this->get_tag_keys(keys);
+
+  PyObject *result = PyList_New(keys.size());
+  for (size_t i = 0; i < keys.size(); ++i) {
+    const string &tag_name = keys[i];
+#if PY_MAJOR_VERSION >= 3
+    PyObject *str = PyUnicode_FromStringAndSize(tag_name.data(), tag_name.size());
+#else
+    PyObject *str = PyString_FromStringAndSize(tag_name.data(), tag_name.size());
+#endif
+    PyList_SET_ITEM(result, i, str);
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::get_python_tag_keys
+//       Access: Published
+//  Description: This variant on get_python_tag_keys returns
+//               a Python list of strings.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<PandaNode>::
+get_python_tag_keys() const {
+  vector_string keys;
+  get_python_tag_keys(keys);
+
+  PyObject *result = PyList_New(keys.size());
+  for (size_t i = 0; i < keys.size(); ++i) {
+    const string &tag_name = keys[i];
+#if PY_MAJOR_VERSION >= 3
+    PyObject *str = PyUnicode_FromStringAndSize(tag_name.data(), tag_name.size());
+#else
+    PyObject *str = PyString_FromStringAndSize(tag_name.data(), tag_name.size());
+#endif
+    PyList_SET_ITEM(result, i, str);
+  }
+
+  return result;
+}
+
+#endif  // HAVE_PYTHON
+

+ 50 - 0
panda/src/pgraph/pandaNode_ext.h

@@ -0,0 +1,50 @@
+// Filename: pandaNode_ext.h
+// Created by:  CFSworks (30Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PANDANODE_EXT_H
+#define PANDANODE_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "pandaNode.h"
+#include "py_panda.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : Extension<PandaNode>
+// Description : This class defines the extension methods for
+//               PandaNode, which are called instead of
+//               any C++ methods with the same prototype.
+////////////////////////////////////////////////////////////////////
+template<>
+class Extension<PandaNode> : public ExtensionBase<PandaNode> {
+public:
+  PT(PandaNode) __copy__() const;
+  PyObject *__deepcopy__(PyObject *self, PyObject *memo) const;
+
+  PyObject *get_tag_keys() const;
+
+  void set_python_tag(const string &key, PyObject *value);
+  PyObject *get_python_tag(const string &key) const;
+  bool has_python_tag(const string &key) const;
+  void clear_python_tag(const string &key);
+  void get_python_tag_keys(vector_string &keys) const;
+  PyObject *get_python_tag_keys() const;
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // PANDANODE_EXT_H

+ 0 - 159
panda/src/pgraph/renderState.cxx

@@ -733,126 +733,6 @@ unref() const {
   return false;
 }
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: RenderState::get_composition_cache
-//       Access: Published
-//  Description: Returns a list of 2-tuples that represents the
-//               composition cache.  For each tuple in the list, the
-//               first element is the source render, and the second
-//               is the result render.  If both are None, there is
-//               no entry in the cache at that slot.
-//
-//               In general, a->compose(source) == result.
-//
-//               This has no practical value other than for examining
-//               the cache for performance analysis.
-////////////////////////////////////////////////////////////////////
-PyObject *RenderState::
-get_composition_cache() const {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
-  LightReMutexHolder holder(*_states_lock);
-  size_t cache_size = _composition_cache.get_size();
-  PyObject *list = PyList_New(cache_size);
-
-  for (size_t i = 0; i < cache_size; ++i) {
-    PyObject *tuple = PyTuple_New(2);
-    PyObject *a, *b;
-    if (!_composition_cache.has_element(i)) {
-      a = Py_None;
-      Py_INCREF(a);
-      b = Py_None;
-      Py_INCREF(b);
-    } else {
-      const RenderState *source = _composition_cache.get_key(i);
-      if (source == (RenderState *)NULL) {
-        a = Py_None;
-        Py_INCREF(a);
-      } else {
-        source->ref();
-        a = DTool_CreatePyInstanceTyped((void *)source, Dtool_RenderState, 
-                                        true, true, source->get_type_index());
-      }
-      const RenderState *result = _composition_cache.get_data(i)._result;
-      if (result == (RenderState *)NULL) {
-        b = Py_None;
-        Py_INCREF(b);
-      } else {
-        result->ref();
-        b = DTool_CreatePyInstanceTyped((void *)result, Dtool_RenderState, 
-                                        true, true, result->get_type_index());
-      }
-    }
-    PyTuple_SET_ITEM(tuple, 0, a);
-    PyTuple_SET_ITEM(tuple, 1, b);
-
-    PyList_SET_ITEM(list, i, tuple);
-  }
-
-  return list;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: RenderState::get_invert_composition_cache
-//       Access: Published
-//  Description: Returns a list of 2-tuples that represents the
-//               invert_composition cache.  For each tuple in the list, the
-//               first element is the source render, and the second
-//               is the result render.  If both are None, there is
-//               no entry in the cache at that slot.
-//
-//               In general, a->invert_compose(source) == result.
-//
-//               This has no practical value other than for examining
-//               the cache for performance analysis.
-////////////////////////////////////////////////////////////////////
-PyObject *RenderState::
-get_invert_composition_cache() const {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
-  LightReMutexHolder holder(*_states_lock);
-  size_t cache_size = _invert_composition_cache.get_size();
-  PyObject *list = PyList_New(cache_size);
-
-  for (size_t i = 0; i < cache_size; ++i) {
-    PyObject *tuple = PyTuple_New(2);
-    PyObject *a, *b;
-    if (!_invert_composition_cache.has_element(i)) {
-      a = Py_None;
-      Py_INCREF(a);
-      b = Py_None;
-      Py_INCREF(b);
-    } else {
-      const RenderState *source = _invert_composition_cache.get_key(i);
-      if (source == (RenderState *)NULL) {
-        a = Py_None;
-        Py_INCREF(a);
-      } else {
-        source->ref();
-        a = DTool_CreatePyInstanceTyped((void *)source, Dtool_RenderState, 
-                                        true, true, source->get_type_index());
-      }
-      const RenderState *result = _invert_composition_cache.get_data(i)._result;
-      if (result == (RenderState *)NULL) {
-        b = Py_None;
-        Py_INCREF(b);
-      } else {
-        result->ref();
-        b = DTool_CreatePyInstanceTyped((void *)result, Dtool_RenderState, 
-                                        true, true, result->get_type_index());
-      }
-    }
-    PyTuple_SET_ITEM(tuple, 0, a);
-    PyTuple_SET_ITEM(tuple, 1, b);
-
-    PyList_SET_ITEM(list, i, tuple);
-  }
-
-  return list;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderState::get_auto_shader_state
 //       Access: Published
@@ -2271,45 +2151,6 @@ update_pstats(int old_referenced_bits, int new_referenced_bits) {
 #endif  // DO_PSTATS
 }
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: RenderState::get_states
-//       Access: Published, Static
-//  Description: Returns a list of all of the RenderState objects
-//               in the state cache.  The order of elements in this
-//               cache is arbitrary.
-////////////////////////////////////////////////////////////////////
-PyObject *RenderState::
-get_states() {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
-  if (_states == (States *)NULL) {
-    return PyList_New(0);
-  }
-  LightReMutexHolder holder(*_states_lock);
-
-  size_t num_states = _states->get_num_entries();
-  PyObject *list = PyList_New(num_states);
-  size_t i = 0;
-
-  int size = _states->get_size();
-  for (int si = 0; si < size; ++si) {
-    if (!_states->has_element(si)) {
-      continue;
-    }
-    const RenderState *state = _states->get_key(si);
-    state->ref();
-    PyObject *a = 
-      DTool_CreatePyInstanceTyped((void *)state, Dtool_RenderState, 
-                                  true, true, state->get_type_index());
-    nassertr(i < num_states, list);
-    PyList_SET_ITEM(list, i, a);
-    ++i;
-  }
-  nassertr(i == num_states, list);
-  return list;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderState::init_states
 //       Access: Public, Static

+ 4 - 7
panda/src/pgraph/renderState.h

@@ -126,10 +126,8 @@ PUBLISHED:
   INLINE int get_invert_composition_cache_size() const;
   INLINE const RenderState *get_invert_composition_cache_source(int n) const;
   INLINE const RenderState *get_invert_composition_cache_result(int n) const;
-#ifdef HAVE_PYTHON
-  PyObject *get_composition_cache() const;
-  PyObject *get_invert_composition_cache() const;
-#endif  // HAVE_PYTHON
+  EXTENSION(PyObject *get_composition_cache() const);
+  EXTENSION(PyObject *get_invert_composition_cache() const);
 
   const RenderState *get_auto_shader_state() const;
 
@@ -146,9 +144,7 @@ PUBLISHED:
   static void list_cycles(ostream &out);
   static void list_states(ostream &out);
   static bool validate_states();
-#ifdef HAVE_PYTHON
-  static PyObject *get_states();
-#endif  // HAVE_PYTHON
+  EXTENSION(static PyObject *get_states());
 
 PUBLISHED:
   // These methods are intended for use by low-level code, but they're
@@ -363,6 +359,7 @@ private:
 
   friend class GraphicsStateGuardian;
   friend class RenderAttribRegistry;
+  friend class Extension<RenderState>;
 };
 
 INLINE ostream &operator << (ostream &out, const RenderState &state) {

+ 174 - 0
panda/src/pgraph/renderState_ext.cxx

@@ -0,0 +1,174 @@
+// Filename: renderState_ext.cxx
+// Created by:  CFSworks (31Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "renderState_ext.h"
+
+#ifdef HAVE_PYTHON
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<RenderState>::get_composition_cache
+//       Access: Published
+//  Description: Returns a list of 2-tuples that represents the
+//               composition cache.  For each tuple in the list, the
+//               first element is the source render, and the second
+//               is the result render.  If both are None, there is
+//               no entry in the cache at that slot.
+//
+//               In general, a->compose(source) == result.
+//
+//               This has no practical value other than for examining
+//               the cache for performance analysis.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<RenderState>::
+get_composition_cache() const {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
+  LightReMutexHolder holder(*RenderState::_states_lock);
+  size_t cache_size = _this->_composition_cache.get_size();
+  PyObject *list = PyList_New(cache_size);
+
+  for (size_t i = 0; i < cache_size; ++i) {
+    PyObject *tuple = PyTuple_New(2);
+    PyObject *a, *b;
+    if (!_this->_composition_cache.has_element(i)) {
+      a = Py_None;
+      Py_INCREF(a);
+      b = Py_None;
+      Py_INCREF(b);
+    } else {
+      const RenderState *source = _this->_composition_cache.get_key(i);
+      if (source == (RenderState *)NULL) {
+        a = Py_None;
+        Py_INCREF(a);
+      } else {
+        source->ref();
+        a = DTool_CreatePyInstanceTyped((void *)source, Dtool_RenderState, 
+                                        true, true, source->get_type_index());
+      }
+      const RenderState *result = _this->_composition_cache.get_data(i)._result;
+      if (result == (RenderState *)NULL) {
+        b = Py_None;
+        Py_INCREF(b);
+      } else {
+        result->ref();
+        b = DTool_CreatePyInstanceTyped((void *)result, Dtool_RenderState, 
+                                        true, true, result->get_type_index());
+      }
+    }
+    PyTuple_SET_ITEM(tuple, 0, a);
+    PyTuple_SET_ITEM(tuple, 1, b);
+
+    PyList_SET_ITEM(list, i, tuple);
+  }
+
+  return list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<RenderState>::get_invert_composition_cache
+//       Access: Published
+//  Description: Returns a list of 2-tuples that represents the
+//               invert_composition cache.  For each tuple in the list, the
+//               first element is the source render, and the second
+//               is the result render.  If both are None, there is
+//               no entry in the cache at that slot.
+//
+//               In general, a->invert_compose(source) == result.
+//
+//               This has no practical value other than for examining
+//               the cache for performance analysis.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<RenderState>::
+get_invert_composition_cache() const {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
+  LightReMutexHolder holder(*RenderState::_states_lock);
+  size_t cache_size = _this->_invert_composition_cache.get_size();
+  PyObject *list = PyList_New(cache_size);
+
+  for (size_t i = 0; i < cache_size; ++i) {
+    PyObject *tuple = PyTuple_New(2);
+    PyObject *a, *b;
+    if (!_this->_invert_composition_cache.has_element(i)) {
+      a = Py_None;
+      Py_INCREF(a);
+      b = Py_None;
+      Py_INCREF(b);
+    } else {
+      const RenderState *source = _this->_invert_composition_cache.get_key(i);
+      if (source == (RenderState *)NULL) {
+        a = Py_None;
+        Py_INCREF(a);
+      } else {
+        source->ref();
+        a = DTool_CreatePyInstanceTyped((void *)source, Dtool_RenderState, 
+                                        true, true, source->get_type_index());
+      }
+      const RenderState *result = _this->_invert_composition_cache.get_data(i)._result;
+      if (result == (RenderState *)NULL) {
+        b = Py_None;
+        Py_INCREF(b);
+      } else {
+        result->ref();
+        b = DTool_CreatePyInstanceTyped((void *)result, Dtool_RenderState, 
+                                        true, true, result->get_type_index());
+      }
+    }
+    PyTuple_SET_ITEM(tuple, 0, a);
+    PyTuple_SET_ITEM(tuple, 1, b);
+
+    PyList_SET_ITEM(list, i, tuple);
+  }
+
+  return list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<RenderState>::get_states
+//       Access: Published, Static
+//  Description: Returns a list of all of the RenderState objects
+//               in the state cache.  The order of elements in this
+//               cache is arbitrary.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<RenderState>::
+get_states() {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
+  if (RenderState::_states == (RenderState::States *)NULL) {
+    return PyList_New(0);
+  }
+  LightReMutexHolder holder(*RenderState::_states_lock);
+
+  size_t num_states = RenderState::_states->get_num_entries();
+  PyObject *list = PyList_New(num_states);
+  size_t i = 0;
+
+  int size = RenderState::_states->get_size();
+  for (int si = 0; si < size; ++si) {
+    if (!RenderState::_states->has_element(si)) {
+      continue;
+    }
+    const RenderState *state = RenderState::_states->get_key(si);
+    state->ref();
+    PyObject *a = 
+      DTool_CreatePyInstanceTyped((void *)state, Dtool_RenderState, 
+                                  true, true, state->get_type_index());
+    nassertr(i < num_states, list);
+    PyList_SET_ITEM(list, i, a);
+    ++i;
+  }
+  nassertr(i == num_states, list);
+  return list;
+}
+
+
+
+#endif  // HAVE_PYTHON

+ 42 - 0
panda/src/pgraph/renderState_ext.h

@@ -0,0 +1,42 @@
+// Filename: renderState_ext.h
+// Created by:  CFSworks (31Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef RENDERSTATE_EXT_H
+#define RENDERSTATE_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "renderState.h"
+#include "py_panda.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : Extension<RenderState>
+// Description : This class defines the extension methods for
+//               RenderState, which are called instead of
+//               any C++ methods with the same prototype.
+////////////////////////////////////////////////////////////////////
+template<>
+class Extension<RenderState> : public ExtensionBase<RenderState> {
+public:
+  PyObject *get_composition_cache() const;
+  PyObject *get_invert_composition_cache() const;
+  static PyObject *get_states();
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // RENDERSTATE_EXT_H

+ 0 - 203
panda/src/pgraph/transformState.cxx

@@ -818,134 +818,6 @@ validate_composition_cache() const {
   return true;
 }
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: TransformState::get_composition_cache
-//       Access: Published
-//  Description: Returns a list of 2-tuples that represents the
-//               composition cache.  For each tuple in the list, the
-//               first element is the source transform, and the second
-//               is the result transform.  If both are None, there is
-//               no entry in the cache at that slot.
-//
-//               In general, a->compose(source) == result.
-//
-//               This has no practical value other than for examining
-//               the cache for performance analysis.
-////////////////////////////////////////////////////////////////////
-PyObject *TransformState::
-get_composition_cache() const {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
-  LightReMutexHolder holder(*_states_lock);
-
-  size_t num_states = _composition_cache.get_num_entries();
-  PyObject *list = PyList_New(num_states);
-  size_t i = 0;
-
-  int size = _composition_cache.get_size();
-  for (int si = 0; si < size; ++si) {
-    if (!_composition_cache.has_element(si)) {
-      continue;
-    }
-
-    PyObject *tuple = PyTuple_New(2);
-    PyObject *a, *b;
-
-    const TransformState *source = _composition_cache.get_key(si);
-    if (source == (TransformState *)NULL) {
-      a = Py_None;
-      Py_INCREF(a);
-    } else {
-      source->ref();
-      a = DTool_CreatePyInstanceTyped((void *)source, Dtool_TransformState, 
-                                      true, true, source->get_type_index());
-    }
-    const TransformState *result = _composition_cache.get_data(si)._result;
-    if (result == (TransformState *)NULL) {
-      b = Py_None;
-      Py_INCREF(b);
-    } else {
-      result->ref();
-      b = DTool_CreatePyInstanceTyped((void *)result, Dtool_TransformState, 
-                                      true, true, result->get_type_index());
-    }
-
-    PyTuple_SET_ITEM(tuple, 0, a);
-    PyTuple_SET_ITEM(tuple, 1, b);
-
-    nassertr(i < num_states, list);
-    PyList_SET_ITEM(list, i, tuple);
-    ++i;
-  }
-  nassertr(i == num_states, list);
-  return list;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: TransformState::get_invert_composition_cache
-//       Access: Published
-//  Description: Returns a list of 2-tuples that represents the
-//               invert_composition cache.  For each tuple in the list, the
-//               first element is the source transform, and the second
-//               is the result transform.  If both are None, there is
-//               no entry in the cache at that slot.
-//
-//               In general, a->invert_compose(source) == result.
-//
-//               This has no practical value other than for examining
-//               the cache for performance analysis.
-////////////////////////////////////////////////////////////////////
-PyObject *TransformState::
-get_invert_composition_cache() const {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
-  LightReMutexHolder holder(*_states_lock);
-
-  size_t num_states = _invert_composition_cache.get_num_entries();
-  PyObject *list = PyList_New(num_states);
-  size_t i = 0;
-
-  int size = _invert_composition_cache.get_size();
-  for (int si = 0; si < size; ++si) {
-    if (!_invert_composition_cache.has_element(si)) {
-      continue;
-    }
-
-    PyObject *tuple = PyTuple_New(2);
-    PyObject *a, *b;
-
-    const TransformState *source = _invert_composition_cache.get_key(si);
-    if (source == (TransformState *)NULL) {
-      a = Py_None;
-      Py_INCREF(a);
-    } else {
-      source->ref();
-      a = DTool_CreatePyInstanceTyped((void *)source, Dtool_TransformState, 
-                                      true, true, source->get_type_index());
-    }
-    const TransformState *result = _invert_composition_cache.get_data(si)._result;
-    if (result == (TransformState *)NULL) {
-      b = Py_None;
-      Py_INCREF(b);
-    } else {
-      result->ref();
-      b = DTool_CreatePyInstanceTyped((void *)result, Dtool_TransformState, 
-                                      true, true, result->get_type_index());
-    }
-
-    PyTuple_SET_ITEM(tuple, 0, a);
-    PyTuple_SET_ITEM(tuple, 1, b);
-
-    nassertr(i < num_states, list);
-    PyList_SET_ITEM(list, i, tuple);
-    ++i;
-  }
-  nassertr(i == num_states, list);
-  return list;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::output
 //       Access: Published
@@ -1529,81 +1401,6 @@ validate_states() {
   return true;
 }
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: TransformState::get_states
-//       Access: Published, Static
-//  Description: Returns a list of all of the TransformState objects
-//               in the state cache.  The order of elements in this
-//               cache is arbitrary.
-////////////////////////////////////////////////////////////////////
-PyObject *TransformState::
-get_states() {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
-  if (_states == (States *)NULL) {
-    return PyList_New(0);
-  }
-  LightReMutexHolder holder(*_states_lock);
-
-  size_t num_states = _states->get_num_entries();
-  PyObject *list = PyList_New(num_states);
-  size_t i = 0;
-
-  int size = _states->get_size();
-  for (int si = 0; si < size; ++si) {
-    if (!_states->has_element(si)) {
-      continue;
-    }
-    const TransformState *state = _states->get_key(si);
-    state->ref();
-    PyObject *a = 
-      DTool_CreatePyInstanceTyped((void *)state, Dtool_TransformState, 
-                                  true, true, state->get_type_index());
-    nassertr(i < num_states, list);
-    PyList_SET_ITEM(list, i, a);
-    ++i;
-  }
-  nassertr(i == num_states, list);
-  return list;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: TransformState::get_unused_states
-//       Access: Published, Static
-//  Description: Returns a list of all of the "unused" TransformState
-//               objects in the state cache.  See
-//               get_num_unused_states().
-////////////////////////////////////////////////////////////////////
-PyObject *TransformState::
-get_unused_states() {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
-  if (_states == (States *)NULL) {
-    return PyList_New(0);
-  }
-  LightReMutexHolder holder(*_states_lock);
-
-  PyObject *list = PyList_New(0);
-  int size = _states->get_size();
-  for (int si = 0; si < size; ++si) {
-    if (!_states->has_element(si)) {
-      continue;
-    }
-    const TransformState *state = _states->get_key(si);
-    if (state->get_cache_ref_count() == state->get_ref_count()) {
-      state->ref();
-      PyObject *a = 
-        DTool_CreatePyInstanceTyped((void *)state, Dtool_TransformState, 
-                                    true, true, state->get_type_index());
-      PyList_Append(list, a);
-      Py_DECREF(a);
-    }
-  }
-  return list;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::init_states
 //       Access: Public, Static

+ 7 - 8
panda/src/pgraph/transformState.h

@@ -32,6 +32,7 @@
 #include "deletedChain.h"
 #include "simpleHashMap.h"
 #include "cacheStats.h"
+#include "extension.h"
 
 class GraphicsStateGuardianBase;
 class FactoryParams;
@@ -190,10 +191,8 @@ PUBLISHED:
   INLINE const TransformState *get_invert_composition_cache_source(int n) const;
   INLINE const TransformState *get_invert_composition_cache_result(int n) const;
   bool validate_composition_cache() const;
-#ifdef HAVE_PYTHON
-  PyObject *get_composition_cache() const;
-  PyObject *get_invert_composition_cache() const;
-#endif  // HAVE_PYTHON
+  EXTENSION(PyObject *get_composition_cache() const);
+  EXTENSION(PyObject *get_invert_composition_cache() const);
 
   void output(ostream &out) const;
   void write(ostream &out, int indent_level) const;
@@ -206,10 +205,8 @@ PUBLISHED:
   static void list_cycles(ostream &out);
   static void list_states(ostream &out);
   static bool validate_states();
-#ifdef HAVE_PYTHON
-  static PyObject *get_states();
-  static PyObject *get_unused_states();
-#endif  // HAVE_PYTHON
+  EXTENSION(static PyObject *get_states());
+  EXTENSION(static PyObject *get_unused_states());
 
 
 public:
@@ -405,6 +402,8 @@ public:
 
 private:
   static TypeHandle _type_handle;
+
+  friend class Extension<TransformState>;
 };
 
 INLINE ostream &operator << (ostream &out, const TransformState &state) {

+ 214 - 0
panda/src/pgraph/transformState_ext.cxx

@@ -0,0 +1,214 @@
+// Filename: transformState_ext.cxx
+// Created by:  CFSworks (30Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "transformState_ext.h"
+
+#ifdef HAVE_PYTHON
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<TransformState>::get_composition_cache
+//       Access: Published
+//  Description: Returns a list of 2-tuples that represents the
+//               composition cache.  For each tuple in the list, the
+//               first element is the source transform, and the second
+//               is the result transform.  If both are None, there is
+//               no entry in the cache at that slot.
+//
+//               In general, a->compose(source) == result.
+//
+//               This has no practical value other than for examining
+//               the cache for performance analysis.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<TransformState>::
+get_composition_cache() const {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
+  LightReMutexHolder holder(*_this->_states_lock);
+
+  size_t num_states = _this->_composition_cache.get_num_entries();
+  PyObject *list = PyList_New(num_states);
+  size_t i = 0;
+
+  int size = _this->_composition_cache.get_size();
+  for (int si = 0; si < size; ++si) {
+    if (!_this->_composition_cache.has_element(si)) {
+      continue;
+    }
+
+    PyObject *tuple = PyTuple_New(2);
+    PyObject *a, *b;
+
+    const TransformState *source = _this->_composition_cache.get_key(si);
+    if (source == (TransformState *)NULL) {
+      a = Py_None;
+      Py_INCREF(a);
+    } else {
+      source->ref();
+      a = DTool_CreatePyInstanceTyped((void *)source, Dtool_TransformState, 
+                                      true, true, source->get_type_index());
+    }
+    const TransformState *result = _this->_composition_cache.get_data(si)._result;
+    if (result == (TransformState *)NULL) {
+      b = Py_None;
+      Py_INCREF(b);
+    } else {
+      result->ref();
+      b = DTool_CreatePyInstanceTyped((void *)result, Dtool_TransformState, 
+                                      true, true, result->get_type_index());
+    }
+
+    PyTuple_SET_ITEM(tuple, 0, a);
+    PyTuple_SET_ITEM(tuple, 1, b);
+
+    nassertr(i < num_states, list);
+    PyList_SET_ITEM(list, i, tuple);
+    ++i;
+  }
+  nassertr(i == num_states, list);
+  return list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<TransformState>::get_invert_composition_cache
+//       Access: Published
+//  Description: Returns a list of 2-tuples that represents the
+//               invert_composition cache.  For each tuple in the list, the
+//               first element is the source transform, and the second
+//               is the result transform.  If both are None, there is
+//               no entry in the cache at that slot.
+//
+//               In general, a->invert_compose(source) == result.
+//
+//               This has no practical value other than for examining
+//               the cache for performance analysis.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<TransformState>::
+get_invert_composition_cache() const {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
+  LightReMutexHolder holder(*_this->_states_lock);
+
+  size_t num_states = _this->_invert_composition_cache.get_num_entries();
+  PyObject *list = PyList_New(num_states);
+  size_t i = 0;
+
+  int size = _this->_invert_composition_cache.get_size();
+  for (int si = 0; si < size; ++si) {
+    if (!_this->_invert_composition_cache.has_element(si)) {
+      continue;
+    }
+
+    PyObject *tuple = PyTuple_New(2);
+    PyObject *a, *b;
+
+    const TransformState *source = _this->_invert_composition_cache.get_key(si);
+    if (source == (TransformState *)NULL) {
+      a = Py_None;
+      Py_INCREF(a);
+    } else {
+      source->ref();
+      a = DTool_CreatePyInstanceTyped((void *)source, Dtool_TransformState, 
+                                      true, true, source->get_type_index());
+    }
+    const TransformState *result = _this->_invert_composition_cache.get_data(si)._result;
+    if (result == (TransformState *)NULL) {
+      b = Py_None;
+      Py_INCREF(b);
+    } else {
+      result->ref();
+      b = DTool_CreatePyInstanceTyped((void *)result, Dtool_TransformState, 
+                                      true, true, result->get_type_index());
+    }
+
+    PyTuple_SET_ITEM(tuple, 0, a);
+    PyTuple_SET_ITEM(tuple, 1, b);
+
+    nassertr(i < num_states, list);
+    PyList_SET_ITEM(list, i, tuple);
+    ++i;
+  }
+  nassertr(i == num_states, list);
+  return list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<TransformState>::get_states
+//       Access: Published, Static
+//  Description: Returns a list of all of the TransformState objects
+//               in the state cache.  The order of elements in this
+//               cache is arbitrary.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<TransformState>::
+get_states() {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
+  if (TransformState::_states == (TransformState::States *)NULL) {
+    return PyList_New(0);
+  }
+  LightReMutexHolder holder(*TransformState::_states_lock);
+
+  size_t num_states = TransformState::_states->get_num_entries();
+  PyObject *list = PyList_New(num_states);
+  size_t i = 0;
+
+  int size = TransformState::_states->get_size();
+  for (int si = 0; si < size; ++si) {
+    if (!TransformState::_states->has_element(si)) {
+      continue;
+    }
+    const TransformState *state = TransformState::_states->get_key(si);
+    state->ref();
+    PyObject *a = 
+      DTool_CreatePyInstanceTyped((void *)state, Dtool_TransformState, 
+                                  true, true, state->get_type_index());
+    nassertr(i < num_states, list);
+    PyList_SET_ITEM(list, i, a);
+    ++i;
+  }
+  nassertr(i == num_states, list);
+  return list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<TransformState>::get_unused_states
+//       Access: Published, Static
+//  Description: Returns a list of all of the "unused" TransformState
+//               objects in the state cache.  See
+//               get_num_unused_states().
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<TransformState>::
+get_unused_states() {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
+  if (TransformState::_states == (TransformState::States *)NULL) {
+    return PyList_New(0);
+  }
+  LightReMutexHolder holder(*TransformState::_states_lock);
+
+  PyObject *list = PyList_New(0);
+  int size = TransformState::_states->get_size();
+  for (int si = 0; si < size; ++si) {
+    if (!TransformState::_states->has_element(si)) {
+      continue;
+    }
+    const TransformState *state = TransformState::_states->get_key(si);
+    if (state->get_cache_ref_count() == state->get_ref_count()) {
+      state->ref();
+      PyObject *a = 
+        DTool_CreatePyInstanceTyped((void *)state, Dtool_TransformState, 
+                                    true, true, state->get_type_index());
+      PyList_Append(list, a);
+      Py_DECREF(a);
+    }
+  }
+  return list;
+}
+
+#endif  // HAVE_PYTHON

+ 43 - 0
panda/src/pgraph/transformState_ext.h

@@ -0,0 +1,43 @@
+// Filename: transformState_ext.h
+// Created by:  CFSworks (31Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef TRANSFORMSTATE_EXT_H
+#define TRANSFORMSTATE_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "transformState.h"
+#include "py_panda.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : Extension<TransformState>
+// Description : This class defines the extension methods for
+//               TransformState, which are called instead of
+//               any C++ methods with the same prototype.
+////////////////////////////////////////////////////////////////////
+template<>
+class Extension<TransformState> : public ExtensionBase<TransformState> {
+public:
+  PyObject *get_composition_cache() const;
+  PyObject *get_invert_composition_cache() const;
+  static PyObject *get_states();
+  static PyObject *get_unused_states();
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // TRANSFORMSTATE_EXT_H

+ 25 - 0
panda/src/pnmimage/pfmFile.I

@@ -478,6 +478,7 @@ INLINE void PfmFile::
 set_no_data_chan4(bool chan4) {
   if (chan4 && _num_channels == 4) {
     _has_no_data_value = true;
+    _has_no_data_threshold = false;
     _no_data_value.set(0.0, 0.0, 0.0, -1.0);
     _has_point = has_point_chan4;
   } else {
@@ -496,6 +497,18 @@ set_no_data_value(const LPoint4d &no_data_value) {
   set_no_data_value(LCAST(PN_float32, no_data_value));
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::set_no_data_threshold
+//       Access: Published
+//  Description: Sets the special threshold value.  Points that are
+//               below this value in all components are considered "no
+//               value".
+////////////////////////////////////////////////////////////////////
+INLINE void PfmFile::
+set_no_data_threshold(const LPoint4d &no_data_threshold) {
+  set_no_data_threshold(LCAST(PN_float32, no_data_threshold));
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PfmFile::set_no_data_value
 //       Access: Published
@@ -506,6 +519,7 @@ set_no_data_value(const LPoint4d &no_data_value) {
 INLINE void PfmFile::
 clear_no_data_value() {
   _has_no_data_value = false;
+  _has_no_data_threshold = false;
   _no_data_value = LPoint4f::zero();
   _has_point = has_point_noop;
 }
@@ -521,6 +535,17 @@ has_no_data_value() const {
   return _has_no_data_value;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::has_no_data_threshold
+//       Access: Published
+//  Description: Returns whether a "no data" threshold value has been
+//               established by set_no_data_threshold().
+////////////////////////////////////////////////////////////////////
+INLINE bool PfmFile::
+has_no_data_threshold() const {
+  return _has_no_data_threshold;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PfmFile::get_no_data_value
 //       Access: Published

+ 107 - 0
panda/src/pnmimage/pfmFile.cxx

@@ -33,6 +33,7 @@
 PfmFile::
 PfmFile() {
   _has_no_data_value = false;
+  _has_no_data_threshold = false;
   _no_data_value = LPoint4f::zero();
   _has_point = has_point_noop;
   clear();
@@ -49,6 +50,7 @@ PfmFile(const PfmFile &copy) :
   _table(copy._table),
   _scale(copy._scale),
   _has_no_data_value(copy._has_no_data_value),
+  _has_no_data_threshold(copy._has_no_data_threshold),
   _no_data_value(copy._no_data_value),
   _has_point(copy._has_point)
 {
@@ -65,6 +67,7 @@ operator = (const PfmFile &copy) {
   _table = copy._table;
   _scale = copy._scale;
   _has_no_data_value = copy._has_no_data_value;
+  _has_no_data_threshold = copy._has_no_data_threshold;
   _no_data_value = copy._no_data_value;
   _has_point = copy._has_point;
 }
@@ -908,6 +911,7 @@ set_no_data_nan(int num_channels) {
   if (num_channels > 0) {
     num_channels = min(num_channels, _num_channels);
     _has_no_data_value = true;
+    _has_no_data_threshold = false;
     _no_data_value = LPoint4f::zero();
     PN_float32 nan = make_nan((PN_float32)0.0);
     for (int i = 0; i < num_channels; ++i) {
@@ -945,6 +949,7 @@ set_no_data_value(const LPoint4f &no_data_value) {
   nassertv(is_valid());
 
   _has_no_data_value = true;
+  _has_no_data_threshold = false;
   _no_data_value = no_data_value;
   switch (_num_channels) {
   case 1:
@@ -964,6 +969,38 @@ set_no_data_value(const LPoint4f &no_data_value) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::set_no_data_threshold
+//       Access: Published
+//  Description: Sets the special threshold value.  Points that are
+//               below this value in all components are considered "no
+//               value".
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+set_no_data_threshold(const LPoint4f &no_data_value) {
+  nassertv(is_valid());
+
+  _has_no_data_value = true;
+  _has_no_data_threshold = true;
+  _no_data_value = no_data_value;
+  switch (_num_channels) {
+  case 1:
+    _has_point = has_point_threshold_1;
+    break;
+  case 2:
+    _has_point = has_point_threshold_2;
+    break;
+  case 3:
+    _has_point = has_point_threshold_3;
+    break;
+  case 4:
+    _has_point = has_point_threshold_4;
+    break;
+  default:
+    nassertv(false);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PfmFile::resize
 //       Access: Published
@@ -2395,6 +2432,76 @@ has_point_4(const PfmFile *self, int x, int y) {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::has_point_threshold_1
+//       Access: Private, Static
+//  Description: The implementation of has_point_threshold() for 1-component
+//               files with a no_data_value.
+////////////////////////////////////////////////////////////////////
+bool PfmFile::
+has_point_threshold_1(const PfmFile *self, int x, int y) {
+  if ((x >= 0 && x < self->_x_size) && 
+      (y >= 0 && y < self->_y_size)) {
+    const float *table = &self->_table[(y * self->_x_size + x)];
+    return table[0] >= self->_no_data_value[0];
+  }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::has_point_threshold_2
+//       Access: Private, Static
+//  Description: The implementation of has_point_threshold() for 2-component
+//               files with a no_data_value.
+////////////////////////////////////////////////////////////////////
+bool PfmFile::
+has_point_threshold_2(const PfmFile *self, int x, int y) {
+  if ((x >= 0 && x < self->_x_size) && 
+      (y >= 0 && y < self->_y_size)) {
+    const float *table = &self->_table[(y * self->_x_size + x) * 2];
+    return (table[0] >= self->_no_data_value[0] ||
+            table[1] >= self->_no_data_value[1]);
+  }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::has_point_threshold_3
+//       Access: Private, Static
+//  Description: The implementation of has_point_threshold() for 3-component
+//               files with a no_data_value.
+////////////////////////////////////////////////////////////////////
+bool PfmFile::
+has_point_threshold_3(const PfmFile *self, int x, int y) {
+  if ((x >= 0 && x < self->_x_size) && 
+      (y >= 0 && y < self->_y_size)) {
+    const float *table = &self->_table[(y * self->_x_size + x) * 3];
+    return (table[0] >= self->_no_data_value[0] ||
+            table[1] >= self->_no_data_value[1] ||
+            table[2] >= self->_no_data_value[2]);
+  }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::has_point_threshold_4
+//       Access: Private, Static
+//  Description: The implementation of has_point_threshold() for 4-component
+//               files with a no_data_value.
+////////////////////////////////////////////////////////////////////
+bool PfmFile::
+has_point_threshold_4(const PfmFile *self, int x, int y) {
+  if ((x >= 0 && x < self->_x_size) && 
+      (y >= 0 && y < self->_y_size)) {
+    const float *table = &self->_table[(y * self->_x_size + x) * 4];
+    return (table[0] >= self->_no_data_value[0] ||
+            table[1] >= self->_no_data_value[1] ||
+            table[2] >= self->_no_data_value[2] ||
+            table[3] >= self->_no_data_value[3]);
+  }
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PfmFile::has_point_chan4
 //       Access: Private, Static

+ 8 - 0
panda/src/pnmimage/pfmFile.h

@@ -104,8 +104,11 @@ PUBLISHED:
   void set_no_data_nan(int num_channels);
   void set_no_data_value(const LPoint4f &no_data_value);
   INLINE void set_no_data_value(const LPoint4d &no_data_value);
+  void set_no_data_threshold(const LPoint4f &no_data_value);
+  INLINE void set_no_data_threshold(const LPoint4d &no_data_value);
   INLINE void clear_no_data_value();
   INLINE bool has_no_data_value() const;
+  INLINE bool has_no_data_threshold() const;
   INLINE const LPoint4f &get_no_data_value() const;
 
   BLOCKING void resize(int new_x_size, int new_y_size);
@@ -195,6 +198,10 @@ private:
   static bool has_point_2(const PfmFile *file, int x, int y);
   static bool has_point_3(const PfmFile *file, int x, int y);
   static bool has_point_4(const PfmFile *file, int x, int y);
+  static bool has_point_threshold_1(const PfmFile *file, int x, int y);
+  static bool has_point_threshold_2(const PfmFile *file, int x, int y);
+  static bool has_point_threshold_3(const PfmFile *file, int x, int y);
+  static bool has_point_threshold_4(const PfmFile *file, int x, int y);
   static bool has_point_chan4(const PfmFile *file, int x, int y);
   static bool has_point_nan_1(const PfmFile *file, int x, int y);
   static bool has_point_nan_2(const PfmFile *file, int x, int y);
@@ -208,6 +215,7 @@ private:
   PN_float32 _scale;
 
   bool _has_no_data_value;
+  bool _has_no_data_threshold;
   LPoint4f _no_data_value;
 
   typedef bool HasPointFunc(const PfmFile *file, int x, int y);

+ 7 - 5
panda/src/putil/Sources.pp

@@ -21,8 +21,9 @@
     bamWriter.I bamWriter.h \
     bitArray.I bitArray.h \
     bitMask.I bitMask.h \
-    buttonHandle.I \
-    buttonHandle.h buttonRegistry.I buttonRegistry.h \
+    buttonHandle.I buttonHandle.h \
+    buttonMap.I buttonMap.h \
+    buttonRegistry.I buttonRegistry.h \
     cachedTypedWritableReferenceCount.h cachedTypedWritableReferenceCount.I \
     callbackData.h callbackData.I \
     callbackObject.h callbackObject.I \
@@ -84,7 +85,7 @@
     bamWriter.cxx \
     bitArray.cxx \
     bitMask.cxx \
-    buttonHandle.cxx buttonRegistry.cxx \
+    buttonHandle.cxx buttonMap.cxx buttonRegistry.cxx \
     cachedTypedWritableReferenceCount.cxx \
     callbackData.cxx \
     callbackObject.cxx \
@@ -132,8 +133,9 @@
     bamWriter.I bamWriter.h \
     bitArray.I bitArray.h \
     bitMask.I bitMask.h \
-    buttonHandle.I buttonHandle.h buttonRegistry.I \
-    buttonRegistry.h \
+    buttonHandle.I buttonHandle.h \
+    buttonMap.I buttonMap.h \
+    buttonRegistry.I buttonRegistry.h \
     cachedTypedWritableReferenceCount.h cachedTypedWritableReferenceCount.I \
     callbackData.h callbackData.I \
     callbackObject.h callbackObject.I \

+ 16 - 0
panda/src/putil/buttonHandle.cxx

@@ -20,6 +20,22 @@ ButtonHandle ButtonHandle::_none;
 
 TypeHandle ButtonHandle::_type_handle;
 
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonHandle::Constructor
+//       Access: Published
+//  Description: Constructs a ButtonHandle with the corresponding
+//               name, which is looked up in the ButtonRegistry.
+//               This exists for the purpose of being able to
+//               automatically coerce a string into a ButtonHandle;
+//               for most purposes, you should use either the static
+//               KeyboardButton/MouseButton getters or
+//               ButtonRegistry::register_button().
+////////////////////////////////////////////////////////////////////
+ButtonHandle::
+ButtonHandle(const string &name) {
+  _index = ButtonRegistry::ptr()->get_button(name)._index;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ButtonHandle::get_name
 //       Access: Public

+ 1 - 0
panda/src/putil/buttonHandle.h

@@ -29,6 +29,7 @@ class EXPCL_PANDA_PUTIL ButtonHandle {
 PUBLISHED:
   INLINE ButtonHandle();
   INLINE ButtonHandle(int index);
+  ButtonHandle(const string &name);
 
 public:
   INLINE ButtonHandle(const ButtonHandle &copy);

+ 141 - 0
panda/src/putil/buttonMap.I

@@ -0,0 +1,141 @@
+// Filename: buttonMap.I
+// Created by:  rdb (09Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonMap::get_num_buttons
+//       Access: Published
+//  Description: Returns the number of buttons that this button
+//               mapping specifies.
+////////////////////////////////////////////////////////////////////
+INLINE int ButtonMap::
+get_num_buttons() const {
+  return _buttons.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonMap::get_raw_button
+//       Access: Published
+//  Description: Returns the underlying raw button associated with
+//               the nth button.
+////////////////////////////////////////////////////////////////////
+INLINE ButtonHandle ButtonMap::
+get_raw_button(int i) const {
+  return _buttons[i]->_raw;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonMap::get_mapped_button
+//       Access: Published
+//  Description: Returns the nth mapped button, meaning the button
+//               that the nth raw button is mapped to.
+////////////////////////////////////////////////////////////////////
+INLINE ButtonHandle ButtonMap::
+get_mapped_button(int i) const {
+  return _buttons[i]->_mapped;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonMap::get_mapped_button_label
+//       Access: Published
+//  Description: Returns the label associated with the nth mapped
+//               button, meaning the button that the nth raw
+//               button is mapped to.
+////////////////////////////////////////////////////////////////////
+INLINE const string &ButtonMap::
+get_mapped_button_label(int i) const {
+  return _buttons[i]->_label;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonMap::get_mapped_button
+//       Access: Published
+//  Description: Returns the button that the given button is mapped
+//               to, or ButtonHandle::none() if this map does not
+//               specify a mapped button for the given raw button.
+////////////////////////////////////////////////////////////////////
+INLINE ButtonHandle ButtonMap::
+get_mapped_button(ButtonHandle raw) const {
+  pmap<int, ButtonNode>::const_iterator it;
+  it = _button_map.find(raw.get_index());
+  if (it == _button_map.end()) {
+    return ButtonHandle::none();
+  } else {
+    return it->second._mapped;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonMap::get_mapped_button
+//       Access: Published
+//  Description: Returns the button that the given button is mapped
+//               to, or ButtonHandle::none() if this map does not
+//               specify a mapped button for the given raw button.
+////////////////////////////////////////////////////////////////////
+INLINE ButtonHandle ButtonMap::
+get_mapped_button(const string &raw_name) const {
+  ButtonHandle raw_button = ButtonRegistry::ptr()->find_button(raw_name);
+  if (raw_button == ButtonHandle::none()) {
+    return ButtonHandle::none();
+  } else {
+    return get_mapped_button(raw_button);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtoMap::get_mapped_button_label
+//       Access: Published
+//  Description: If the button map specifies a special name for the
+//               button (eg. if the operating system or keyboard
+//               device has a localized name describing the key),
+//               returns it, or the empty string otherwise.
+//
+//               Note that this is not the same as
+//               get_mapped_button().get_name(), which returns the
+//               name of the Panda event associated with the button.
+////////////////////////////////////////////////////////////////////
+INLINE const string &ButtonMap::
+get_mapped_button_label(ButtonHandle raw) const {
+  pmap<int, ButtonNode>::const_iterator it;
+  it = _button_map.find(raw.get_index());
+  if (it == _button_map.end()) {
+    static const string empty = "";
+    return empty;
+  } else {
+    return it->second._label;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtoMap::get_mapped_button_label
+//       Access: Published
+//  Description: If the button map specifies a special name for the
+//               button (eg. if the operating system or keyboard
+//               device has a localized name describing the key),
+//               returns it, or the empty string otherwise.
+//
+//               Note that this is not the same as
+//               get_mapped_button().get_name(), which returns the
+//               name of the Panda event associated with the button.
+////////////////////////////////////////////////////////////////////
+INLINE const string &ButtonMap::
+get_mapped_button_label(const string &raw_name) const {
+  ButtonHandle raw_button = ButtonRegistry::ptr()->find_button(raw_name);
+  if (raw_button == ButtonHandle::none()) {
+    static const string empty = "";
+    return empty;
+  } else {
+    return get_mapped_button_label(raw_button);
+  }
+}

+ 67 - 0
panda/src/putil/buttonMap.cxx

@@ -0,0 +1,67 @@
+// Filename: buttonMap.cxx
+// Created by:  rdb (09Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "buttonMap.h"
+
+TypeHandle ButtonMap::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonMap::map_button
+//       Access: Public
+//  Description: Registers a new button mapping.
+////////////////////////////////////////////////////////////////////
+void ButtonMap::
+map_button(ButtonHandle raw_button, ButtonHandle button, const string &label) {
+  int index = raw_button.get_index();
+  if (_button_map.find(index) != _button_map.end()) {
+    // A button with this index was already mapped.
+    return;
+  }
+
+  ButtonNode bnode;
+  bnode._raw = raw_button;
+  bnode._mapped = button;
+  bnode._label = label;
+  _button_map[index] = bnode;
+  _buttons.push_back(&_button_map[index]);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonMap::output
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void ButtonMap::
+output(ostream &out) const {
+  out << "ButtonMap (" << get_num_buttons() << " buttons)";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonMap::write
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void ButtonMap::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level)
+    << "ButtonMap, " << get_num_buttons() << " buttons:\n";
+
+  pvector<ButtonNode*>::const_iterator it;
+  for (it = _buttons.begin(); it != _buttons.end(); ++it) {
+    const ButtonNode *bn = *it;
+
+    indent(out, indent_level + 2)
+      << bn->_raw << " -> " << bn->_mapped << " \"" << bn->_label << "\"\n";
+  }
+}

+ 81 - 0
panda/src/putil/buttonMap.h

@@ -0,0 +1,81 @@
+// Filename: buttonMap.h
+// Created by:  rdb (07Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef BUTTONMAP_H
+#define BUTTONMAP_H
+
+#include "pandabase.h"
+#include "typedReferenceCount.h"
+#include "buttonHandle.h"
+#include "buttonRegistry.h"
+#include "pmap.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : ButtonMap
+// Description : This class represents a map containing all of the
+//               buttons of a (keyboard) device, though it can also
+//               be used as a generic mapping between ButtonHandles.
+//               It maps an underlying 'raw' button to a 'virtual'
+//               button, which may optionally be associated with an
+//               appropriate platform-specific name for the button.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA_PUTIL ButtonMap : public TypedReferenceCount {
+PUBLISHED:
+  INLINE int get_num_buttons() const;
+  INLINE ButtonHandle get_raw_button(int i) const;
+  INLINE ButtonHandle get_mapped_button(int i) const;
+  INLINE const string &get_mapped_button_label(int i) const;
+
+  INLINE ButtonHandle get_mapped_button(ButtonHandle raw) const;
+  INLINE ButtonHandle get_mapped_button(const string &raw_name) const;
+  INLINE const string &get_mapped_button_label(ButtonHandle raw) const;
+  INLINE const string &get_mapped_button_label(const string &raw_name) const;
+
+  void output(ostream &out) const;
+  void write(ostream &out, int indent_level = 0) const;
+
+public:
+  void map_button(ButtonHandle raw_button, ButtonHandle button, const string &label = "");
+
+private:
+  struct ButtonNode {
+    ButtonHandle _raw;
+    ButtonHandle _mapped;
+    string _label;
+  };
+
+  pmap<int, ButtonNode> _button_map;
+  pvector<ButtonNode*> _buttons;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedReferenceCount::init_type();
+    register_type(_type_handle, "ButtonMap",
+                  TypedReferenceCount::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "buttonMap.I"
+
+#endif

+ 19 - 0
panda/src/putil/buttonRegistry.cxx

@@ -131,6 +131,25 @@ get_button(const string &name) {
   return button;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonRegistry::find_button
+//       Access: Published
+//  Description: Finds a ButtonHandle in the registry matching the
+//               indicated name.  If there is no such ButtonHandle,
+//               returns ButtonHandle::none().
+////////////////////////////////////////////////////////////////////
+ButtonHandle ButtonRegistry::
+find_button(const string &name) {
+  NameRegistry::const_iterator ri;
+  ri = _name_registry.find(name);
+
+  if (ri != _name_registry.end()) {
+    return (*ri).second->_handle;
+  }
+
+  return ButtonHandle::none();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ButtonRegistry::find_ascii_button
 //       Access: Published

+ 1 - 0
panda/src/putil/buttonRegistry.h

@@ -48,6 +48,7 @@ public:
 
 PUBLISHED:
   ButtonHandle get_button(const string &name);
+  ButtonHandle find_button(const string &name);
   ButtonHandle find_ascii_button(char ascii_equivalent) const;
 
   void write(ostream &out) const;

+ 2 - 0
panda/src/putil/config_util.cxx

@@ -21,6 +21,7 @@
 #include "bitArray.h"
 #include "bitMask.h"
 #include "buttonHandle.h"
+#include "buttonMap.h"
 #include "cachedTypedWritableReferenceCount.h"
 #include "callbackData.h"
 #include "callbackObject.h"
@@ -186,6 +187,7 @@ init_libputil() {
   BitMask32::init_type();
   BitMask64::init_type();
   ButtonHandle::init_type();
+  ButtonMap::init_type();
   CPointerCallbackObject::init_type();
   CachedTypedWritableReferenceCount::init_type();
   CallbackData::init_type();

+ 1 - 0
panda/src/putil/p3putil_composite1.cxx

@@ -10,6 +10,7 @@
 #include "bitArray.cxx"
 #include "bitMask.cxx"
 #include "buttonHandle.cxx"
+#include "buttonMap.cxx"
 #include "buttonRegistry.cxx"
 #include "cachedTypedWritableReferenceCount.cxx"
 #include "callbackData.cxx"

+ 4 - 0
panda/src/rocket/rocketRegion_ext.cxx

@@ -15,6 +15,8 @@
 #include "rocketRegion_ext.h"
 #include "extension.h"
 
+#ifdef HAVE_PYTHON
+
 #ifndef CPPPARSER
 #define HAVE_LONG_LONG 1
 #include <Rocket/Core/Context.h>
@@ -48,3 +50,5 @@ get_context() const {
   }
   return NULL;
 }
+
+#endif

+ 6 - 11
panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx

@@ -1350,16 +1350,16 @@ end_draw_primitives() {
 //               copy.
 ////////////////////////////////////////////////////////////////////
 bool TinyGraphicsStateGuardian::
-framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
+framebuffer_copy_to_texture(Texture *tex, int view, int z,
+                            const DisplayRegion *dr,
                             const RenderBuffer &rb) {
   nassertr(tex != NULL && dr != NULL, false);
-  
+
   int xo, yo, w, h;
   dr->get_region_pixels_i(xo, yo, w, h);
 
   tex->setup_2d_texture(w, h, Texture::T_unsigned_byte, Texture::F_rgba);
 
-  int view = dr->get_target_tex_view();
   TextureContext *tc = tex->prepare_now(view, get_prepared_objects(), this);
   nassertr(tc != (TextureContext *)NULL, false);
   TinyTextureContext *gtc = DCAST(TinyTextureContext, tc);
@@ -1389,7 +1389,6 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
   return true;
 }
 
-
 ////////////////////////////////////////////////////////////////////
 //     Function: TinyGraphicsStateGuardian::framebuffer_copy_to_ram
 //       Access: Public, Virtual
@@ -1401,10 +1400,11 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
 //               indicated texture.
 ////////////////////////////////////////////////////////////////////
 bool TinyGraphicsStateGuardian::
-framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
+framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                        const DisplayRegion *dr,
                         const RenderBuffer &rb) {
   nassertr(tex != NULL && dr != NULL, false);
-  
+
   int xo, yo, w, h;
   dr->get_region_pixels_i(xo, yo, w, h);
 
@@ -1433,11 +1433,6 @@ framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
 
   nassertr(z < tex->get_z_size(), false);
 
-  int view = dr->get_target_tex_view();
-  if (view >= tex->get_num_views()) {
-    tex->set_num_views(view + 1);
-  }
-
   unsigned char *image_ptr = tex->modify_ram_image();
   size_t image_size = tex->get_ram_image_size();
   if (z >= 0 || view > 0) {

+ 2 - 2
panda/src/tinydisplay/tinyGraphicsStateGuardian.h

@@ -82,9 +82,9 @@ public:
   virtual void end_draw_primitives();
 
   virtual bool framebuffer_copy_to_texture
-  (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb);
+  (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
   virtual bool framebuffer_copy_to_ram
-  (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb);
+  (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
 
   virtual void set_state_and_transform(const RenderState *state,
                                        const TransformState *transform);

+ 1 - 1
panda/src/wgldisplay/wglGraphicsBuffer.cxx

@@ -214,7 +214,7 @@ bind_texture_to_pbuffer() {
 //               the indicated face.
 ////////////////////////////////////////////////////////////////////
 void wglGraphicsBuffer::
-select_target_tex_page(int page, int view) {
+select_target_tex_page(int page) {
   wglGraphicsStateGuardian *wglgsg;
   DCAST_INTO_V(wglgsg, _gsg);
 

+ 1 - 1
panda/src/wgldisplay/wglGraphicsBuffer.h

@@ -49,7 +49,7 @@ public:
   virtual bool begin_frame(FrameMode mode, Thread *current_thread);
   virtual void end_frame(FrameMode mode, Thread *current_thread);
 
-  virtual void select_target_tex_page(int page, int view);
+  virtual void select_target_tex_page(int page);
 
   virtual void process_events();
 

Some files were not shown because too many files changed in this diff