瀏覽代碼

Merge remote-tracking branch 'origin/master' into deploy-ng

rdb 8 年之前
父節點
當前提交
fba41dafae
共有 92 個文件被更改,包括 4493 次插入2735 次删除
  1. 0 10
      direct/metalibs/direct/direct.cxx
  2. 1 1
      direct/src/dcparser/dcPacker.cxx
  3. 1727 1686
      dtool/src/cppparser/cppBison.cxx.prebuilt
  4. 175 100
      dtool/src/cppparser/cppBison.yxx
  5. 8 28
      dtool/src/cppparser/cppMakeProperty.cxx
  6. 63 8
      dtool/src/cppparser/cppMakeProperty.h
  7. 10 0
      dtool/src/dtoolbase/dtoolbase.h
  8. 4 0
      dtool/src/dtoolutil/executionEnvironment.h
  9. 6 8
      dtool/src/interrogate/interfaceMaker.cxx
  10. 2 2
      dtool/src/interrogate/interfaceMaker.h
  11. 1 1
      dtool/src/interrogate/interfaceMakerPython.cxx
  12. 386 214
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  13. 4 0
      dtool/src/interrogate/interfaceMakerPythonNative.h
  14. 96 41
      dtool/src/interrogate/interrogateBuilder.cxx
  15. 10 0
      dtool/src/interrogatedb/interrogateElement.I
  16. 4 0
      dtool/src/interrogatedb/interrogateElement.h
  17. 211 19
      dtool/src/interrogatedb/py_panda.cxx
  18. 27 6
      dtool/src/interrogatedb/py_panda.h
  19. 2 10
      dtool/src/prc/notifyCategory.I
  20. 2 10
      dtool/src/prc/notifyCategoryProxy.I
  21. 1 6
      makepanda/makepanda.py
  22. 2 0
      makepanda/makepandacore.py
  23. 28 0
      panda/src/bullet/bulletTriangleMesh.I
  24. 1 1
      panda/src/bullet/bulletTriangleMesh.cxx
  25. 11 2
      panda/src/bullet/bulletTriangleMesh.h
  26. 3 2
      panda/src/chan/animGroup.h
  27. 3 1
      panda/src/chan/partGroup.h
  28. 1 1
      panda/src/collide/collisionBox.I
  29. 2 2
      panda/src/display/graphicsEngine.cxx
  30. 2 2
      panda/src/display/graphicsEngine.h
  31. 35 20
      panda/src/display/graphicsOutput.cxx
  32. 71 3
      panda/src/display/graphicsStateGuardian.cxx
  33. 3 0
      panda/src/display/graphicsStateGuardian.h
  34. 13 38
      panda/src/display/standardMunger.cxx
  35. 2 2
      panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
  36. 40 0
      panda/src/event/eventHandler.cxx
  37. 3 0
      panda/src/event/eventHandler.h
  38. 5 7
      panda/src/express/pointerToArray.h
  39. 164 87
      panda/src/express/pointerToArray_ext.I
  40. 29 10
      panda/src/express/pointerToArray_ext.h
  41. 6 3
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  42. 21 0
      panda/src/glstuff/glTextureContext_src.cxx
  43. 3 0
      panda/src/glstuff/glTextureContext_src.h
  44. 3 0
      panda/src/gobj/geomVertexFormat.h
  45. 2 2
      panda/src/gobj/internalName_ext.cxx
  46. 2 0
      panda/src/gobj/texture.h
  47. 21 0
      panda/src/gobj/textureContext.cxx
  48. 2 0
      panda/src/gobj/textureContext.h
  49. 61 15
      panda/src/gobj/textureStage.I
  50. 3 1
      panda/src/gobj/textureStage.cxx
  51. 6 1
      panda/src/gobj/textureStage.h
  52. 11 0
      panda/src/gobj/transformBlend.I
  53. 6 0
      panda/src/gobj/transformBlend.h
  54. 1 0
      panda/src/gsgbase/graphicsStateGuardianBase.cxx
  55. 13 0
      panda/src/gsgbase/graphicsStateGuardianBase.h
  56. 21 0
      panda/src/linmath/lvecBase4_src.I
  57. 2 0
      panda/src/linmath/lvecBase4_src.h
  58. 4 0
      panda/src/pgraph/camera.h
  59. 1 1
      panda/src/pgraph/clipPlaneAttrib.cxx
  60. 1 1
      panda/src/pgraph/config_pgraph.cxx
  61. 8 3
      panda/src/pgraph/cullableObject.cxx
  62. 9 1
      panda/src/pgraph/lightAttrib.cxx
  63. 11 3
      panda/src/pgraph/nodePath.h
  64. 28 173
      panda/src/pgraph/nodePath_ext.cxx
  65. 1 0
      panda/src/pgraph/nodePath_ext.h
  66. 2 0
      panda/src/pgraph/p3pgraph_ext_composite.cxx
  67. 1 1
      panda/src/pgraph/renderAttrib.cxx
  68. 1 3
      panda/src/pgraph/renderAttrib.h
  69. 1 1
      panda/src/pgraph/renderAttribRegistry.cxx
  70. 3 2
      panda/src/pgraph/renderState.cxx
  71. 12 6
      panda/src/pgraph/renderState.h
  72. 6 0
      panda/src/pgraph/shaderAttrib.h
  73. 71 0
      panda/src/pgraph/shaderAttrib_ext.cxx
  74. 38 0
      panda/src/pgraph/shaderAttrib_ext.h
  75. 8 0
      panda/src/pgraph/shaderInput.I
  76. 9 1
      panda/src/pgraph/shaderInput.h
  77. 539 0
      panda/src/pgraph/shaderInput_ext.cxx
  78. 37 0
      panda/src/pgraph/shaderInput_ext.h
  79. 10 1
      panda/src/pgraph/stateMunger.I
  80. 9 4
      panda/src/pgraph/stateMunger.cxx
  81. 3 2
      panda/src/pgraph/stateMunger.h
  82. 6 0
      panda/src/pgraph/textureAttrib.h
  83. 1 1
      panda/src/pgraph/transformState.cxx
  84. 1 3
      panda/src/pgraph/transformState.h
  85. 218 123
      panda/src/pgraphnodes/shaderGenerator.cxx
  86. 34 19
      panda/src/pgraphnodes/shaderGenerator.h
  87. 20 9
      panda/src/putil/simpleHashMap.I
  88. 51 19
      panda/src/putil/simpleHashMap.h
  89. 1 1
      pandatool/src/eggbase/eggReader.cxx
  90. 1 1
      samples/fireflies/main.py
  91. 5 5
      samples/shadows/advanced.py
  92. 0 1
      samples/shadows/basic.py

+ 0 - 10
direct/metalibs/direct/direct.cxx

@@ -1,10 +0,0 @@
-/**
- * @file direct.cxx
- * @author drose
- * @date 2000-05-18
- */
-
-// This is a dummy file whose sole purpose is to give the compiler something
-// to compile when making libdirect.so in NO_DEFER mode, which generates an
-// empty library that itself links with all the other shared libraries that
-// make up libdirect.

+ 1 - 1
direct/src/dcparser/dcPacker.cxx

@@ -708,7 +708,7 @@ pack_object(PyObject *object) {
     pack_int64(PyLong_AsLongLong(object));
     pack_int64(PyLong_AsLongLong(object));
 #if PY_MAJOR_VERSION >= 3
 #if PY_MAJOR_VERSION >= 3
   } else if (PyUnicode_Check(object)) {
   } else if (PyUnicode_Check(object)) {
-    char *buffer;
+    const char *buffer;
     Py_ssize_t length;
     Py_ssize_t length;
     buffer = PyUnicode_AsUTF8AndSize(object, &length);
     buffer = PyUnicode_AsUTF8AndSize(object, &length);
     if (buffer) {
     if (buffer) {

File diff suppressed because it is too large
+ 1727 - 1686
dtool/src/cppparser/cppBison.cxx.prebuilt


+ 175 - 100
dtool/src/cppparser/cppBison.yxx

@@ -397,6 +397,7 @@ pop_struct() {
 %type <u.type> class_derivation_name
 %type <u.type> class_derivation_name
 %type <u.type> enum_element_type
 %type <u.type> enum_element_type
 %type <u.type> maybe_trailing_return_type
 %type <u.type> maybe_trailing_return_type
+%type <u.identifier> maybe_comma_identifier
 /*%type <u.type> typedefname*/
 /*%type <u.type> typedefname*/
 %type <u.identifier> name
 %type <u.identifier> name
 %type <u.identifier> name_no_final
 %type <u.identifier> name_no_final
@@ -548,202 +549,264 @@ declaration:
 {
 {
   current_scope->set_current_vis(V_private);
   current_scope->set_current_vis(V_private);
 }
 }
-        | KW_MAKE_PROPERTY '(' name ',' IDENTIFIER ')' ';'
-{
-
-  CPPDeclaration *getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
-    yyerror("Reference to non-existent or invalid getter: " + $5->get_fully_scoped_name(), @5);
-  }
-
-  CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(), NULL, current_scope, @1.file);
-  current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
-}
-        | KW_MAKE_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+        | KW_MAKE_PROPERTY '(' name ',' IDENTIFIER maybe_comma_identifier ')' ';'
 {
 {
   CPPDeclaration *getter = $5->find_symbol(current_scope, global_scope, current_lexer);
   CPPDeclaration *getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $5->get_fully_scoped_name(), @5);
     yyerror("Reference to non-existent or invalid getter: " + $5->get_fully_scoped_name(), @5);
-
   } else {
   } else {
-    CPPDeclaration *setter = $7->find_symbol(current_scope, global_scope, current_lexer);
-    CPPFunctionGroup *setter_func = NULL;
-
-    if (setter == (CPPDeclaration *)NULL || setter->get_subtype() != CPPDeclaration::ST_function_group) {
-      yyerror("Reference to non-existent or invalid setter: " + $7->get_fully_scoped_name(), @7);
-    } else {
-      setter_func = setter->as_function_group();
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_normal, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+
+    if ($6 != nullptr) {
+      CPPDeclaration *setter = $6->find_symbol(current_scope, global_scope, current_lexer);
+      if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+        yyerror("Reference to non-existent or invalid setter: " + $6->get_fully_scoped_name(), @6);
+      } else {
+        make_property->_set_function = setter->as_function_group();
+      }
     }
     }
 
 
-    CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(),
-                                                         setter_func, current_scope, @1.file);
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
   }
 }
 }
         | KW_MAKE_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
         | KW_MAKE_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
 {
   CPPDeclaration *getter = $5->find_symbol(current_scope, global_scope, current_lexer);
   CPPDeclaration *getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $5->get_fully_scoped_name(), @5);
     yyerror("Reference to non-existent or invalid getter: " + $5->get_fully_scoped_name(), @5);
 
 
   } else {
   } else {
-    CPPDeclaration *setter = $7->find_symbol(current_scope, global_scope, current_lexer);
-    CPPFunctionGroup *setter_func = NULL;
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_normal, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
 
 
-    if (setter == (CPPDeclaration *)NULL || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+    CPPDeclaration *setter = $7->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
       yyerror("Reference to non-existent or invalid setter: " + $7->get_fully_scoped_name(), @7);
       yyerror("Reference to non-existent or invalid setter: " + $7->get_fully_scoped_name(), @7);
     } else {
     } else {
-      setter_func = setter->as_function_group();
+      make_property->_set_function = setter->as_function_group();
     }
     }
 
 
     CPPDeclaration *deleter = $9->find_symbol(current_scope, global_scope, current_lexer);
     CPPDeclaration *deleter = $9->find_symbol(current_scope, global_scope, current_lexer);
-    if (deleter == (CPPDeclaration *)NULL || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
+    if (deleter == nullptr || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
       yyerror("reference to non-existent or invalid delete method: " + $9->get_fully_scoped_name(), @9);
       yyerror("reference to non-existent or invalid delete method: " + $9->get_fully_scoped_name(), @9);
-      deleter = NULL;
-    }
-
-    CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(),
-                                                         setter_func, current_scope, @1.file);
-    if (deleter) {
+    } else {
       make_property->_del_function = deleter->as_function_group();
       make_property->_del_function = deleter->as_function_group();
     }
     }
+
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
   }
 }
 }
         | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
         | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
 {
   CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
   CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (length_getter == (CPPDeclaration *)NULL || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (length_getter == nullptr || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
     yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
-    length_getter = NULL;
+    length_getter = nullptr;
   }
   }
 
 
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+    getter = nullptr;
   }
   }
 
 
-  CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(), NULL, current_scope, @1.file);
-  make_property->_length_function = length_getter->as_function_group();
-  current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
+  if (getter != nullptr && length_getter != nullptr) {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_sequence, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+    make_property->_length_function = length_getter->as_function_group();
+    current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
+  }
 }
 }
         | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
         | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
 {
   CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
   CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (length_getter == (CPPDeclaration *)NULL || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (length_getter == nullptr || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
     yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
-    length_getter = NULL;
+    length_getter = nullptr;
   }
   }
 
 
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+    getter = nullptr;
+  }
 
 
-  } else {
-    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
-    CPPFunctionGroup *setter_func = NULL;
+  if (getter != nullptr && length_getter != nullptr) {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_sequence, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+    make_property->_length_function = length_getter->as_function_group();
 
 
-    if (setter == (CPPDeclaration *)NULL || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
       yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
       yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
     } else {
     } else {
-      setter_func = setter->as_function_group();
+      make_property->_set_function = setter->as_function_group();
     }
     }
 
 
-    CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(),
-                                                         setter_func, current_scope, @1.file);
-    make_property->_length_function = length_getter->as_function_group();
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
   }
 }
 }
         | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
         | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
 {
   CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
   CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (length_getter == (CPPDeclaration *)NULL || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (length_getter == nullptr || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
     yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
     length_getter = NULL;
     length_getter = NULL;
   }
   }
 
 
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+    getter = nullptr;
+  }
 
 
-  } else {
-    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
-    CPPFunctionGroup *setter_func = NULL;
+  if (getter != nullptr && length_getter != nullptr) {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_sequence, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+    make_property->_length_function = length_getter->as_function_group();
 
 
-    if (setter == (CPPDeclaration *)NULL || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
       yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
       yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
     } else {
     } else {
-      setter_func = setter->as_function_group();
+      make_property->_set_function = setter->as_function_group();
     }
     }
 
 
     CPPDeclaration *deleter = $11->find_symbol(current_scope, global_scope, current_lexer);
     CPPDeclaration *deleter = $11->find_symbol(current_scope, global_scope, current_lexer);
-    if (deleter == (CPPDeclaration *)NULL || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
+    if (deleter == nullptr || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
       yyerror("reference to non-existent or invalid delete method: " + $11->get_fully_scoped_name(), @11);
       yyerror("reference to non-existent or invalid delete method: " + $11->get_fully_scoped_name(), @11);
-      deleter = NULL;
-    }
-
-    CPPMakeProperty *make_property = new CPPMakeProperty($3, getter->as_function_group(),
-                                                         setter_func, current_scope, @1.file);
-    make_property->_length_function = length_getter->as_function_group();
-    if (deleter) {
+    } else {
       make_property->_del_function = deleter->as_function_group();
       make_property->_del_function = deleter->as_function_group();
     }
     }
+
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
   }
 }
 }
-        | KW_MAKE_PROPERTY2 '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+        | KW_MAKE_MAP_PROPERTY '(' name ',' IDENTIFIER ')' ';'
 {
 {
-  CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (hasser == (CPPDeclaration *)NULL || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
-    yyerror("Reference to non-existent or invalid has-function: " + $5->get_fully_scoped_name(), @5);
-  }
+  CPPDeclaration *getter = $5->find_symbol(current_scope, global_scope, current_lexer);
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("reference to non-existent or invalid item getter method: " + $5->get_fully_scoped_name(), @5);
 
 
+  } else {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_mapping, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+    current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
+  }
+}
+        | KW_MAKE_MAP_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+{
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
-  }
 
 
-  if (hasser && getter) {
+  } else {
     CPPMakeProperty *make_property;
     CPPMakeProperty *make_property;
-    make_property = new CPPMakeProperty($3,
-                                        hasser->as_function_group(),
-                                        getter->as_function_group(),
-                                        NULL, NULL,
-                                        current_scope, @1.file);
+    make_property = new CPPMakeProperty($3, CPPMakeProperty::T_mapping, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+
+    CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
+    if (hasser == nullptr || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid has/find method: " + $5->get_fully_scoped_name(), @5);
+    } else {
+      make_property->_has_function = hasser->as_function_group();
+    }
+
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
   }
 }
 }
-        | KW_MAKE_PROPERTY2 '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+        | KW_MAKE_MAP_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER maybe_comma_identifier ')' ';'
 {
 {
-  CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
-  if (hasser == (CPPDeclaration *)NULL || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
-    yyerror("Reference to non-existent or invalid has-function: " + $5->get_fully_scoped_name(), @5);
-  }
-
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
   CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
-  if (getter == (CPPDeclaration *)NULL || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
     yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
-  }
 
 
-  CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
-  if (setter == (CPPDeclaration *)NULL || setter->get_subtype() != CPPDeclaration::ST_function_group) {
-    yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
+  } else {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_mapping, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+
+    CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
+    if (hasser == nullptr || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid has/find method: " + $5->get_fully_scoped_name(), @5);
+    } else {
+      make_property->_has_function = hasser->as_function_group();
+    }
+
+    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
+    } else {
+      make_property->_set_function = setter->as_function_group();
+    }
+
+    if ($10 != nullptr) {
+      CPPDeclaration *deleter = $10->find_symbol(current_scope, global_scope, current_lexer);
+      if (deleter == nullptr || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
+        yyerror("reference to non-existent or invalid delete method: " + $10->get_fully_scoped_name(), @10);
+      } else {
+        make_property->_del_function = deleter->as_function_group();
+      }
+    }
+
+    current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
   }
+}
+        | KW_MAKE_PROPERTY2 '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+{
+  CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+
+  } else {
+    CPPMakeProperty *make_property;
+    make_property = new CPPMakeProperty($3, CPPMakeProperty::T_normal,
+                                        current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+
+    CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
+    if (hasser == nullptr || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid has/find method: " + $5->get_fully_scoped_name(), @5);
+    } else {
+      make_property->_has_function = hasser->as_function_group();
+    }
 
 
-  CPPDeclaration *clearer = $11->find_symbol(current_scope, global_scope, current_lexer);
-  if (clearer == (CPPDeclaration *)NULL || clearer->get_subtype() != CPPDeclaration::ST_function_group) {
-    yyerror("Reference to non-existent or invalid clear-function: " + $11->get_fully_scoped_name(), @11);
+    current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
   }
+}
+        | KW_MAKE_PROPERTY2 '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+{
+  CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
 
 
-  if (hasser && getter && setter && clearer) {
+  } else {
     CPPMakeProperty *make_property;
     CPPMakeProperty *make_property;
-    make_property = new CPPMakeProperty($3,
-                                        hasser->as_function_group(),
-                                        getter->as_function_group(),
-                                        setter->as_function_group(),
-                                        clearer->as_function_group(),
+    make_property = new CPPMakeProperty($3, CPPMakeProperty::T_normal,
                                         current_scope, @1.file);
                                         current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+
+    CPPDeclaration *hasser = $5->find_symbol(current_scope, global_scope, current_lexer);
+    if (hasser == nullptr || hasser->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid has/find method: " + $5->get_fully_scoped_name(), @5);
+    } else {
+      make_property->_has_function = hasser->as_function_group();
+    }
+
+    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
+    } else {
+      make_property->_set_function = setter->as_function_group();
+    }
+
+    CPPDeclaration *clearer = $11->find_symbol(current_scope, global_scope, current_lexer);
+    if (clearer == nullptr || clearer->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid clear method: " + $11->get_fully_scoped_name(), @11);
+    } else {
+      make_property->_clear_function = clearer->as_function_group();
+    }
+
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
   }
 }
 }
@@ -1723,6 +1786,18 @@ maybe_trailing_return_type:
         ;
         ;
 
 
 
 
+maybe_comma_identifier:
+        empty
+{
+  $$ = NULL;
+}
+        | ',' IDENTIFIER
+{
+  $$ = $2;
+}
+        ;
+
+
 function_parameter_list:
 function_parameter_list:
         empty
         empty
 {
 {

+ 8 - 28
dtool/src/cppparser/cppMakeProperty.cxx

@@ -18,37 +18,17 @@
  *
  *
  */
  */
 CPPMakeProperty::
 CPPMakeProperty::
-CPPMakeProperty(CPPIdentifier *ident,
-                CPPFunctionGroup *getter, CPPFunctionGroup *setter,
+CPPMakeProperty(CPPIdentifier *ident, Type type,
                 CPPScope *current_scope, const CPPFile &file) :
                 CPPScope *current_scope, const CPPFile &file) :
   CPPDeclaration(file),
   CPPDeclaration(file),
   _ident(ident),
   _ident(ident),
-  _length_function(NULL),
-  _has_function(NULL),
-  _get_function(getter),
-  _set_function(setter),
-  _clear_function(NULL),
-  _del_function(NULL)
-{
-  _ident->_native_scope = current_scope;
-}
-
-/**
- *
- */
-CPPMakeProperty::
-CPPMakeProperty(CPPIdentifier *ident,
-                CPPFunctionGroup *hasser, CPPFunctionGroup *getter,
-                CPPFunctionGroup *setter, CPPFunctionGroup *clearer,
-                CPPScope *current_scope, const CPPFile &file) :
-  CPPDeclaration(file),
-  _ident(ident),
-  _length_function(NULL),
-  _has_function(hasser),
-  _get_function(getter),
-  _set_function(setter),
-  _clear_function(clearer),
-  _del_function(NULL)
+  _type(type),
+  _length_function(nullptr),
+  _has_function(nullptr),
+  _get_function(nullptr),
+  _set_function(nullptr),
+  _clear_function(nullptr),
+  _del_function(nullptr)
 {
 {
   _ident->_native_scope = current_scope;
   _ident->_native_scope = current_scope;
 }
 }

+ 63 - 8
dtool/src/cppparser/cppMakeProperty.h

@@ -23,16 +23,72 @@
  * This is a MAKE_PROPERTY() declaration appearing within a class body.  It
  * This is a MAKE_PROPERTY() declaration appearing within a class body.  It
  * means to generate a property within Python, replacing (for instance)
  * means to generate a property within Python, replacing (for instance)
  * get_something()/set_something() with a synthetic 'something' attribute.
  * get_something()/set_something() with a synthetic 'something' attribute.
+ *
+ * This is an example of a simple property (MAKE_PROPERTY is defined as
+ * the built-in __make_property):
+ * @@code
+ *   Thing get_thing() const;
+ *   void set_thing(const Thing &);
+ *
+ *   MAKE_PROPERTY(thing, get_thing, set_thing);
+ * @@endcode
+ * The setter may be omitted to make the property read-only.
+ *
+ * There is also a secondary macro that allows the property to be set to a
+ * cleared state using separate clear functions.  In the scripting language,
+ * this would be represented by a "null" value, or an "optional" construct in
+ * languages that have no notion of a null value.
+ *
+ * @@code
+ *   bool has_thing() const;
+ *   Thing get_thing() const;
+ *   void set_thing(const Thing &);
+ *   void clear_thing();
+ *   MAKE_PROPERTY2(thing, has_thing, get_thing, set_thing, clear_thing);
+ * @@endcode
+ * As with MAKE_PROPERTY, both the setter and clearer can be omitted to create
+ * a read-only property.
+ *
+ * Thirdly, there is a variant called MAKE_SEQ_PROPERTY.  It takes a length
+ * function as argument and the getter and setter take an index as first
+ * argument:
+ * @@code
+ *   size_t get_num_things() const;
+ *   Thing &get_thing(size_t i) const;
+ *   void set_thing(size_t i, Thing value) const;
+ *   void remove_thing(size_t i) const;
+ *
+ *   MAKE_SEQ_PROPERTY(get_num_things, get_thing, set_thing, remove_thing);
+ * @@endcode
+ *
+ * Lastly, there is the possibility to have properties with key/value
+ * associations, often called a "map" or "dictionary" in scripting languages:
+ * @@code
+ *   bool has_thing(string key) const;
+ *   Thing &get_thing(string key) const;
+ *   void set_thing(string key, Thing value) const;
+ *   void clear_thing(string key) const;
+ *
+ *   MAKE_MAP_PROPERTY(things, has_thing, get_thing, set_thing, clear_thing);
+ * @@endcode
+ * You may also replace the "has" function with a "find" function that returns
+ * an index.  If the returned index is negative (or in the case of an unsigned
+ * integer, the maximum value), the item is assumed not to be present in the
+ * mapping.
+ *
+ * It is also possible to use both MAKE_SEQ_PROPERTY and MAKE_MAP_PROPERTY on
+ * the same property name.  This implies that this property has both a
+ * sequence and mapping interface.
  */
  */
 class CPPMakeProperty : public CPPDeclaration {
 class CPPMakeProperty : public CPPDeclaration {
 public:
 public:
-  CPPMakeProperty(CPPIdentifier *ident,
-                  CPPFunctionGroup *getter, CPPFunctionGroup *setter,
-                  CPPScope *current_scope, const CPPFile &file);
+  enum Type {
+    T_normal = 0x0,
+    T_sequence = 0x1,
+    T_mapping = 0x2,
+  };
 
 
-  CPPMakeProperty(CPPIdentifier *ident,
-                  CPPFunctionGroup *hasser, CPPFunctionGroup *getter,
-                  CPPFunctionGroup *setter, CPPFunctionGroup *clearer,
+  CPPMakeProperty(CPPIdentifier *ident, Type type,
                   CPPScope *current_scope, const CPPFile &file);
                   CPPScope *current_scope, const CPPFile &file);
 
 
   virtual string get_simple_name() const;
   virtual string get_simple_name() const;
@@ -46,8 +102,7 @@ public:
   virtual CPPMakeProperty *as_make_property();
   virtual CPPMakeProperty *as_make_property();
 
 
   CPPIdentifier *_ident;
   CPPIdentifier *_ident;
-  // If length_function is not NULL, this is actually a sequence property,
-  // and the other functions take an additional index argument.
+  Type _type;
   CPPFunctionGroup *_length_function;
   CPPFunctionGroup *_length_function;
   CPPFunctionGroup *_has_function;
   CPPFunctionGroup *_has_function;
   CPPFunctionGroup *_get_function;
   CPPFunctionGroup *_get_function;

+ 10 - 0
dtool/src/dtoolbase/dtoolbase.h

@@ -94,6 +94,14 @@
 #define RETURNS_ALIGNED(x)
 #define RETURNS_ALIGNED(x)
 #endif
 #endif
 
 
+#ifdef __GNUC__
+#define LIKELY(x) __builtin_expect(!!(x), 1)
+#define UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+#define LIKELY(x) (x)
+#define UNLIKELY(x) (x)
+#endif
+
 /*
 /*
   include win32 defns for everything up to WinServer2003, and assume
   include win32 defns for everything up to WinServer2003, and assume
   I'm smart enough to use GetProcAddress for backward compat on
   I'm smart enough to use GetProcAddress for backward compat on
@@ -456,6 +464,7 @@ typedef struct _object PyObject;
 #define MAKE_PROPERTY2(property_name, ...) __make_property2(property_name, __VA_ARGS__)
 #define MAKE_PROPERTY2(property_name, ...) __make_property2(property_name, __VA_ARGS__)
 #define MAKE_SEQ(seq_name, num_name, element_name) __make_seq(seq_name, num_name, element_name)
 #define MAKE_SEQ(seq_name, num_name, element_name) __make_seq(seq_name, num_name, element_name)
 #define MAKE_SEQ_PROPERTY(property_name, ...) __make_seq_property(property_name, __VA_ARGS__)
 #define MAKE_SEQ_PROPERTY(property_name, ...) __make_seq_property(property_name, __VA_ARGS__)
+#define MAKE_MAP_PROPERTY(property_name, ...) __make_map_property(property_name, __VA_ARGS__)
 #define EXTENSION(x) __extension x
 #define EXTENSION(x) __extension x
 #define EXTEND __extension
 #define EXTEND __extension
 #else
 #else
@@ -466,6 +475,7 @@ typedef struct _object PyObject;
 #define MAKE_PROPERTY2(property_name, ...)
 #define MAKE_PROPERTY2(property_name, ...)
 #define MAKE_SEQ(seq_name, num_name, element_name)
 #define MAKE_SEQ(seq_name, num_name, element_name)
 #define MAKE_SEQ_PROPERTY(property_name, ...)
 #define MAKE_SEQ_PROPERTY(property_name, ...)
+#define MAKE_MAP_PROPERTY(property_name, ...)
 #define EXTENSION(x)
 #define EXTENSION(x)
 #define EXTEND
 #define EXTEND
 #endif
 #endif

+ 4 - 0
dtool/src/dtoolutil/executionEnvironment.h

@@ -51,6 +51,10 @@ PUBLISHED:
 
 
   static Filename get_cwd();
   static Filename get_cwd();
 
 
+PUBLISHED:
+  MAKE_MAP_PROPERTY(environment_variables, has_environment_variable,
+                    get_environment_variable, set_environment_variable);
+
   MAKE_SEQ_PROPERTY(args, get_num_args, get_arg);
   MAKE_SEQ_PROPERTY(args, get_num_args, get_arg);
   MAKE_PROPERTY(binary_name, get_binary_name, set_binary_name);
   MAKE_PROPERTY(binary_name, get_binary_name, set_binary_name);
   MAKE_PROPERTY(dtool_name, get_dtool_name, set_dtool_name);
   MAKE_PROPERTY(dtool_name, get_dtool_name, set_dtool_name);

+ 6 - 8
dtool/src/interrogate/interfaceMaker.cxx

@@ -75,8 +75,8 @@ InterfaceMaker::MakeSeq::
 MakeSeq(const string &name, const InterrogateMakeSeq &imake_seq) :
 MakeSeq(const string &name, const InterrogateMakeSeq &imake_seq) :
   _name(name),
   _name(name),
   _imake_seq(imake_seq),
   _imake_seq(imake_seq),
-  _length_getter(NULL),
-  _element_getter(NULL)
+  _length_getter(nullptr),
+  _element_getter(nullptr)
 {
 {
 }
 }
 
 
@@ -86,12 +86,10 @@ MakeSeq(const string &name, const InterrogateMakeSeq &imake_seq) :
 InterfaceMaker::Property::
 InterfaceMaker::Property::
 Property(const InterrogateElement &ielement) :
 Property(const InterrogateElement &ielement) :
   _ielement(ielement),
   _ielement(ielement),
-  _length_function(NULL),
-  _getter(NULL),
-  _setter(NULL),
-  _has_function(NULL),
-  _clear_function(NULL),
-  _deleter(NULL),
+  _length_function(nullptr),
+  _has_function(nullptr),
+  _clear_function(nullptr),
+  _deleter(nullptr),
   _has_this(false)
   _has_this(false)
 {
 {
 }
 }

+ 2 - 2
dtool/src/interrogate/interfaceMaker.h

@@ -126,9 +126,9 @@ public:
     Property(const InterrogateElement &ielement);
     Property(const InterrogateElement &ielement);
 
 
     const InterrogateElement &_ielement;
     const InterrogateElement &_ielement;
+    vector<FunctionRemap *> _getter_remaps;
+    vector<FunctionRemap *> _setter_remaps;
     Function *_length_function;
     Function *_length_function;
-    Function *_getter;
-    Function *_setter;
     Function *_has_function;
     Function *_has_function;
     Function *_clear_function;
     Function *_clear_function;
     Function *_deleter;
     Function *_deleter;

+ 1 - 1
dtool/src/interrogate/interfaceMakerPython.cxx

@@ -51,7 +51,7 @@ test_assert(ostream &out, int indent_level) const {
     indent(out, indent_level)
     indent(out, indent_level)
       << "Notify *notify = Notify::ptr();\n";
       << "Notify *notify = Notify::ptr();\n";
     indent(out, indent_level)
     indent(out, indent_level)
-      << "if (notify->has_assert_failed()) {\n";
+      << "if (UNLIKELY(notify->has_assert_failed())) {\n";
     indent(out, indent_level + 2)
     indent(out, indent_level + 2)
       << "PyErr_SetString(PyExc_AssertionError, notify->get_assert_error_message().c_str());\n";
       << "PyErr_SetString(PyExc_AssertionError, notify->get_assert_error_message().c_str());\n";
     indent(out, indent_level + 2)
     indent(out, indent_level + 2)

+ 386 - 214
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -2640,8 +2640,7 @@ write_module_class(ostream &out, Object *obj) {
     for (pit = obj->_properties.begin(); pit != obj->_properties.end(); ++pit) {
     for (pit = obj->_properties.begin(); pit != obj->_properties.end(); ++pit) {
       Property *property = (*pit);
       Property *property = (*pit);
       const InterrogateElement &ielem = property->_ielement;
       const InterrogateElement &ielem = property->_ielement;
-      if (!property->_has_this ||
-          property->_getter == NULL || !is_function_legal(property->_getter)) {
+      if (!property->_has_this || property->_getter_remaps.empty()) {
         continue;
         continue;
       }
       }
 
 
@@ -2652,8 +2651,7 @@ write_module_class(ostream &out, Object *obj) {
 
 
       string getter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter";
       string getter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter";
       string setter = "NULL";
       string setter = "NULL";
-      if (property->_length_function == NULL &&
-          property->_setter != NULL && is_function_legal(property->_setter)) {
+      if (!ielem.is_sequence() && !ielem.is_mapping() && !property->_setter_remaps.empty()) {
         setter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Setter";
         setter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Setter";
       }
       }
 
 
@@ -3190,8 +3188,7 @@ write_module_class(ostream &out, Object *obj) {
   for (pit = obj->_properties.begin(); pit != obj->_properties.end(); ++pit) {
   for (pit = obj->_properties.begin(); pit != obj->_properties.end(); ++pit) {
     Property *property = (*pit);
     Property *property = (*pit);
     const InterrogateElement &ielem = property->_ielement;
     const InterrogateElement &ielem = property->_ielement;
-    if (property->_has_this ||
-        property->_getter == NULL || !is_function_legal(property->_getter)) {
+    if (property->_has_this || property->_getter_remaps.empty()) {
       continue;
       continue;
     }
     }
 
 
@@ -3200,8 +3197,7 @@ write_module_class(ostream &out, Object *obj) {
 
 
     string getter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter";
     string getter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter";
     string setter = "NULL";
     string setter = "NULL";
-    if (property->_length_function == NULL &&
-        property->_setter != NULL && is_function_legal(property->_setter)) {
+    if (!ielem.is_sequence() && !ielem.is_mapping() && !property->_setter_remaps.empty()) {
       setter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Setter";
       setter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Setter";
     }
     }
 
 
@@ -4389,57 +4385,6 @@ write_function_forset(ostream &out,
     std::sort(remaps.begin(), remaps.end(), RemapCompareLess);
     std::sort(remaps.begin(), remaps.end(), RemapCompareLess);
     std::vector<FunctionRemap *>::const_iterator sii;
     std::vector<FunctionRemap *>::const_iterator sii;
 
 
-    // Check if all of them have an InternalName pointer as first parameter.
-    // This is a dirty hack, of course, to work around an awkward overload
-    // resolution problem in NodePath::set_shader_input() (while perhaps also
-    // improving its performance).  If I had more time I'd create a better
-    // solution.
-    bool first_internalname = false;
-    string first_pexpr2(first_pexpr);
-    if (first_pexpr.empty() && args_type != AT_no_args) {
-      first_internalname = true;
-
-      for (sii = remaps.begin(); sii != remaps.end(); ++sii) {
-        remap = (*sii);
-        if (remap->_parameters.size() > (size_t)remap->_has_this) {
-          ParameterRemap *param = remap->_parameters[(size_t)remap->_has_this]._remap;
-          string param_name = param->get_orig_type()->get_local_name(&parser);
-
-          if (param_name != "CPT_InternalName" &&
-              param_name != "InternalName const *" &&
-              param_name != "InternalName *") {
-            // Aw.
-            first_internalname = false;
-            break;
-          }
-        } else {
-          first_internalname = false;
-          break;
-        }
-      }
-      if (first_internalname) {
-        // Yeah, all remaps have a first InternalName parameter, so process
-        // that and remove it from the args tuple.
-        if (args_type == AT_single_arg) {
-          // Bit of a weird case, but whatever.
-          indent(out, indent_level) << "PyObject *name_obj = arg;\n";
-          args_type = AT_no_args;
-        } else if (min_num_args == 2 && max_num_args == 2) {
-          indent(out, indent_level) << "PyObject *name_obj = PyTuple_GET_ITEM(args, 0);\n";
-          indent(out, indent_level) << "PyObject *arg = PyTuple_GET_ITEM(args, 1);\n";
-          args_type = AT_single_arg;
-        } else {
-          indent(out, indent_level) << "PyObject *name_obj = PyTuple_GET_ITEM(args, 0);\n";
-          indent(out, indent_level) << "args = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args));\n";
-          return_flags |= RF_decref_args;
-        }
-        indent(out, indent_level) << "PT(InternalName) name;\n";
-        indent(out, indent_level) << "if (Dtool_Coerce_InternalName(name_obj, name)) {\n";
-        indent_level += 2;
-        first_pexpr2 = "name";
-      }
-    }
-
     int num_coercion_possible = 0;
     int num_coercion_possible = 0;
     sii = remaps.begin();
     sii = remaps.begin();
     while (sii != remaps.end()) {
     while (sii != remaps.end()) {
@@ -4474,7 +4419,7 @@ write_function_forset(ostream &out,
       write_function_instance(out, remap, min_num_args, max_num_args,
       write_function_instance(out, remap, min_num_args, max_num_args,
                               expected_params, indent_level + 2,
                               expected_params, indent_level + 2,
                               false, false, args_type, return_flags,
                               false, false, args_type, return_flags,
-                              check_exceptions, first_pexpr2);
+                              check_exceptions, first_pexpr);
 
 
       indent(out, indent_level) << "}\n\n";
       indent(out, indent_level) << "}\n\n";
     }
     }
@@ -4507,28 +4452,11 @@ write_function_forset(ostream &out,
         write_function_instance(out, remap, min_num_args, max_num_args,
         write_function_instance(out, remap, min_num_args, max_num_args,
                                 ignore_expected_params, indent_level + 2,
                                 ignore_expected_params, indent_level + 2,
                                 true, false, args_type, return_flags,
                                 true, false, args_type, return_flags,
-                                check_exceptions, first_pexpr2);
+                                check_exceptions, first_pexpr);
 
 
         indent(out, indent_level) << "}\n\n";
         indent(out, indent_level) << "}\n\n";
       }
       }
     }
     }
-
-    if (first_internalname) {
-      indent_level -= 2;
-      if (report_errors) {
-        indent(out, indent_level) << "} else {\n";
-
-        string class_name = remap->_cpptype->get_simple_name();
-        ostringstream msg;
-        msg << classNameFromCppName(class_name, false) << "."
-            << methodNameFromCppName(remap, class_name, false)
-            << "() first argument must be str or InternalName";
-
-        error_raise_return(out, indent_level + 2, return_flags,
-                           "TypeError", msg.str());
-      }
-      indent(out, indent_level) << "}\n";
-    }
   } else {
   } else {
     // There is only one possible overload with this number of parameters.
     // There is only one possible overload with this number of parameters.
     // Just call it.
     // Just call it.
@@ -4891,7 +4819,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
                                       << default_value->_str.size() << ";\n";
                                       << default_value->_str.size() << ";\n";
           }
           }
         } else {
         } else {
-          indent(out, indent_level) << "char *" << param_name << "_str = NULL;\n";
+          indent(out, indent_level) << "const char *" << param_name << "_str = NULL;\n";
           indent(out, indent_level) << "Py_ssize_t " << param_name << "_len;\n";
           indent(out, indent_level) << "Py_ssize_t " << param_name << "_len;\n";
         }
         }
 
 
@@ -4900,7 +4828,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
           // As a special hack to fix pickling in Python 3, if the method name
           // As a special hack to fix pickling in Python 3, if the method name
           // starts with py_decode_, we take a bytes object instead of a str.
           // starts with py_decode_, we take a bytes object instead of a str.
           if (remap->_cppfunc->get_local_name().substr(0, 10) == "py_decode_") {
           if (remap->_cppfunc->get_local_name().substr(0, 10) == "py_decode_") {
-            indent(out, indent_level) << "if (PyBytes_AsStringAndSize(arg, &"
+            indent(out, indent_level) << "if (PyBytes_AsStringAndSize(arg, (char **)&"
               << param_name << "_str, &" << param_name << "_len) == -1) {\n";
               << param_name << "_str, &" << param_name << "_len) == -1) {\n";
             indent(out, indent_level + 2) << param_name << "_str = NULL;\n";
             indent(out, indent_level + 2) << param_name << "_str = NULL;\n";
             indent(out, indent_level) << "}\n";
             indent(out, indent_level) << "}\n";
@@ -4910,7 +4838,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
               << param_name << "_len);\n";
               << param_name << "_len);\n";
           }
           }
           out << "#else\n"; // NB. PyString_AsStringAndSize also accepts a PyUnicode.
           out << "#else\n"; // NB. PyString_AsStringAndSize also accepts a PyUnicode.
-          indent(out, indent_level) << "if (PyString_AsStringAndSize(arg, &"
+          indent(out, indent_level) << "if (PyString_AsStringAndSize(arg, (char **)&"
             << param_name << "_str, &" << param_name << "_len) == -1) {\n";
             << param_name << "_str, &" << param_name << "_len) == -1) {\n";
           indent(out, indent_level + 2) << param_name << "_str = NULL;\n";
           indent(out, indent_level + 2) << param_name << "_str = NULL;\n";
           indent(out, indent_level) << "}\n";
           indent(out, indent_level) << "}\n";
@@ -6065,14 +5993,14 @@ write_function_instance(ostream &out, FunctionRemap *remap,
     // this for coercion constructors since they are called by other wrapper
     // this for coercion constructors since they are called by other wrapper
     // functions which already check this on their own.  Generated getters
     // functions which already check this on their own.  Generated getters
     // obviously can't raise asserts.
     // obviously can't raise asserts.
-    if (watch_asserts && (return_flags & RF_coerced) == 0 &&
+    if (watch_asserts && (return_flags & (RF_coerced | RF_raise_keyerror)) == 0 &&
         remap->_type != FunctionRemap::T_getter &&
         remap->_type != FunctionRemap::T_getter &&
         remap->_type != FunctionRemap::T_setter) {
         remap->_type != FunctionRemap::T_setter) {
       out << "#ifndef NDEBUG\n";
       out << "#ifndef NDEBUG\n";
       indent(out, indent_level)
       indent(out, indent_level)
         << "Notify *notify = Notify::ptr();\n";
         << "Notify *notify = Notify::ptr();\n";
       indent(out, indent_level)
       indent(out, indent_level)
-        << "if (notify->has_assert_failed()) {\n";
+        << "if (UNLIKELY(notify->has_assert_failed())) {\n";
 
 
       if (manage_return) {
       if (manage_return) {
         // Output code to delete any temporary object we may have allocated.
         // Output code to delete any temporary object we may have allocated.
@@ -6194,6 +6122,27 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       indent(out, indent_level) << "manage = true;\n";
       indent(out, indent_level) << "manage = true;\n";
       indent(out, indent_level) << "return true;\n";
       indent(out, indent_level) << "return true;\n";
     }
     }
+
+  } else if (return_flags & RF_raise_keyerror) {
+    CPPType *orig_type = remap->_return_type->get_orig_type();
+
+    if (TypeManager::is_bool(orig_type) || TypeManager::is_pointer(orig_type)) {
+      indent(out, indent_level) << "if (!" << return_expr << ") {\n";
+    } else if (TypeManager::is_unsigned_integer(orig_type)) {
+      indent(out, indent_level) << "if ((int)" << return_expr << " == -1) {\n";
+    } else if (TypeManager::is_integer(orig_type)) {
+      indent(out, indent_level) << "if (" << return_expr << " < 0) {\n";
+    } else {
+      indent(out, indent_level) << "if (false) {\n";
+    }
+
+    if (args_type == AT_single_arg) {
+      indent(out, indent_level) << "  PyErr_SetObject(PyExc_KeyError, arg);\n";
+    } else {
+      indent(out, indent_level) << "  PyErr_SetObject(PyExc_KeyError, key);\n";
+    }
+    error_return(out, indent_level + 2, return_flags);
+    indent(out, indent_level) << "}\n";
   }
   }
 
 
   // Close the extra braces opened earlier.
   // Close the extra braces opened earlier.
@@ -6466,19 +6415,21 @@ write_make_seq(ostream &out, Object *obj, const std::string &ClassName,
  */
  */
 void InterfaceMakerPythonNative::
 void InterfaceMakerPythonNative::
 write_getset(ostream &out, Object *obj, Property *property) {
 write_getset(ostream &out, Object *obj, Property *property) {
+  // We keep around this empty vector for passing to get_call_str.
+  const vector_string pexprs;
 
 
   string ClassName = make_safe_name(obj->_itype.get_scoped_name());
   string ClassName = make_safe_name(obj->_itype.get_scoped_name());
   std::string cClassName = obj->_itype.get_true_name();
   std::string cClassName = obj->_itype.get_true_name();
 
 
   const InterrogateElement &ielem = property->_ielement;
   const InterrogateElement &ielem = property->_ielement;
 
 
-  if (property->_length_function != NULL) {
+  FunctionRemap *len_remap = nullptr;
+  if (property->_length_function != nullptr) {
     // This is actually a sequence.  Wrap this with a special class.
     // This is actually a sequence.  Wrap this with a special class.
-    FunctionRemap *len_remap = property->_length_function->_remaps.front();
-    vector_string pexprs;
+    len_remap = property->_length_function->_remaps.front();
 
 
     out << "/**\n"
     out << "/**\n"
-           " * sequence length function for property " << cClassName << "::" << ielem.get_name() << "\n"
+           " * sequence length function for property " << ielem.get_scoped_name() << "\n"
            " */\n"
            " */\n"
            "static Py_ssize_t Dtool_" + ClassName + "_" + ielem.get_name() + "_Len(PyObject *self) {\n";
            "static Py_ssize_t Dtool_" + ClassName + "_" + ielem.get_name() + "_Len(PyObject *self) {\n";
     if (property->_length_function->_has_this) {
     if (property->_length_function->_has_this) {
@@ -6492,72 +6443,72 @@ write_getset(ostream &out, Object *obj, Property *property) {
       out << "  return (Py_ssize_t)" << len_remap->get_call_str("", pexprs) << ";\n";
       out << "  return (Py_ssize_t)" << len_remap->get_call_str("", pexprs) << ";\n";
     }
     }
     out << "}\n\n";
     out << "}\n\n";
+  }
+
+  if (property->_getter_remaps.empty()) {
+    return;
+  }
 
 
-    // Now write out the getitem helper function.
-    if (property->_getter != NULL) {
+  if (ielem.is_sequence()) {
+    out <<
+      "/**\n"
+      " * sequence getter for property " << ielem.get_scoped_name() << "\n"
+      " */\n"
+      "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Sequence_Getitem(PyObject *self, Py_ssize_t index) {\n";
+
+    if (property->_has_this) {
       out <<
       out <<
-        "/**\n"
-        " * sequence getter for property " << cClassName << "::" << ielem.get_name() << "\n"
-        " */\n"
-        "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getitem(PyObject *self, Py_ssize_t index) {\n";
-      if (property->_getter->_has_this ||
-          (property->_has_function && property->_has_function->_has_this)) {
-        out <<
-          "  " << cClassName << " *local_this = NULL;\n"
-          "  if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n"
-          "    return NULL;\n"
-          "  }\n";
-      }
+        "  " << cClassName << " *local_this = NULL;\n"
+        "  if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n"
+        "    return NULL;\n"
+        "  }\n";
+    }
 
 
-      // This is a getitem of a sequence type.  This means we *need* to raise
-      // IndexError if we're out of bounds.
-      out << "  if (index < 0 || index >= (Py_ssize_t)"
-          << len_remap->get_call_str("local_this", pexprs) << ") {\n";
-      out << "    PyErr_SetString(PyExc_IndexError, \"" << ClassName << "." << ielem.get_name() << "[] index out of range\");\n";
-      out << "    return NULL;\n";
-      out << "  }\n";
+    // This is a getitem of a sequence type.  This means we *need* to raise
+    // IndexError if we're out of bounds.
+    out << "  if (index < 0 || index >= (Py_ssize_t)"
+        << len_remap->get_call_str("local_this", pexprs) << ") {\n";
+    out << "    PyErr_SetString(PyExc_IndexError, \"" << ClassName << "." << ielem.get_name() << "[] index out of range\");\n";
+    out << "    return NULL;\n";
+    out << "  }\n";
 
 
-      if (property->_has_function != NULL) {
-        if (property->_has_function->_has_this) {
-          out << "  if (!local_this->" << property->_has_function->_ifunc.get_name() << "(index)) {\n";
-        } else {
-          out << "  if (!" << cClassName << "::" << property->_has_function->_ifunc.get_name() << "(index)) {\n";
-        }
-        out << "    Py_INCREF(Py_None);\n"
-            << "    return Py_None;\n"
-            << "  }\n";
-      }
+    /*if (property->_has_function != NULL) {
+      out << "  if (!local_this->" << property->_has_function->_ifunc.get_name() << "(index)) {\n"
+          << "    Py_INCREF(Py_None);\n"
+          << "    return Py_None;\n"
+          << "  }\n";
+    }*/
 
 
-      std::set<FunctionRemap*> remaps;
+    std::set<FunctionRemap*> remaps;
 
 
-      // Extract only the getters that take one argument.
-      Function::Remaps::iterator it;
-      for (it = property->_getter->_remaps.begin();
-           it != property->_getter->_remaps.end();
-           ++it) {
-        FunctionRemap *remap = *it;
-        int min_num_args = remap->get_min_num_args();
-        int max_num_args = remap->get_max_num_args();
-        if (min_num_args <= 1 && max_num_args >= 1) {
-          remaps.insert(remap);
-        }
+    // Extract only the getters that take one integral argument.
+    Function::Remaps::iterator it;
+    for (it = property->_getter_remaps.begin();
+          it != property->_getter_remaps.end();
+          ++it) {
+      FunctionRemap *remap = *it;
+      int min_num_args = remap->get_min_num_args();
+      int max_num_args = remap->get_max_num_args();
+      if (min_num_args <= 1 && max_num_args >= 1 &&
+          TypeManager::is_integer(remap->_parameters[(size_t)remap->_has_this]._remap->get_new_type())) {
+        remaps.insert(remap);
       }
       }
+    }
 
 
-      string expected_params;
-      write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
-                            AT_no_args, RF_pyobject | RF_err_null, false, true, "index");
+    string expected_params;
+    write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
+                          AT_no_args, RF_pyobject | RF_err_null, false, true, "index");
 
 
-      out << "  if (!_PyErr_OCCURRED()) {\n";
-      out << "    return Dtool_Raise_BadArgumentsError(\n";
-      output_quoted(out, 6, expected_params);
-      out << ");\n"
-             "  }\n"
-             "}\n\n";
-    }
+    out << "  if (!_PyErr_OCCURRED()) {\n";
+    out << "    return Dtool_Raise_BadArgumentsError(\n";
+    output_quoted(out, 6, expected_params);
+    out << ");\n"
+            "  }\n"
+            "}\n\n";
 
 
     // Write out a setitem if this is not a read-only property.
     // Write out a setitem if this is not a read-only property.
-    if (property->_setter != NULL) {
-      out << "static int Dtool_" + ClassName + "_" + ielem.get_name() + "_Setitem(PyObject *self, Py_ssize_t index, PyObject *arg) {\n";
+    if (!property->_setter_remaps.empty()) {
+      out << "static int Dtool_" + ClassName + "_" + ielem.get_name() + "_Sequence_Setitem(PyObject *self, Py_ssize_t index, PyObject *arg) {\n";
       if (property->_has_this) {
       if (property->_has_this) {
         out << "  " << cClassName  << " *local_this = NULL;\n";
         out << "  " << cClassName  << " *local_this = NULL;\n";
         out << "  if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", (void **)&local_this, \""
         out << "  if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", (void **)&local_this, \""
@@ -6595,13 +6546,14 @@ write_getset(ostream &out, Object *obj, Property *property) {
 
 
       // Extract only the setters that take two arguments.
       // Extract only the setters that take two arguments.
       Function::Remaps::iterator it;
       Function::Remaps::iterator it;
-      for (it = property->_setter->_remaps.begin();
-           it != property->_setter->_remaps.end();
+      for (it = property->_setter_remaps.begin();
+           it != property->_setter_remaps.end();
            ++it) {
            ++it) {
         FunctionRemap *remap = *it;
         FunctionRemap *remap = *it;
         int min_num_args = remap->get_min_num_args();
         int min_num_args = remap->get_min_num_args();
         int max_num_args = remap->get_max_num_args();
         int max_num_args = remap->get_max_num_args();
-        if (min_num_args <= 2 && max_num_args >= 2) {
+        if (min_num_args <= 2 && max_num_args >= 2 &&
+            TypeManager::is_integer(remap->_parameters[1]._remap->get_new_type())) {
           remaps.insert(remap);
           remaps.insert(remap);
         }
         }
       }
       }
@@ -6619,8 +6571,192 @@ write_getset(ostream &out, Object *obj, Property *property) {
       out << "  return -1;\n";
       out << "  return -1;\n";
       out << "}\n\n";
       out << "}\n\n";
     }
     }
+  }
+
+
+  // Write the getitem functions.
+  if (ielem.is_mapping()) {
+    out <<
+      "/**\n"
+      " * mapping getitem for property " << ielem.get_scoped_name() << "\n"
+      " */\n"
+      "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Mapping_Getitem(PyObject *self, PyObject *arg) {\n";
+
+    // Before we do the has_function: if this is also a sequence, then we have
+    // to also handle the case here that we were passed an index.
+    if (ielem.is_sequence()) {
+      out <<
+        "#if PY_MAJOR_VERSION >= 3\n"
+        "  if (PyLong_CheckExact(arg)) {\n"
+        "#else\n"
+        "  if (PyLong_CheckExact(arg) || PyInt_CheckExact(arg)) {\n"
+        "#endif\n"
+        "    return Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem(self, PyLongOrInt_AsSize_t(arg));\n"
+        "  }\n\n";
+    }
+
+    if (property->_has_this) {
+      out <<
+        "  " << cClassName << " *local_this = NULL;\n"
+        "  if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n"
+        "    return NULL;\n"
+        "  }\n";
+    }
+
+    if (property->_has_function != NULL) {
+      std::set<FunctionRemap*> remaps;
+      remaps.insert(property->_has_function->_remaps.begin(),
+                    property->_has_function->_remaps.end());
+
+      out << "  {\n";
+      string expected_params;
+      write_function_forset(out, remaps, 1, 1, expected_params, 4, true, true,
+                            AT_single_arg, RF_raise_keyerror | RF_err_null, false, true);
+      out << "  }\n";
+    }
+
+    std::set<FunctionRemap*> remaps;
+    // Extract only the getters that take one argument.  Fish out the ones
+    // already taken by the sequence getter.
+    Function::Remaps::iterator it;
+    for (it = property->_getter_remaps.begin();
+          it != property->_getter_remaps.end();
+          ++it) {
+      FunctionRemap *remap = *it;
+      int min_num_args = remap->get_min_num_args();
+      int max_num_args = remap->get_max_num_args();
+      if (min_num_args <= 1 && max_num_args >= 1 &&
+          (!ielem.is_sequence() || !TypeManager::is_integer(remap->_parameters[(size_t)remap->_has_this]._remap->get_new_type()))) {
+        remaps.insert(remap);
+      }
+    }
+
+    string expected_params;
+    write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
+                          AT_single_arg, RF_pyobject | RF_err_null, false, true);
+
+    out << "  if (!_PyErr_OCCURRED()) {\n";
+    out << "    return Dtool_Raise_BadArgumentsError(\n";
+    output_quoted(out, 6, expected_params);
+    out << ");\n"
+            "  }\n"
+            "  return NULL;\n"
+            "}\n\n";
+
+    // Write out a setitem if this is not a read-only property.
+    if (!property->_setter_remaps.empty()) {
+      out <<
+        "/**\n"
+        " * mapping setitem for property " << ielem.get_scoped_name() << "\n"
+        " */\n"
+        "static int Dtool_" + ClassName + "_" + ielem.get_name() + "_Mapping_Setitem(PyObject *self, PyObject *key, PyObject *value) {\n";
+
+      if (property->_has_this) {
+        out <<
+          "  " << cClassName  << " *local_this = NULL;\n"
+          "  if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", (void **)&local_this, \""
+            << classNameFromCppName(cClassName, false) << "." << ielem.get_name() << "\")) {\n"
+          "    return -1;\n"
+          "  }\n\n";
+      }
+
+      out << "  if (value == (PyObject *)NULL) {\n";
+      if (property->_deleter != NULL) {
+        out << "    PyObject *arg = key;\n";
+        std::set<FunctionRemap*> remaps;
+        remaps.insert(property->_deleter->_remaps.begin(),
+                      property->_deleter->_remaps.end());
 
 
-    // Now write the getter, which returns a special wrapper object.
+        string expected_params;
+        write_function_forset(out, remaps, 1, 1,
+                              expected_params, 4, true, true, AT_single_arg,
+                              RF_int, false, false);
+        out << "    return -1;\n";
+      } else {
+        out << "    Dtool_Raise_TypeError(\"can't delete " << ielem.get_name() << "[] attribute\");\n"
+               "    return -1;\n";
+      }
+      out << "  }\n";
+
+      if (property->_clear_function != NULL) {
+        out << "  if (value == Py_None) {\n"
+            << "    local_this->" << property->_clear_function->_ifunc.get_name() << "(key);\n"
+            << "    return 0;\n"
+            << "  }\n";
+      }
+
+      std::set<FunctionRemap*> remaps;
+      remaps.insert(property->_setter_remaps.begin(),
+                    property->_setter_remaps.end());
+
+      // We have to create an args tuple only to unpack it alter, ugh.
+      out << "  PyObject *args = PyTuple_New(2);\n"
+          << "  PyTuple_SET_ITEM(args, 0, key);\n"
+          << "  PyTuple_SET_ITEM(args, 1, value);\n"
+          << "  Py_INCREF(key);\n"
+          << "  Py_INCREF(value);\n";
+
+      string expected_params;
+      write_function_forset(out, remaps, 2, 2,
+                            expected_params, 2, true, true, AT_varargs,
+                            RF_int | RF_decref_args, false, false);
+
+      out << "  if (!_PyErr_OCCURRED()) {\n";
+      out << "    Dtool_Raise_BadArgumentsError(\n";
+      output_quoted(out, 6, expected_params);
+      out << ");\n";
+      out << "  }\n";
+      out << "  Py_DECREF(args);\n";
+      out << "  return -1;\n";
+      out << "}\n\n";
+    }
+  }
+
+  // Now write the actual getter wrapper.  It will be a different wrapper
+  // depending on whether it's a mapping, sequence, or both.
+  if (ielem.is_mapping() && ielem.is_sequence()) {
+    out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
+    if (property->_has_this) {
+      out << "  nassertr(self != NULL, NULL);\n"
+             "  Py_INCREF(self);\n";
+    } else {
+      out << "  Py_XINCREF(self);\n";
+    }
+    out << "  Dtool_SeqMapWrapper *wrap = PyObject_New(Dtool_SeqMapWrapper, &Dtool_SeqMapWrapper_Type);\n"
+           "  wrap->_seq._base._self = self;\n"
+           "  wrap->_seq._len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
+           "  wrap->_seq._getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n"
+           "  wrap->_map_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getitem;\n";
+    if (!property->_setter_remaps.empty()) {
+      out << "  wrap->_seq._setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Setitem;\n";
+      out << "  wrap->_map_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Setitem;\n";
+    } else {
+      out << "  wrap->_seq._setitem_func = NULL;\n";
+      out << "  wrap->_map_setitem_func = NULL;\n";
+    }
+    out << "  return (PyObject *)wrap;\n"
+            "}\n\n";
+
+  } else if (ielem.is_mapping()) {
+    out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
+    if (property->_has_this) {
+      out << "  nassertr(self != NULL, NULL);\n"
+             "  Py_INCREF(self);\n";
+    } else {
+      out << "  Py_XINCREF(self);\n";
+    }
+    out << "  Dtool_MappingWrapper *wrap = PyObject_New(Dtool_MappingWrapper, &Dtool_MappingWrapper_Type);\n"
+           "  wrap->_base._self = self;\n"
+           "  wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getitem;\n";
+    if (!property->_setter_remaps.empty()) {
+      out << "  wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Setitem;\n";
+    } else {
+      out << "  wrap->_setitem_func = NULL;\n";
+    }
+    out << "  return (PyObject *)wrap;\n"
+           "}\n\n";
+
+  } else if (ielem.is_sequence()) {
     out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
     out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
     if (property->_has_this) {
     if (property->_has_this) {
       out << "  nassertr(self != NULL, NULL);\n"
       out << "  nassertr(self != NULL, NULL);\n"
@@ -6629,21 +6765,21 @@ write_getset(ostream &out, Object *obj, Property *property) {
       out << "  Py_XINCREF(self);\n";
       out << "  Py_XINCREF(self);\n";
     }
     }
     out << "  Dtool_SequenceWrapper *wrap = PyObject_New(Dtool_SequenceWrapper, &Dtool_SequenceWrapper_Type);\n"
     out << "  Dtool_SequenceWrapper *wrap = PyObject_New(Dtool_SequenceWrapper, &Dtool_SequenceWrapper_Type);\n"
-           "  wrap->_base = self;\n"
+           "  wrap->_base._self = self;\n"
            "  wrap->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
            "  wrap->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
-           "  wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Getitem;\n";
-    if (property->_setter != NULL) {
-      out << "  wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Setitem;\n";
+           "  wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n";
+    if (!property->_setter_remaps.empty()) {
+      out << "  wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Setitem;\n";
     } else {
     } else {
       out << "  wrap->_setitem_func = NULL;\n";
       out << "  wrap->_setitem_func = NULL;\n";
     }
     }
     out << "  return (PyObject *)wrap;\n"
     out << "  return (PyObject *)wrap;\n"
-           "}\n\n";
+            "}\n\n";
 
 
-  } else if (property->_getter != NULL) {
+  } else {
     // Write out a regular, unwrapped getter.
     // Write out a regular, unwrapped getter.
     out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
     out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
-    FunctionRemap *remap = property->_getter->_remaps.front();
+    FunctionRemap *remap = property->_getter_remaps.front();
 
 
     if (remap->_has_this) {
     if (remap->_has_this) {
       if (remap->_const_method) {
       if (remap->_const_method) {
@@ -6679,7 +6815,7 @@ write_getset(ostream &out, Object *obj, Property *property) {
     out << "}\n\n";
     out << "}\n\n";
 
 
     // Write out a setter if this is not a read-only property.
     // Write out a setter if this is not a read-only property.
-    if (property->_setter != NULL) {
+    if (!property->_setter_remaps.empty()) {
       out << "static int Dtool_" + ClassName + "_" + ielem.get_name() + "_Setter(PyObject *self, PyObject *arg, void *) {\n";
       out << "static int Dtool_" + ClassName + "_" + ielem.get_name() + "_Setter(PyObject *self, PyObject *arg, void *) {\n";
       if (remap->_has_this) {
       if (remap->_has_this) {
         out << "  " << cClassName  << " *local_this = NULL;\n";
         out << "  " << cClassName  << " *local_this = NULL;\n";
@@ -6698,7 +6834,7 @@ write_getset(ostream &out, Object *obj, Property *property) {
             << "    return 0;\n";
             << "    return 0;\n";
       } else {
       } else {
         out << "    Dtool_Raise_TypeError(\"can't delete " << ielem.get_name() << " attribute\");\n"
         out << "    Dtool_Raise_TypeError(\"can't delete " << ielem.get_name() << " attribute\");\n"
-               "    return -1;\n";
+                "    return -1;\n";
       }
       }
       out << "  }\n";
       out << "  }\n";
 
 
@@ -6717,9 +6853,9 @@ write_getset(ostream &out, Object *obj, Property *property) {
 
 
       // Extract only the setters that take one argument.
       // Extract only the setters that take one argument.
       Function::Remaps::iterator it;
       Function::Remaps::iterator it;
-      for (it = property->_setter->_remaps.begin();
-           it != property->_setter->_remaps.end();
-           ++it) {
+      for (it = property->_setter_remaps.begin();
+            it != property->_setter_remaps.end();
+            ++it) {
         FunctionRemap *remap = *it;
         FunctionRemap *remap = *it;
         int min_num_args = remap->get_min_num_args();
         int min_num_args = remap->get_min_num_args();
         int max_num_args = remap->get_max_num_args();
         int max_num_args = remap->get_max_num_args();
@@ -6831,62 +6967,8 @@ record_object(TypeIndex type_index) {
     ElementIndex element_index = itype.get_element(ei);
     ElementIndex element_index = itype.get_element(ei);
     const InterrogateElement &ielement = idb->get_element(element_index);
     const InterrogateElement &ielement = idb->get_element(element_index);
 
 
-    Property *property = new Property(ielement);
-
-    if (ielement.has_setter()) {
-      FunctionIndex func_index = ielement.get_setter();
-      Function *setter = record_function(itype, func_index);
-      if (is_function_legal(setter)) {
-        property->_setter = setter;
-        property->_has_this |= setter->_has_this;
-      }
-    }
-
-    if (ielement.has_getter()) {
-      FunctionIndex func_index = ielement.get_getter();
-      Function *getter = record_function(itype, func_index);
-      if (is_function_legal(getter)) {
-        property->_getter = getter;
-        property->_has_this |= getter->_has_this;
-      }
-    }
-
-    if (ielement.has_has_function()) {
-      FunctionIndex func_index = ielement.get_has_function();
-      Function *has_function = record_function(itype, func_index);
-      if (is_function_legal(has_function)) {
-        property->_has_function = has_function;
-        property->_has_this |= has_function->_has_this;
-      }
-    }
-
-    if (ielement.has_clear_function()) {
-      FunctionIndex func_index = ielement.get_clear_function();
-      Function *clear_function = record_function(itype, func_index);
-      if (is_function_legal(clear_function)) {
-        property->_clear_function = clear_function;
-        property->_has_this |= clear_function->_has_this;
-      }
-    }
-
-    if (ielement.has_del_function()) {
-      FunctionIndex func_index = ielement.get_del_function();
-      Function *del_function = record_function(itype, func_index);
-      if (is_function_legal(del_function)) {
-        property->_deleter = del_function;
-        property->_has_this |= del_function->_has_this;
-      }
-    }
-
-    if (ielement.is_sequence()) {
-      FunctionIndex func_index = ielement.get_length_function();
-      property->_length_function = record_function(itype, func_index);
-      if (property->_length_function != nullptr) {
-        property->_has_this |= property->_length_function->_has_this;
-      }
-    }
-
-    if (property->_getter != NULL) {
+    Property *property = record_property(itype, itype.get_element(ei));
+    if (property != nullptr) {
       object->_properties.push_back(property);
       object->_properties.push_back(property);
     } else {
     } else {
       // No use exporting a property without a getter.
       // No use exporting a property without a getter.
@@ -6918,6 +7000,96 @@ record_object(TypeIndex type_index) {
   }
   }
   return object;
   return object;
 }
 }
+
+/**
+ *
+ */
+InterfaceMaker::Property *InterfaceMakerPythonNative::
+record_property(const InterrogateType &itype, ElementIndex element_index) {
+  InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
+  const InterrogateElement &ielement = idb->get_element(element_index);
+  if (!ielement.has_getter()) {
+    // A property needs at the very least a getter.
+    return nullptr;
+  }
+
+  Property *property;
+  {
+    FunctionIndex func_index = ielement.get_getter();
+    if (func_index != 0) {
+      const InterrogateFunction &ifunc = idb->get_function(func_index);
+      property = new Property(ielement);
+
+      InterrogateFunction::Instances::const_iterator ii;
+      for (ii = ifunc._instances->begin(); ii != ifunc._instances->end(); ++ii) {
+        CPPInstance *cppfunc = (*ii).second;
+        FunctionRemap *remap =
+          make_function_remap(itype, ifunc, cppfunc, 0);
+
+        if (remap != nullptr && is_remap_legal(remap)) {
+          property->_getter_remaps.push_back(remap);
+          property->_has_this |= remap->_has_this;
+        }
+      }
+    } else {
+      return nullptr;
+    }
+  }
+
+  if (ielement.has_setter()) {
+    FunctionIndex func_index = ielement.get_setter();
+    if (func_index != 0) {
+      const InterrogateFunction &ifunc = idb->get_function(func_index);
+
+      InterrogateFunction::Instances::const_iterator ii;
+      for (ii = ifunc._instances->begin(); ii != ifunc._instances->end(); ++ii) {
+        CPPInstance *cppfunc = (*ii).second;
+        FunctionRemap *remap =
+          make_function_remap(itype, ifunc, cppfunc, 0);
+
+        if (remap != nullptr && is_remap_legal(remap)) {
+          property->_setter_remaps.push_back(remap);
+          property->_has_this |= remap->_has_this;
+        }
+      }
+    }
+  }
+
+  if (ielement.has_has_function()) {
+    FunctionIndex func_index = ielement.get_has_function();
+    Function *has_function = record_function(itype, func_index);
+    if (is_function_legal(has_function)) {
+      property->_has_function = has_function;
+      property->_has_this |= has_function->_has_this;
+    }
+  }
+
+  if (ielement.has_clear_function()) {
+    FunctionIndex func_index = ielement.get_clear_function();
+    Function *clear_function = record_function(itype, func_index);
+    if (is_function_legal(clear_function)) {
+      property->_clear_function = clear_function;
+      property->_has_this |= clear_function->_has_this;
+    }
+  }
+
+  if (ielement.has_del_function()) {
+    FunctionIndex func_index = ielement.get_del_function();
+    Function *del_function = record_function(itype, func_index);
+    if (is_function_legal(del_function)) {
+      property->_deleter = del_function;
+      property->_has_this |= del_function->_has_this;
+    }
+  }
+
+  if (ielement.is_sequence()) {
+    FunctionIndex func_index = ielement.get_length_function();
+    property->_length_function = record_function(itype, func_index);
+  }
+
+  return property;
+}
+
 /**
 /**
  * Walks through the set of functions in the database and generates wrappers
  * Walks through the set of functions in the database and generates wrappers
  * for each function, storing these in the database.  No actual code should be
  * for each function, storing these in the database.  No actual code should be

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

@@ -47,6 +47,7 @@ public:
   virtual bool separate_overloading();
   virtual bool separate_overloading();
 
 
   virtual Object *record_object(TypeIndex type_index);
   virtual Object *record_object(TypeIndex type_index);
+  Property *record_property(const InterrogateType &itype, ElementIndex element_index);
 
 
 protected:
 protected:
   virtual string get_wrapper_prefix();
   virtual string get_wrapper_prefix();
@@ -111,6 +112,9 @@ private:
 
 
     // Decref temporary args object before returning.
     // Decref temporary args object before returning.
     RF_decref_args = 0x1000,
     RF_decref_args = 0x1000,
+
+    // This raises a KeyError on falsey (or -1) return value.
+    RF_raise_keyerror = 0x4000,
   };
   };
 
 
   class SlottedFunctionDef {
   class SlottedFunctionDef {

+ 96 - 41
dtool/src/interrogate/interrogateBuilder.cxx

@@ -1798,34 +1798,46 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
   }
   }
 
 
   string property_name = make_property->get_local_name(&parser);
   string property_name = make_property->get_local_name(&parser);
+  InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
 
 
   // First, check to see if it's already there.
   // First, check to see if it's already there.
+  ElementIndex index = 0;
   PropertiesByName::const_iterator tni =
   PropertiesByName::const_iterator tni =
     _properties_by_name.find(property_name);
     _properties_by_name.find(property_name);
   if (tni != _properties_by_name.end()) {
   if (tni != _properties_by_name.end()) {
-    ElementIndex index = (*tni).second;
-    return index;
+    index = (*tni).second;
+    const InterrogateElement &ielem = idb->get_element(index);
+    if (ielem._make_property == make_property) {
+      // This is the same property.
+      return index;
+    }
+
+    // It is possible to have property definitions with the same name, but
+    // they cannot define conflicting interfaces.
+    if ((ielem.is_sequence() || ielem.is_mapping()) !=
+        (make_property->_type != CPPMakeProperty::T_normal)) {
+      cerr << "Conflicting property definitions for " << property_name << "!\n";
+      return index;
+    }
   }
   }
 
 
   // If we have a length function (ie. this is a sequence property), we should
   // If we have a length function (ie. this is a sequence property), we should
   // find the function that will give us the length.
   // find the function that will give us the length.
   FunctionIndex length_function = 0;
   FunctionIndex length_function = 0;
-  bool is_seq = false;
-
-  CPPFunctionGroup::Instances::const_iterator fi;
-  CPPFunctionGroup *fgroup = make_property->_length_function;
-  if (fgroup != NULL) {
-    is_seq = true;
-
-    for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
-      CPPInstance *function = (*fi);
-      CPPFunctionType *ftype =
-        function->_type->as_function_type();
-      if (ftype != NULL) {
-        length_function = get_function(function, "", struct_type,
-                                       struct_type->get_scope(), 0);
-        if (length_function != 0) {
-          break;
+  if (make_property->_type & CPPMakeProperty::T_sequence) {
+    CPPFunctionGroup::Instances::const_iterator fi;
+    CPPFunctionGroup *fgroup = make_property->_length_function;
+    if (fgroup != NULL) {
+      for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
+        CPPInstance *function = (*fi);
+        CPPFunctionType *ftype =
+          function->_type->as_function_type();
+        if (ftype != NULL) {
+          length_function = get_function(function, "", struct_type,
+                                         struct_type->get_scope(), 0);
+          if (length_function != 0) {
+            break;
+          }
         }
         }
       }
       }
     }
     }
@@ -1840,7 +1852,10 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
   CPPInstance *getter = NULL;
   CPPInstance *getter = NULL;
   CPPType *return_type = NULL;
   CPPType *return_type = NULL;
 
 
-  fgroup = make_property->_get_function;
+  // How many arguments we expect the getter to have.
+  size_t num_args = (size_t)(make_property->_type != CPPMakeProperty::T_normal);
+
+  CPPFunctionGroup *fgroup = make_property->_get_function;
   if (fgroup != NULL) {
   if (fgroup != NULL) {
     CPPFunctionGroup::Instances::const_iterator fi;
     CPPFunctionGroup::Instances::const_iterator fi;
     for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
     for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
@@ -1852,9 +1867,13 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
 
 
       const CPPParameterList::Parameters &params = ftype->_parameters->_parameters;
       const CPPParameterList::Parameters &params = ftype->_parameters->_parameters;
 
 
-      size_t expected_num_args = (size_t)is_seq;
+      size_t expected_num_args = 0;
       size_t index_arg = 0;
       size_t index_arg = 0;
 
 
+      if (make_property->_type != CPPMakeProperty::T_normal) {
+        ++expected_num_args;
+      }
+
       if (!params.empty() && params[0]->get_simple_name() == "self" &&
       if (!params.empty() && params[0]->get_simple_name() == "self" &&
           TypeManager::is_pointer_to_PyObject(params[0]->_type)) {
           TypeManager::is_pointer_to_PyObject(params[0]->_type)) {
         // Taking a PyObject *self argument.
         // Taking a PyObject *self argument.
@@ -1867,7 +1886,8 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
           (params.size() > expected_num_args &&
           (params.size() > expected_num_args &&
            params[expected_num_args]->_initializer != NULL)) {
            params[expected_num_args]->_initializer != NULL)) {
         // If this is a sequence getter, it must take an index argument.
         // If this is a sequence getter, it must take an index argument.
-        if (is_seq && !TypeManager::is_integer(params[index_arg]->_type)) {
+        if (make_property->_type == CPPMakeProperty::T_sequence &&
+            !TypeManager::is_integer(params[index_arg]->_type)) {
           continue;
           continue;
         }
         }
 
 
@@ -1899,13 +1919,14 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
       CPPInstance *function = (*fi);
       CPPInstance *function = (*fi);
       CPPFunctionType *ftype =
       CPPFunctionType *ftype =
         function->_type->as_function_type();
         function->_type->as_function_type();
-      if (ftype != NULL && TypeManager::is_bool(ftype->_return_type)) {
+      if (ftype != nullptr && (TypeManager::is_integer(ftype->_return_type) ||
+                               TypeManager::is_pointer(ftype->_return_type))) {
         hasser = function;
         hasser = function;
         break;
         break;
       }
       }
     }
     }
 
 
-    if (hasser == NULL || return_type == NULL) {
+    if (hasser == nullptr) {
       cerr << "No instance of has-function '"
       cerr << "No instance of has-function '"
            << fgroup->_name << "' is suitable!\n";
            << fgroup->_name << "' is suitable!\n";
       return 0;
       return 0;
@@ -1921,44 +1942,77 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
     for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
     for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
       CPPInstance *function = (*fi);
       CPPInstance *function = (*fi);
       CPPFunctionType *ftype = function->_type->as_function_type();
       CPPFunctionType *ftype = function->_type->as_function_type();
-      if (ftype != NULL && ftype->_parameters->_parameters.size() == (size_t)is_seq) {
+      if (ftype != NULL && ftype->_parameters->_parameters.size() == num_args) {
         deleter = function;
         deleter = function;
         break;
         break;
       }
       }
     }
     }
 
 
-    if (deleter == NULL || return_type == NULL) {
+    if (deleter == nullptr) {
       cerr << "No instance of delete-function '"
       cerr << "No instance of delete-function '"
            << fgroup->_name << "' is suitable!\n";
            << fgroup->_name << "' is suitable!\n";
       return 0;
       return 0;
     }
     }
   }
   }
 
 
-  InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
-  // It isn't here, so we'll have to define it.
-  ElementIndex index = idb->get_next_index();
-  _properties_by_name[property_name] = index;
+  if (index == 0) {
+    // It isn't here, so we'll have to define it.
+    index = idb->get_next_index();
+    _properties_by_name[property_name] = index;
+
+    InterrogateElement iproperty;
+    iproperty._name = make_property->get_simple_name();
+    iproperty._scoped_name = descope(make_property->get_local_name(&parser));
+    idb->add_element(index, iproperty);
+  }
 
 
-  InterrogateElement iproperty;
-  iproperty._name = make_property->get_simple_name();
-  iproperty._scoped_name = descope(make_property->get_local_name(&parser));
+  InterrogateElement &iproperty = idb->update_element(index);
 
 
   if (return_type != NULL) {
   if (return_type != NULL) {
-    iproperty._type = get_type(TypeManager::unwrap_reference(return_type), false);
+    TypeIndex return_index = get_type(TypeManager::unwrap_reference(return_type), false);
+    if (iproperty._type != 0 && iproperty._type != return_index) {
+      cerr << "Property " << property_name << " has inconsistent element type!\n";
+    }
   } else {
   } else {
     iproperty._type = 0;
     iproperty._type = 0;
   }
   }
 
 
-  if (length_function != 0) {
+  if (make_property->_type & CPPMakeProperty::T_sequence) {
     iproperty._flags |= InterrogateElement::F_sequence;
     iproperty._flags |= InterrogateElement::F_sequence;
     iproperty._length_function = length_function;
     iproperty._length_function = length_function;
   }
   }
 
 
-  if (getter != NULL) {
-    iproperty._flags |= InterrogateElement::F_has_getter;
-    iproperty._getter = get_function(getter, "", struct_type,
-                                     struct_type->get_scope(), 0);
-    nassertr(iproperty._getter, 0);
+  if (make_property->_type & CPPMakeProperty::T_mapping) {
+    iproperty._flags |= InterrogateElement::F_mapping;
+  }
+
+  if (make_property->_type == CPPMakeProperty::T_normal) {
+    if (getter != NULL) {
+      iproperty._flags |= InterrogateElement::F_has_getter;
+      iproperty._getter = get_function(getter, "", struct_type,
+                                      struct_type->get_scope(), 0);
+      nassertr(iproperty._getter, 0);
+    }
+  } else {
+    // We could have a mixed sequence/mapping property, so synthesize a
+    // getitem function.  We don't really care what's in here; we just use
+    // this to store the remaps.
+    if (!iproperty.has_getter()) {
+      iproperty._flags |= InterrogateElement::F_has_getter;
+      iproperty._getter = InterrogateDatabase::get_ptr()->get_next_index();
+      InterrogateFunction *ifunction = new InterrogateFunction;
+      ifunction->_instances = new InterrogateFunction::Instances;
+      InterrogateDatabase::get_ptr()->add_function(iproperty._getter, ifunction);
+    }
+
+    // Add our getter to the generated getitem function.
+    string signature = TypeManager::get_function_signature(getter);
+    InterrogateFunction &ifunction =
+      InterrogateDatabase::get_ptr()->update_function(iproperty._getter);
+    if (ifunction._instances == nullptr) {
+      ifunction._instances = new InterrogateFunction::Instances;
+    }
+    ifunction._instances->insert(InterrogateFunction::Instances::value_type(signature, getter));
   }
   }
 
 
   if (hasser != NULL) {
   if (hasser != NULL) {
@@ -2011,7 +2065,6 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
     }
     }
   }
   }
 
 
-  idb->add_element(index, iproperty);
   return index;
   return index;
 }
 }
 
 
@@ -2621,7 +2674,9 @@ define_struct_type(InterrogateType &itype, CPPStructType *cpptype,
 
 
     } else if ((*di)->get_subtype() == CPPDeclaration::ST_make_property) {
     } else if ((*di)->get_subtype() == CPPDeclaration::ST_make_property) {
       ElementIndex element_index = get_make_property((*di)->as_make_property(), cpptype, scope);
       ElementIndex element_index = get_make_property((*di)->as_make_property(), cpptype, scope);
-      itype._elements.push_back(element_index);
+      if (find(itype._elements.begin(), itype._elements.end(), element_index) == itype._elements.end()) {
+        itype._elements.push_back(element_index);
+      }
 
 
     } else if ((*di)->get_subtype() == CPPDeclaration::ST_make_seq) {
     } else if ((*di)->get_subtype() == CPPDeclaration::ST_make_seq) {
       MakeSeqIndex make_seq_index = get_make_seq((*di)->as_make_seq(), cpptype);
       MakeSeqIndex make_seq_index = get_make_seq((*di)->as_make_seq(), cpptype);

+ 10 - 0
dtool/src/interrogatedb/interrogateElement.I

@@ -26,6 +26,7 @@ InterrogateElement(InterrogateModuleDef *def) :
   _clear_function = 0;
   _clear_function = 0;
   _del_function = 0;
   _del_function = 0;
   _length_function = 0;
   _length_function = 0;
+  _make_property = nullptr;
 }
 }
 
 
 /**
 /**
@@ -52,6 +53,7 @@ operator = (const InterrogateElement &copy) {
   _clear_function = copy._clear_function;
   _clear_function = copy._clear_function;
   _del_function = copy._del_function;
   _del_function = copy._del_function;
   _length_function = copy._length_function;
   _length_function = copy._length_function;
+  _make_property = copy._make_property;
 }
 }
 
 
 /**
 /**
@@ -199,6 +201,14 @@ get_length_function() const {
   return _length_function;
   return _length_function;
 }
 }
 
 
+/**
+ *
+ */
+INLINE bool InterrogateElement::
+is_mapping() const {
+  return (_flags & F_mapping) != 0;
+}
+
 
 
 INLINE ostream &
 INLINE ostream &
 operator << (ostream &out, const InterrogateElement &element) {
 operator << (ostream &out, const InterrogateElement &element) {

+ 4 - 0
dtool/src/interrogatedb/interrogateElement.h

@@ -19,6 +19,7 @@
 #include "interrogateComponent.h"
 #include "interrogateComponent.h"
 
 
 class IndexRemapper;
 class IndexRemapper;
+class CPPMakeProperty;
 
 
 /**
 /**
  * An internal representation of a data element, like a data member or a
  * An internal representation of a data element, like a data member or a
@@ -51,6 +52,7 @@ public:
   INLINE FunctionIndex get_del_function() const;
   INLINE FunctionIndex get_del_function() const;
   INLINE bool is_sequence() const;
   INLINE bool is_sequence() const;
   INLINE FunctionIndex get_length_function() const;
   INLINE FunctionIndex get_length_function() const;
+  INLINE bool is_mapping() const;
 
 
   void output(ostream &out) const;
   void output(ostream &out) const;
   void input(istream &in);
   void input(istream &in);
@@ -80,6 +82,8 @@ private:
   FunctionIndex _clear_function;
   FunctionIndex _clear_function;
   FunctionIndex _del_function;
   FunctionIndex _del_function;
 
 
+  CPPMakeProperty *_make_property;
+
   friend class InterrogateBuilder;
   friend class InterrogateBuilder;
 };
 };
 
 

+ 211 - 19
dtool/src/interrogatedb/py_panda.cxx

@@ -322,11 +322,11 @@ PyObject *_Dtool_Raise_BadArgumentsError() {
  * NULL, otherwise Py_None.
  * NULL, otherwise Py_None.
  */
  */
 PyObject *_Dtool_Return_None() {
 PyObject *_Dtool_Return_None() {
-  if (_PyErr_OCCURRED()) {
+  if (UNLIKELY(_PyErr_OCCURRED())) {
     return NULL;
     return NULL;
   }
   }
 #ifndef NDEBUG
 #ifndef NDEBUG
-  if (Notify::ptr()->has_assert_failed()) {
+  if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
     return Dtool_Raise_AssertionError();
     return Dtool_Raise_AssertionError();
   }
   }
 #endif
 #endif
@@ -339,11 +339,11 @@ PyObject *_Dtool_Return_None() {
  * NULL, otherwise the given boolean value as a PyObject *.
  * NULL, otherwise the given boolean value as a PyObject *.
  */
  */
 PyObject *Dtool_Return_Bool(bool value) {
 PyObject *Dtool_Return_Bool(bool value) {
-  if (_PyErr_OCCURRED()) {
+  if (UNLIKELY(_PyErr_OCCURRED())) {
     return NULL;
     return NULL;
   }
   }
 #ifndef NDEBUG
 #ifndef NDEBUG
-  if (Notify::ptr()->has_assert_failed()) {
+  if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
     return Dtool_Raise_AssertionError();
     return Dtool_Raise_AssertionError();
   }
   }
 #endif
 #endif
@@ -358,11 +358,11 @@ PyObject *Dtool_Return_Bool(bool value) {
  * increased.
  * increased.
  */
  */
 PyObject *_Dtool_Return(PyObject *value) {
 PyObject *_Dtool_Return(PyObject *value) {
-  if (_PyErr_OCCURRED()) {
+  if (UNLIKELY(_PyErr_OCCURRED())) {
     return NULL;
     return NULL;
   }
   }
 #ifndef NDEBUG
 #ifndef NDEBUG
-  if (Notify::ptr()->has_assert_failed()) {
+  if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
     return Dtool_Raise_AssertionError();
     return Dtool_Raise_AssertionError();
   }
   }
 #endif
 #endif
@@ -614,6 +614,14 @@ PyObject *Dtool_PyModuleInitHelper(LibraryDef *defs[], const char *modulename) {
       return Dtool_Raise_TypeError("PyType_Ready(Dtool_SequenceWrapper)");
       return Dtool_Raise_TypeError("PyType_Ready(Dtool_SequenceWrapper)");
     }
     }
 
 
+    if (PyType_Ready(&Dtool_MappingWrapper_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MappingWrapper)");
+    }
+
+    if (PyType_Ready(&Dtool_SeqMapWrapper_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_SeqMapWrapper)");
+    }
+
     if (PyType_Ready(&Dtool_StaticProperty_Type) < 0) {
     if (PyType_Ready(&Dtool_StaticProperty_Type) < 0) {
       return Dtool_Raise_TypeError("PyType_Ready(Dtool_StaticProperty_Type)");
       return Dtool_Raise_TypeError("PyType_Ready(Dtool_StaticProperty_Type)");
     }
     }
@@ -1028,34 +1036,79 @@ bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds)
 }
 }
 
 
 /**
 /**
- * This class is returned from properties that require a settable interface,
- * ie. something.children[i] = 3.
+ * These classes are returned from properties that require a subscript
+ * interface, ie. something.children[i] = 3.
  */
  */
-static void Dtool_SequenceWrapper_dealloc(PyObject *self) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+static void Dtool_WrapperBase_dealloc(PyObject *self) {
+  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
   nassertv(wrap);
   nassertv(wrap);
-  Py_XDECREF(wrap->_base);
+  Py_XDECREF(wrap->_self);
 }
 }
 
 
 static Py_ssize_t Dtool_SequenceWrapper_length(PyObject *self) {
 static Py_ssize_t Dtool_SequenceWrapper_length(PyObject *self) {
   Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
   Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
   nassertr(wrap, -1);
   nassertr(wrap, -1);
-  nassertr(wrap->_len_func, -1);
-  return wrap->_len_func(wrap->_base);
+  if (wrap->_len_func != nullptr) {
+    nassertr(wrap->_len_func, -1);
+    return wrap->_len_func(wrap->_base._self);
+  } else {
+    Dtool_Raise_TypeError("property does not support len()");
+    return -1;
+  }
 }
 }
 
 
 static PyObject *Dtool_SequenceWrapper_getitem(PyObject *self, Py_ssize_t index) {
 static PyObject *Dtool_SequenceWrapper_getitem(PyObject *self, Py_ssize_t index) {
   Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
   Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, NULL);
-  nassertr(wrap->_getitem_func, NULL);
-  return wrap->_getitem_func(wrap->_base, index);
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+  return wrap->_getitem_func(wrap->_base._self, index);
 }
 }
 
 
 static int Dtool_SequenceWrapper_setitem(PyObject *self, Py_ssize_t index, PyObject *value) {
 static int Dtool_SequenceWrapper_setitem(PyObject *self, Py_ssize_t index, PyObject *value) {
   Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
   Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
   nassertr(wrap, -1);
   nassertr(wrap, -1);
-  nassertr(wrap->_setitem_func, -1);
-  return wrap->_setitem_func(wrap->_base, index, value);
+  if (wrap->_setitem_func != nullptr) {
+    return wrap->_setitem_func(wrap->_base._self, index, value);
+  } else {
+    Dtool_Raise_TypeError("property does not support item assignment");
+    return -1;
+  }
+}
+
+static PyObject *Dtool_MappingWrapper_getitem(PyObject *self, PyObject *key) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+  return wrap->_getitem_func(wrap->_base._self, key);
+}
+
+static int Dtool_MappingWrapper_setitem(PyObject *self, PyObject *key, PyObject *value) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, -1);
+  if (wrap->_setitem_func != nullptr) {
+    return wrap->_setitem_func(wrap->_base._self, key, value);
+  } else {
+    Dtool_Raise_TypeError("property does not support item assignment");
+    return -1;
+  }
+}
+
+static PyObject *Dtool_SeqMapWrapper_getitem(PyObject *self, PyObject *key) {
+  Dtool_SeqMapWrapper *wrap = (Dtool_SeqMapWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_map_getitem_func, nullptr);
+  return wrap->_map_getitem_func(wrap->_seq._base._self, key);
+}
+
+static int Dtool_SeqMapWrapper_setitem(PyObject *self, PyObject *key, PyObject *value) {
+  Dtool_SeqMapWrapper *wrap = (Dtool_SeqMapWrapper *)self;
+  nassertr(wrap, -1);
+  if (wrap->_map_setitem_func != nullptr) {
+    return wrap->_map_setitem_func(wrap->_seq._base._self, key, value);
+  } else {
+    Dtool_Raise_TypeError("property does not support item assignment");
+    return -1;
+  }
 }
 }
 
 
 static PySequenceMethods Dtool_SequenceWrapper_SequenceMethods = {
 static PySequenceMethods Dtool_SequenceWrapper_SequenceMethods = {
@@ -1071,12 +1124,27 @@ static PySequenceMethods Dtool_SequenceWrapper_SequenceMethods = {
   0, // sq_inplace_repeat
   0, // sq_inplace_repeat
 };
 };
 
 
+static PyMappingMethods Dtool_MappingWrapper_MappingMethods = {
+  0, // mp_length
+  Dtool_MappingWrapper_getitem,
+  Dtool_MappingWrapper_setitem,
+};
+
+static PyMappingMethods Dtool_SeqMapWrapper_MappingMethods = {
+  Dtool_SequenceWrapper_length,
+  Dtool_SeqMapWrapper_getitem,
+  Dtool_SeqMapWrapper_setitem,
+};
+
+/**
+ * This variant defines only a sequence interface.
+ */
 PyTypeObject Dtool_SequenceWrapper_Type = {
 PyTypeObject Dtool_SequenceWrapper_Type = {
   PyVarObject_HEAD_INIT(NULL, 0)
   PyVarObject_HEAD_INIT(NULL, 0)
   "sequence wrapper",
   "sequence wrapper",
   sizeof(Dtool_SequenceWrapper),
   sizeof(Dtool_SequenceWrapper),
   0, // tp_itemsize
   0, // tp_itemsize
-  Dtool_SequenceWrapper_dealloc,
+  Dtool_WrapperBase_dealloc,
   0, // tp_print
   0, // tp_print
   0, // tp_getattr
   0, // tp_getattr
   0, // tp_setattr
   0, // tp_setattr
@@ -1262,4 +1330,128 @@ PyTypeObject Dtool_StaticProperty_Type = {
 #endif
 #endif
 };
 };
 
 
+/**
+ * This variant defines only a mapping interface.
+ */
+PyTypeObject Dtool_MappingWrapper_Type = {
+  PyVarObject_HEAD_INIT(NULL, 0)
+  "mapping wrapper",
+  sizeof(Dtool_MappingWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+#if PY_MAJOR_VERSION >= 3
+  0, // tp_reserved
+#else
+  0, // tp_compare
+#endif
+  0, // tp_repr
+  0, // tp_as_number
+  0, // tp_as_sequence
+  &Dtool_MappingWrapper_MappingMethods,
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  0, // tp_iter
+  0, // tp_iternext
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+#if PY_VERSION_HEX >= 0x02060000
+  0, // tp_version_tag
+#endif
+#if PY_VERSION_HEX >= 0x03040000
+  0, // tp_finalize
+#endif
+};
+
+/**
+ * This variant defines both a sequence and mapping interface.
+ */
+PyTypeObject Dtool_SeqMapWrapper_Type = {
+  PyVarObject_HEAD_INIT(NULL, 0)
+  "sequence/mapping wrapper",
+  sizeof(Dtool_SeqMapWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+#if PY_MAJOR_VERSION >= 3
+  0, // tp_reserved
+#else
+  0, // tp_compare
+#endif
+  0, // tp_repr
+  0, // tp_as_number
+  &Dtool_SequenceWrapper_SequenceMethods,
+  &Dtool_SeqMapWrapper_MappingMethods,
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  0, // tp_iter
+  0, // tp_iternext
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+#if PY_VERSION_HEX >= 0x02060000
+  0, // tp_version_tag
+#endif
+#if PY_VERSION_HEX >= 0x03040000
+  0, // tp_finalize
+#endif
+};
+
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON

+ 27 - 6
dtool/src/interrogatedb/py_panda.h

@@ -320,9 +320,9 @@ EXPCL_INTERROGATEDB bool _Dtool_CheckErrorOccurred();
 #endif
 #endif
 
 
 #ifdef NDEBUG
 #ifdef NDEBUG
-#define Dtool_CheckErrorOccurred() (_PyErr_OCCURRED() != NULL)
+#define Dtool_CheckErrorOccurred() (UNLIKELY(_PyErr_OCCURRED() != NULL))
 #else
 #else
-#define Dtool_CheckErrorOccurred() _Dtool_CheckErrorOccurred()
+#define Dtool_CheckErrorOccurred() (UNLIKELY(_Dtool_CheckErrorOccurred()))
 #endif
 #endif
 
 
 EXPCL_INTERROGATEDB PyObject *Dtool_Raise_AssertionError();
 EXPCL_INTERROGATEDB PyObject *Dtool_Raise_AssertionError();
@@ -339,6 +339,9 @@ EXPCL_INTERROGATEDB PyObject *_Dtool_Raise_BadArgumentsError();
 #define Dtool_Raise_BadArgumentsError(x) Dtool_Raise_TypeError("Arguments must match:\n" x)
 #define Dtool_Raise_BadArgumentsError(x) Dtool_Raise_TypeError("Arguments must match:\n" x)
 #endif
 #endif
 
 
+// These functions are similar to Dtool_WrapValue, except that they also
+// contain code for checking assertions and exceptions when compiling with
+// NDEBUG mode on.
 EXPCL_INTERROGATEDB PyObject *_Dtool_Return_None();
 EXPCL_INTERROGATEDB PyObject *_Dtool_Return_None();
 EXPCL_INTERROGATEDB PyObject *Dtool_Return_Bool(bool value);
 EXPCL_INTERROGATEDB PyObject *Dtool_Return_Bool(bool value);
 EXPCL_INTERROGATEDB PyObject *_Dtool_Return(PyObject *value);
 EXPCL_INTERROGATEDB PyObject *_Dtool_Return(PyObject *value);
@@ -465,18 +468,36 @@ EXPCL_INTERROGATEDB PyObject *
 map_deepcopy_to_copy(PyObject *self, PyObject *args);
 map_deepcopy_to_copy(PyObject *self, PyObject *args);
 
 
 /**
 /**
- * This class is returned from properties that require a settable interface,
- * ie. something.children[i] = 3.
+ * These classes are returned from properties that require a subscript
+ * interface, ie. something.children[i] = 3.
  */
  */
+struct Dtool_WrapperBase {
+  PyObject_HEAD;
+  PyObject *_self;
+};
+
 struct Dtool_SequenceWrapper {
 struct Dtool_SequenceWrapper {
-  PyObject_HEAD
-  PyObject *_base;
+  Dtool_WrapperBase _base;
   lenfunc _len_func;
   lenfunc _len_func;
   ssizeargfunc _getitem_func;
   ssizeargfunc _getitem_func;
   ssizeobjargproc _setitem_func;
   ssizeobjargproc _setitem_func;
 };
 };
 
 
+struct Dtool_MappingWrapper {
+  Dtool_WrapperBase _base;
+  binaryfunc _getitem_func;
+  objobjargproc _setitem_func;
+};
+
+struct Dtool_SeqMapWrapper {
+  Dtool_SequenceWrapper _seq;
+  binaryfunc _map_getitem_func;
+  objobjargproc _map_setitem_func;
+};
+
 EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SequenceWrapper_Type;
 EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SequenceWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SeqMapWrapper_Type;
 EXPCL_INTERROGATEDB extern PyTypeObject Dtool_StaticProperty_Type;
 EXPCL_INTERROGATEDB extern PyTypeObject Dtool_StaticProperty_Type;
 
 
 EXPCL_INTERROGATEDB PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
 EXPCL_INTERROGATEDB PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);

+ 2 - 10
dtool/src/prc/notifyCategory.I

@@ -71,11 +71,7 @@ is_on(NotifySeverity severity) const {
 INLINE bool NotifyCategory::
 INLINE bool NotifyCategory::
 is_spam() const {
 is_spam() const {
   // Instruct the compiler to optimize for the usual case.
   // Instruct the compiler to optimize for the usual case.
-#ifdef __GNUC__
-  return __builtin_expect(is_on(NS_spam), 0);
-#else
-  return is_on(NS_spam);
-#endif
+  return UNLIKELY(is_on(NS_spam));
 }
 }
 
 
 /**
 /**
@@ -84,11 +80,7 @@ is_spam() const {
 INLINE bool NotifyCategory::
 INLINE bool NotifyCategory::
 is_debug() const {
 is_debug() const {
   // Instruct the compiler to optimize for the usual case.
   // Instruct the compiler to optimize for the usual case.
-#ifdef __GNUC__
-  return __builtin_expect(is_on(NS_debug), 0);
-#else
-  return is_on(NS_debug);
-#endif
+  return UNLIKELY(is_on(NS_debug));
 }
 }
 #else
 #else
 /**
 /**

+ 2 - 10
dtool/src/prc/notifyCategoryProxy.I

@@ -70,11 +70,7 @@ template<class GetCategory>
 INLINE bool NotifyCategoryProxy<GetCategory>::
 INLINE bool NotifyCategoryProxy<GetCategory>::
 is_spam() {
 is_spam() {
   // Instruct the compiler to optimize for the usual case.
   // Instruct the compiler to optimize for the usual case.
-#ifdef __GNUC__
-  return __builtin_expect(get_unsafe_ptr()->is_spam(), 0);
-#else
-  return get_unsafe_ptr()->is_spam();
-#endif
+  return UNLIKELY(get_unsafe_ptr()->is_spam());
 }
 }
 #else
 #else
 template<class GetCategory>
 template<class GetCategory>
@@ -92,11 +88,7 @@ template<class GetCategory>
 INLINE bool NotifyCategoryProxy<GetCategory>::
 INLINE bool NotifyCategoryProxy<GetCategory>::
 is_debug() {
 is_debug() {
   // Instruct the compiler to optimize for the usual case.
   // Instruct the compiler to optimize for the usual case.
-#ifdef __GNUC__
-  return __builtin_expect(get_unsafe_ptr()->is_debug(), 0);
-#else
-  return get_unsafe_ptr()->is_debug();
-#endif
+  return UNLIKELY(get_unsafe_ptr()->is_debug());
 }
 }
 #else
 #else
 template<class GetCategory>
 template<class GetCategory>

+ 1 - 6
makepanda/makepanda.py

@@ -3179,7 +3179,6 @@ if (PkgSkip("DIRECT")==0):
     CopyAllHeaders('direct/src/distributed')
     CopyAllHeaders('direct/src/distributed')
     CopyAllHeaders('direct/src/interval')
     CopyAllHeaders('direct/src/interval')
     CopyAllHeaders('direct/src/showbase')
     CopyAllHeaders('direct/src/showbase')
-    CopyAllHeaders('direct/metalibs/direct')
     CopyAllHeaders('direct/src/dcparse')
     CopyAllHeaders('direct/src/dcparse')
 
 
 if (RUNTIME or RTDIST):
 if (RUNTIME or RTDIST):
@@ -5244,10 +5243,6 @@ if (PkgSkip("DIRECT")==0):
 #
 #
 
 
 if (PkgSkip("DIRECT")==0):
 if (PkgSkip("DIRECT")==0):
-  OPTS=['DIR:direct/metalibs/direct', 'BUILDING:DIRECT']
-  TargetAdd('p3direct_direct.obj', opts=OPTS, input='direct.cxx')
-
-  TargetAdd('libp3direct.dll', input='p3direct_direct.obj')
   TargetAdd('libp3direct.dll', input='p3directbase_directbase.obj')
   TargetAdd('libp3direct.dll', input='p3directbase_directbase.obj')
   TargetAdd('libp3direct.dll', input='p3showbase_showBase.obj')
   TargetAdd('libp3direct.dll', input='p3showbase_showBase.obj')
   if GetTarget() == 'darwin':
   if GetTarget() == 'darwin':
@@ -5259,7 +5254,7 @@ if (PkgSkip("DIRECT")==0):
   TargetAdd('libp3direct.dll', input=COMMON_PANDA_LIBS)
   TargetAdd('libp3direct.dll', input=COMMON_PANDA_LIBS)
   TargetAdd('libp3direct.dll', opts=['ADVAPI',  'OPENSSL', 'WINUSER', 'WINGDI'])
   TargetAdd('libp3direct.dll', opts=['ADVAPI',  'OPENSSL', 'WINUSER', 'WINGDI'])
 
 
-  OPTS=['DIR:direct/metalibs/direct', 'PYTHON']
+  OPTS=['PYTHON']
   TargetAdd('direct_module.obj', input='libp3dcparser.in')
   TargetAdd('direct_module.obj', input='libp3dcparser.in')
   TargetAdd('direct_module.obj', input='libp3showbase.in')
   TargetAdd('direct_module.obj', input='libp3showbase.in')
   TargetAdd('direct_module.obj', input='libp3deadrec.in')
   TargetAdd('direct_module.obj', input='libp3deadrec.in')

+ 2 - 0
makepanda/makepandacore.py

@@ -2620,6 +2620,8 @@ def SetupBuildEnvironment(compiler):
             if os.path.isdir(pcbsd_inc):
             if os.path.isdir(pcbsd_inc):
                 SYS_INC_DIRS.append(pcbsd_inc)
                 SYS_INC_DIRS.append(pcbsd_inc)
 
 
+        null.close()
+
         # Print out the search paths
         # Print out the search paths
         if GetVerbose():
         if GetVerbose():
             print("System library search path:")
             print("System library search path:")

+ 28 - 0
panda/src/bullet/bulletTriangleMesh.I

@@ -19,6 +19,34 @@ ptr() const {
   return (btStridingMeshInterface *)&_mesh;
   return (btStridingMeshInterface *)&_mesh;
 }
 }
 
 
+/**
+ * Returns the number of vertices in this triangle mesh.
+ */
+INLINE size_t BulletTriangleMesh::
+get_num_vertices() const {
+  return _vertices.size();
+}
+
+/**
+ * Returns the vertex at the given vertex index.
+ */
+INLINE LPoint3 BulletTriangleMesh::
+get_vertex(size_t index) const {
+  nassertr(index < _vertices.size(), LPoint3::zero());
+  const btVector3 &vertex = _vertices[index];
+  return LPoint3(vertex[0], vertex[1], vertex[2]);
+}
+
+/**
+ * Returns the vertex indices making up the given triangle index.
+ */
+INLINE LVecBase3i BulletTriangleMesh::
+get_triangle(size_t index) const {
+  index *= 3;
+  nassertr(index + 2 < _indices.size(), LVecBase3i::zero());
+  return LVecBase3i(_indices[index], _indices[index + 1], _indices[index + 2]);
+}
+
 /**
 /**
  *
  *
  */
  */

+ 1 - 1
panda/src/bullet/bulletTriangleMesh.cxx

@@ -39,7 +39,7 @@ BulletTriangleMesh()
 /**
 /**
  * Returns the number of triangles in this triangle mesh.
  * Returns the number of triangles in this triangle mesh.
  */
  */
-int BulletTriangleMesh::
+size_t BulletTriangleMesh::
 get_num_triangles() const {
 get_num_triangles() const {
   return _indices.size() / 3;
   return _indices.size() / 3;
 }
 }

+ 11 - 2
panda/src/bullet/bulletTriangleMesh.h

@@ -48,15 +48,24 @@ PUBLISHED:
   void set_welding_distance(PN_stdfloat distance);
   void set_welding_distance(PN_stdfloat distance);
   void preallocate(int num_verts, int num_indices);
   void preallocate(int num_verts, int num_indices);
 
 
-  int get_num_triangles() const;
+  size_t get_num_triangles() const;
   PN_stdfloat get_welding_distance() const;
   PN_stdfloat get_welding_distance() const;
 
 
   virtual void output(ostream &out) const;
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level) const;
   virtual void write(ostream &out, int indent_level) const;
 
 
-  MAKE_PROPERTY(num_triangles, get_num_triangles);
+public:
+  INLINE size_t get_num_vertices() const;
+  INLINE LPoint3 get_vertex(size_t index) const;
+
+  INLINE LVecBase3i get_triangle(size_t index) const;
+
+PUBLISHED:
   MAKE_PROPERTY(welding_distance, get_welding_distance, set_welding_distance);
   MAKE_PROPERTY(welding_distance, get_welding_distance, set_welding_distance);
 
 
+  MAKE_SEQ_PROPERTY(vertices, get_num_vertices, get_vertex);
+  MAKE_SEQ_PROPERTY(triangles, get_num_triangles, get_triangle);
+
 public:
 public:
   INLINE btStridingMeshInterface *ptr() const;
   INLINE btStridingMeshInterface *ptr() const;
 
 

+ 3 - 2
panda/src/chan/animGroup.h

@@ -44,12 +44,13 @@ PUBLISHED:
   AnimGroup *get_child(int n) const;
   AnimGroup *get_child(int n) const;
   MAKE_SEQ(get_children, get_num_children, get_child);
   MAKE_SEQ(get_children, get_num_children, get_child);
 
 
-  MAKE_SEQ_PROPERTY(children, get_num_children, get_child);
-
   AnimGroup *get_child_named(const string &name) const;
   AnimGroup *get_child_named(const string &name) const;
   AnimGroup *find_child(const string &name) const;
   AnimGroup *find_child(const string &name) const;
   void sort_descendants();
   void sort_descendants();
 
 
+  MAKE_SEQ_PROPERTY(children, get_num_children, get_child);
+  MAKE_MAP_PROPERTY(children, get_child_named, get_child_named);
+
 public:
 public:
   virtual TypeHandle get_value_type() const;
   virtual TypeHandle get_value_type() const;
 
 

+ 3 - 1
panda/src/chan/partGroup.h

@@ -70,12 +70,14 @@ PUBLISHED:
   int get_num_children() const;
   int get_num_children() const;
   PartGroup *get_child(int n) const;
   PartGroup *get_child(int n) const;
   MAKE_SEQ(get_children, get_num_children, get_child);
   MAKE_SEQ(get_children, get_num_children, get_child);
-  MAKE_SEQ_PROPERTY(children, get_num_children, get_child);
 
 
   PartGroup *get_child_named(const string &name) const;
   PartGroup *get_child_named(const string &name) const;
   PartGroup *find_child(const string &name) const;
   PartGroup *find_child(const string &name) const;
   void sort_descendants();
   void sort_descendants();
 
 
+  MAKE_SEQ_PROPERTY(children, get_num_children, get_child);
+  MAKE_MAP_PROPERTY(children, get_child_named, get_child_named);
+
   bool apply_freeze(const TransformState *transform);
   bool apply_freeze(const TransformState *transform);
   virtual bool apply_freeze_matrix(const LVecBase3 &pos, const LVecBase3 &hpr, const LVecBase3 &scale);
   virtual bool apply_freeze_matrix(const LVecBase3 &pos, const LVecBase3 &hpr, const LVecBase3 &scale);
   virtual bool apply_freeze_scalar(PN_stdfloat value);
   virtual bool apply_freeze_scalar(PN_stdfloat value);

+ 1 - 1
panda/src/collide/collisionBox.I

@@ -12,7 +12,7 @@
  */
  */
 
 
 /**
 /**
- * Create the Box by giving a Center and distances of of each of the sides of
+ * Create the Box by giving a Center and distances of each of the sides of
  * box from the Center.
  * box from the Center.
  */
  */
 INLINE CollisionBox::
 INLINE CollisionBox::

+ 2 - 2
panda/src/display/graphicsEngine.cxx

@@ -1271,7 +1271,7 @@ set_window_sort(GraphicsOutput *window, int sort) {
  * model begins with the "-" character.
  * model begins with the "-" character.
  */
  */
 void GraphicsEngine::
 void GraphicsEngine::
-cull_and_draw_together(const GraphicsEngine::Windows &wlist,
+cull_and_draw_together(GraphicsEngine::Windows wlist,
                        Thread *current_thread) {
                        Thread *current_thread) {
   PStatTimer timer(_cull_pcollector, current_thread);
   PStatTimer timer(_cull_pcollector, current_thread);
 
 
@@ -1380,7 +1380,7 @@ cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr,
  * drawing.
  * drawing.
  */
  */
 void GraphicsEngine::
 void GraphicsEngine::
-cull_to_bins(const GraphicsEngine::Windows &wlist, Thread *current_thread) {
+cull_to_bins(GraphicsEngine::Windows wlist, Thread *current_thread) {
   PStatTimer timer(_cull_pcollector, current_thread);
   PStatTimer timer(_cull_pcollector, current_thread);
 
 
   _singular_warning_last_frame = _singular_warning_this_frame;
   _singular_warning_last_frame = _singular_warning_this_frame;

+ 2 - 2
panda/src/display/graphicsEngine.h

@@ -142,11 +142,11 @@ private:
 
 
   void set_window_sort(GraphicsOutput *window, int sort);
   void set_window_sort(GraphicsOutput *window, int sort);
 
 
-  void cull_and_draw_together(const Windows &wlist, Thread *current_thread);
+  void cull_and_draw_together(Windows wlist, Thread *current_thread);
   void cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr,
   void cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr,
                               Thread *current_thread);
                               Thread *current_thread);
 
 
-  void cull_to_bins(const Windows &wlist, Thread *current_thread);
+  void cull_to_bins(Windows wlist, Thread *current_thread);
   void cull_to_bins(GraphicsOutput *win, GraphicsStateGuardian *gsg,
   void cull_to_bins(GraphicsOutput *win, GraphicsStateGuardian *gsg,
                     DisplayRegion *dr, SceneSetup *scene_setup,
                     DisplayRegion *dr, SceneSetup *scene_setup,
                     CullResult *cull_result, Thread *current_thread);
                     CullResult *cull_result, Thread *current_thread);

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

@@ -227,14 +227,20 @@ clear_render_textures() {
  * You can specify a bitplane to attach the texture to.  the legal choices
  * You can specify a bitplane to attach the texture to.  the legal choices
  * are:
  * are:
  *
  *
- * * RTP_depth * RTP_depth_stencil * RTP_color * RTP_aux_rgba_0 *
- * RTP_aux_rgba_1 * RTP_aux_rgba_2 * RTP_aux_rgba_3
+ * - RTP_depth
+ * - RTP_depth_stencil
+ * - RTP_color
+ * - RTP_aux_rgba_0
+ * - RTP_aux_rgba_1
+ * - RTP_aux_rgba_2
+ * - RTP_aux_rgba_3
  *
  *
  * If you do not specify a bitplane to attach the texture to, this routine
  * If you do not specify a bitplane to attach the texture to, this routine
  * will use a default based on the texture's format:
  * will use a default based on the texture's format:
  *
  *
- * * F_depth_component attaches to RTP_depth * F_depth_stencil attaches to
- * RTP_depth_stencil * all other formats attach to RTP_color.
+ * - F_depth_component attaches to RTP_depth
+ * - F_depth_stencil attaches to RTP_depth_stencil
+ * - all other formats attach to RTP_color.
  *
  *
  * The texture's format will be changed to match the format of the bitplane to
  * The texture's format will be changed to match the format of the bitplane to
  * which it is attached.  For example, if you pass in an F_rgba texture and
  * which it is attached.  For example, if you pass in an F_rgba texture and
@@ -283,32 +289,41 @@ add_render_texture(Texture *tex, RenderTextureMode mode,
   // bitplane, while we're at it).
   // bitplane, while we're at it).
 
 
   if (plane == RTP_depth) {
   if (plane == RTP_depth) {
-    tex->set_format(Texture::F_depth_component);
+    _fb_properties.setup_depth_texture(tex);
     tex->set_match_framebuffer_format(true);
     tex->set_match_framebuffer_format(true);
+
   } else if (plane == RTP_depth_stencil) {
   } else if (plane == RTP_depth_stencil) {
     tex->set_format(Texture::F_depth_stencil);
     tex->set_format(Texture::F_depth_stencil);
-    tex->set_component_type(Texture::T_unsigned_int_24_8);
+    if (_fb_properties.get_float_depth()) {
+      tex->set_component_type(Texture::T_float);
+    } else {
+      tex->set_component_type(Texture::T_unsigned_int_24_8);
+    }
     tex->set_match_framebuffer_format(true);
     tex->set_match_framebuffer_format(true);
-  } else if ((plane == RTP_color)||
-             (plane == RTP_aux_rgba_0)||
-             (plane == RTP_aux_rgba_1)||
-             (plane == RTP_aux_rgba_2)||
-             (plane == RTP_aux_rgba_3)) {
-    tex->set_format(Texture::F_rgba);
+
+  } else if (plane == RTP_color ||
+             plane == RTP_aux_rgba_0 ||
+             plane == RTP_aux_rgba_1 ||
+             plane == RTP_aux_rgba_2 ||
+             plane == RTP_aux_rgba_3) {
+    _fb_properties.setup_color_texture(tex);
     tex->set_match_framebuffer_format(true);
     tex->set_match_framebuffer_format(true);
-  } else if  ((plane == RTP_aux_hrgba_0)||
-              (plane == RTP_aux_hrgba_1)||
-              (plane == RTP_aux_hrgba_2)||
-              (plane == RTP_aux_hrgba_3)) {
+
+  } else if (plane == RTP_aux_hrgba_0 ||
+             plane == RTP_aux_hrgba_1 ||
+             plane == RTP_aux_hrgba_2 ||
+             plane == RTP_aux_hrgba_3) {
     tex->set_format(Texture::F_rgba16);
     tex->set_format(Texture::F_rgba16);
     tex->set_match_framebuffer_format(true);
     tex->set_match_framebuffer_format(true);
-  } else if ((plane == RTP_aux_float_0)||
-              (plane == RTP_aux_float_1)||
-              (plane == RTP_aux_float_2)||
-              (plane == RTP_aux_float_3)) {
+
+  } else if (plane == RTP_aux_float_0 ||
+             plane == RTP_aux_float_1 ||
+             plane == RTP_aux_float_2 ||
+             plane == RTP_aux_float_3) {
     tex->set_format(Texture::F_rgba32);
     tex->set_format(Texture::F_rgba32);
     tex->set_component_type(Texture::T_float);
     tex->set_component_type(Texture::T_float);
     tex->set_match_framebuffer_format(true);
     tex->set_match_framebuffer_format(true);
+
   } else {
   } else {
     display_cat.error() <<
     display_cat.error() <<
       "add_render_texture: invalid bitplane specified.\n";
       "add_render_texture: invalid bitplane specified.\n";

+ 71 - 3
panda/src/display/graphicsStateGuardian.cxx

@@ -281,6 +281,11 @@ GraphicsStateGuardian(CoordinateSystem internal_coordinate_system,
 
 
   _gamma = 1.0f;
   _gamma = 1.0f;
   _texture_quality_override = Texture::QL_default;
   _texture_quality_override = Texture::QL_default;
+
+  // Give it a unique identifier.  Unlike a pointer, we can guarantee that
+  // this value will never be reused.
+  static size_t next_index = 0;
+  _id = next_index++;
 }
 }
 
 
 /**
 /**
@@ -290,6 +295,19 @@ GraphicsStateGuardian::
 ~GraphicsStateGuardian() {
 ~GraphicsStateGuardian() {
   remove_gsg(this);
   remove_gsg(this);
   GeomMunger::unregister_mungers_for_gsg(this);
   GeomMunger::unregister_mungers_for_gsg(this);
+
+  // Remove the munged states for this GSG.  This requires going through all
+  // states, although destructing a GSG should be rare enough for this not to
+  // matter too much.
+  // Note that if uniquify-states is false, we can't iterate over all the
+  // states, and some GSGs will linger.  Let's hope this isn't a problem.
+  LightReMutexHolder holder(*RenderState::_states_lock);
+  size_t size = RenderState::_states->get_num_entries();
+  for (size_t si = 0; si < size; ++si) {
+    const RenderState *state = RenderState::_states->get_key(si);
+    state->_mungers.remove(_id);
+    state->_munged_states.remove(_id);
+  }
 }
 }
 
 
 /**
 /**
@@ -743,7 +761,7 @@ get_geom_munger(const RenderState *state, Thread *current_thread) {
     // multiple times during a frame.  Also, this might well be the only GSG
     // multiple times during a frame.  Also, this might well be the only GSG
     // in the world anyway.
     // in the world anyway.
     int mi = state->_last_mi;
     int mi = state->_last_mi;
-    if (mi >= 0 && mungers.has_element(mi) && mungers.get_key(mi) == this) {
+    if (mi >= 0 && mi < mungers.get_num_entries() && mungers.get_key(mi) == _id) {
       PT(GeomMunger) munger = mungers.get_data(mi);
       PT(GeomMunger) munger = mungers.get_data(mi);
       if (munger->is_registered()) {
       if (munger->is_registered()) {
         return munger;
         return munger;
@@ -751,7 +769,7 @@ get_geom_munger(const RenderState *state, Thread *current_thread) {
     }
     }
 
 
     // Nope, we have to look it up in the map.
     // Nope, we have to look it up in the map.
-    mi = mungers.find(this);
+    mi = mungers.find(_id);
     if (mi >= 0) {
     if (mi >= 0) {
       PT(GeomMunger) munger = mungers.get_data(mi);
       PT(GeomMunger) munger = mungers.get_data(mi);
       if (munger->is_registered()) {
       if (munger->is_registered()) {
@@ -769,7 +787,7 @@ get_geom_munger(const RenderState *state, Thread *current_thread) {
   nassertr(munger != (GeomMunger *)NULL && munger->is_registered(), munger);
   nassertr(munger != (GeomMunger *)NULL && munger->is_registered(), munger);
   nassertr(munger->is_of_type(StateMunger::get_class_type()), munger);
   nassertr(munger->is_of_type(StateMunger::get_class_type()), munger);
 
 
-  state->_last_mi = mungers.store(this, munger);
+  state->_last_mi = mungers.store(_id, munger);
   return munger;
   return munger;
 }
 }
 
 
@@ -3021,6 +3039,19 @@ determine_target_texture() {
   nassertv(_target_texture->get_num_on_stages() <= max_texture_stages);
   nassertv(_target_texture->get_num_on_stages() <= max_texture_stages);
 }
 }
 
 
+/**
+ * Assigns _target_shader based on the _target_rs.
+ */
+void GraphicsStateGuardian::
+determine_target_shader() {
+  if (_target_rs->_generated_shader != nullptr) {
+    _target_shader = (const ShaderAttrib *)_target_rs->_generated_shader.p();
+  } else {
+    _target_shader = (const ShaderAttrib *)
+      _target_rs->get_attrib_def(ShaderAttrib::get_class_slot());
+  }
+}
+
 /**
 /**
  * Frees some memory that was explicitly allocated within the glgsg.
  * Frees some memory that was explicitly allocated within the glgsg.
  */
  */
@@ -3364,6 +3395,43 @@ make_shadow_buffer(const NodePath &light_np, GraphicsOutputBase *host) {
   return tex;
   return tex;
 }
 }
 
 
+/**
+ * Ensures that an appropriate shader has been generated for the given state.
+ * This is stored in the _generated_shader field on the RenderState.
+ */
+void GraphicsStateGuardian::
+ensure_generated_shader(const RenderState *state) {
+#ifdef HAVE_CG
+  const ShaderAttrib *shader_attrib;
+  state->get_attrib_def(shader_attrib);
+
+  if (shader_attrib->auto_shader()) {
+    if (_shader_generator == nullptr) {
+      if (!_supports_basic_shaders) {
+        return;
+      }
+      _shader_generator = new ShaderGenerator(this);
+    }
+    if (state->_generated_shader == nullptr ||
+        state->_generated_shader_seq != _generated_shader_seq) {
+      GeomVertexAnimationSpec spec;
+
+      // Currently we overload this flag to request vertex animation for the
+      // shader generator.
+      const ShaderAttrib *sattr;
+      state->get_attrib_def(sattr);
+      if (sattr->get_flag(ShaderAttrib::F_hardware_skinning)) {
+        spec.set_hardware(4, true);
+      }
+
+      // Cache the generated ShaderAttrib on the shader state.
+      state->_generated_shader = _shader_generator->synthesize_shader(state, spec);
+      state->_generated_shader_seq = _generated_shader_seq;
+    }
+  }
+#endif
+}
+
 /**
 /**
  * Returns true if the GSG implements the extension identified by the given
  * Returns true if the GSG implements the extension identified by the given
  * string.  This currently is only implemented by the OpenGL back-end.
  * string.  This currently is only implemented by the OpenGL back-end.

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

@@ -426,6 +426,8 @@ public:
   PT(Texture) get_dummy_shadow_map(Texture::TextureType texture_type) const;
   PT(Texture) get_dummy_shadow_map(Texture::TextureType texture_type) const;
   PT(Texture) make_shadow_buffer(const NodePath &light_np, GraphicsOutputBase *host);
   PT(Texture) make_shadow_buffer(const NodePath &light_np, GraphicsOutputBase *host);
 
 
+  virtual void ensure_generated_shader(const RenderState *state);
+
 #ifdef DO_PSTATS
 #ifdef DO_PSTATS
   static void init_frame_pstats();
   static void init_frame_pstats();
 #endif
 #endif
@@ -446,6 +448,7 @@ protected:
   virtual void end_bind_clip_planes();
   virtual void end_bind_clip_planes();
 
 
   void determine_target_texture();
   void determine_target_texture();
+  void determine_target_shader();
 
 
   virtual void free_pointers();
   virtual void free_pointers();
   virtual void close_gsg();
   virtual void close_gsg();

+ 13 - 38
panda/src/display/standardMunger.cxx

@@ -38,7 +38,17 @@ StandardMunger(GraphicsStateGuardianBase *gsg, const RenderState *state,
   _auto_shader(false),
   _auto_shader(false),
   _shader_skinning(false)
   _shader_skinning(false)
 {
 {
-  if (!get_gsg()->get_runtime_color_scale()) {
+  const ShaderAttrib *shader_attrib;
+  state->get_attrib_def(shader_attrib);
+#ifdef HAVE_CG
+  _auto_shader = shader_attrib->auto_shader();
+#endif
+  if (shader_attrib->get_flag(ShaderAttrib::F_hardware_skinning)) {
+    _shader_skinning = true;
+  }
+
+  if (!get_gsg()->get_runtime_color_scale() && !_auto_shader &&
+      shader_attrib->get_shader() == nullptr) {
     // We might need to munge the colors.
     // We might need to munge the colors.
     const ColorAttrib *color_attrib;
     const ColorAttrib *color_attrib;
     const ColorScaleAttrib *color_scale_attrib;
     const ColorScaleAttrib *color_scale_attrib;
@@ -60,6 +70,7 @@ StandardMunger(GraphicsStateGuardianBase *gsg, const RenderState *state,
                      _color[3] * cs[3]);
                      _color[3] * cs[3]);
         }
         }
         _munge_color = true;
         _munge_color = true;
+        _should_munge_state = true;
       }
       }
 
 
     } else if (state->get_attrib(color_scale_attrib) &&
     } else if (state->get_attrib(color_scale_attrib) &&
@@ -74,6 +85,7 @@ StandardMunger(GraphicsStateGuardianBase *gsg, const RenderState *state,
       if ((color_scale_attrib->has_rgb_scale() && !get_gsg()->get_color_scale_via_lighting()) ||
       if ((color_scale_attrib->has_rgb_scale() && !get_gsg()->get_color_scale_via_lighting()) ||
           (color_scale_attrib->has_alpha_scale() && !get_gsg()->get_alpha_scale_via_texture(tex_attrib))) {
           (color_scale_attrib->has_alpha_scale() && !get_gsg()->get_alpha_scale_via_texture(tex_attrib))) {
         _munge_color_scale = true;
         _munge_color_scale = true;
+        _should_munge_state = true;
       }
       }
 
 
       // Known bug: if there is a material on an object that would obscure the
       // Known bug: if there is a material on an object that would obscure the
@@ -82,15 +94,6 @@ StandardMunger(GraphicsStateGuardianBase *gsg, const RenderState *state,
       // effort to detect this contrived situation and handle it correctly.
       // effort to detect this contrived situation and handle it correctly.
     }
     }
   }
   }
-
-  const ShaderAttrib *shader_attrib = (const ShaderAttrib *)
-    state->get_attrib_def(ShaderAttrib::get_class_slot());
-  if (shader_attrib->auto_shader()) {
-    _auto_shader = true;
-  }
-  if (shader_attrib->get_flag(ShaderAttrib::F_hardware_skinning)) {
-    _shader_skinning = true;
-  }
 }
 }
 
 
 /**
 /**
@@ -341,33 +344,5 @@ munge_state_impl(const RenderState *state) {
     munged_state = munged_state->remove_attrib(ColorScaleAttrib::get_class_slot());
     munged_state = munged_state->remove_attrib(ColorScaleAttrib::get_class_slot());
   }
   }
 
 
-#ifdef HAVE_CG
-  if (_auto_shader) {
-    GraphicsStateGuardian *gsg = get_gsg();
-    ShaderGenerator *shader_generator = gsg->get_shader_generator();
-    if (shader_generator == nullptr) {
-      shader_generator = new ShaderGenerator(gsg);
-      gsg->set_shader_generator(shader_generator);
-    }
-    if (munged_state->_generated_shader == nullptr) {
-      // Cache the generated ShaderAttrib on the shader state.
-      GeomVertexAnimationSpec spec;
-
-      // Currently we overload this flag to request vertex animation for the
-      // shader generator.
-      const ShaderAttrib *sattr;
-      munged_state->get_attrib_def(sattr);
-      if (sattr->get_flag(ShaderAttrib::F_hardware_skinning)) {
-        spec.set_hardware(4, true);
-      }
-
-      munged_state->_generated_shader = shader_generator->synthesize_shader(munged_state, spec);
-    }
-    if (munged_state->_generated_shader != nullptr) {
-      munged_state = munged_state->set_attrib(munged_state->_generated_shader);
-    }
-  }
-#endif
-
   return munged_state;
   return munged_state;
 }
 }

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

@@ -3074,7 +3074,7 @@ set_state_and_transform(const RenderState *target,
   }
   }
   _target_rs = target;
   _target_rs = target;
 
 
-  _target_shader = DCAST(ShaderAttrib, _target_rs->get_attrib_def(ShaderAttrib::get_class_slot()));
+  determine_target_shader();
 
 
   int alpha_test_slot = AlphaTestAttrib::get_class_slot();
   int alpha_test_slot = AlphaTestAttrib::get_class_slot();
   if (_target_rs->get_attrib(alpha_test_slot) != _state_rs->get_attrib(alpha_test_slot) ||
   if (_target_rs->get_attrib(alpha_test_slot) != _state_rs->get_attrib(alpha_test_slot) ||
@@ -4438,7 +4438,7 @@ set_texture_blend_mode(int i, const TextureStage *stage) {
     set_texture_stage_state(i, D3DTSS_RESULTARG, D3DTA_CURRENT);
     set_texture_stage_state(i, D3DTSS_RESULTARG, D3DTA_CURRENT);
   }
   }
 
 
-  if (stage->uses_color()) {
+  if (stage->uses_color() || stage->involves_color_scale()) {
     // Set up the constant color for this stage.
     // Set up the constant color for this stage.
 
 
     D3DCOLOR constant_color;
     D3DCOLOR constant_color;

+ 40 - 0
panda/src/event/eventHandler.cxx

@@ -182,6 +182,46 @@ has_hook(const string &event_name) const {
 }
 }
 
 
 
 
+/**
+ * Returns true if there is the hook added on the indicated event name and
+ * function pointer, false otherwise.
+ */
+bool EventHandler::
+has_hook(const string &event_name, EventFunction *function) const {
+  assert(!event_name.empty());
+  Hooks::const_iterator hi;
+  hi = _hooks.find(event_name);
+  if (hi != _hooks.end()) {
+    const Functions& functions = (*hi).second;
+    if (functions.find(function) != functions.end()) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+
+/**
+ * Returns true if there is the hook added on the indicated event name,
+ * function pointer and callback data, false otherwise.
+ */
+bool EventHandler::
+has_hook(const string &event_name, EventCallbackFunction *function, void *data) const {
+  assert(!event_name.empty());
+  CallbackHooks::const_iterator chi;
+  chi = _cbhooks.find(event_name);
+  if (chi != _cbhooks.end()) {
+    const CallbackFunctions& cbfunctions = (*chi).second;
+    if (cbfunctions.find(CallbackFunction(function, data)) != cbfunctions.end()) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+
 /**
 /**
  * Removes the indicated function from the named event hook.  Returns true if
  * Removes the indicated function from the named event hook.  Returns true if
  * the hook was removed, false if it wasn't there in the first place.
  * the hook was removed, false if it wasn't there in the first place.

+ 3 - 0
panda/src/event/eventHandler.h

@@ -55,6 +55,9 @@ public:
   bool add_hook(const string &event_name, EventCallbackFunction *function,
   bool add_hook(const string &event_name, EventCallbackFunction *function,
                 void *data);
                 void *data);
   bool has_hook(const string &event_name) const;
   bool has_hook(const string &event_name) const;
+  bool has_hook(const string &event_name, EventFunction *function) const;
+  bool has_hook(const string &event_name, EventCallbackFunction *function,
+                void *data) const;
   bool remove_hook(const string &event_name, EventFunction *function);
   bool remove_hook(const string &event_name, EventFunction *function);
   bool remove_hook(const string &event_name, EventCallbackFunction *function,
   bool remove_hook(const string &event_name, EventCallbackFunction *function,
                    void *data);
                    void *data);

+ 5 - 7
panda/src/express/pointerToArray.h

@@ -105,9 +105,9 @@ PUBLISHED:
   INLINE void set_element(size_type n, const Element &value);
   INLINE void set_element(size_type n, const Element &value);
   EXTENSION(const Element &__getitem__(size_type n) const);
   EXTENSION(const Element &__getitem__(size_type n) const);
   EXTENSION(void __setitem__(size_type n, const Element &value));
   EXTENSION(void __setitem__(size_type n, const Element &value));
-  INLINE string get_data() const;
-  INLINE void set_data(const string &data);
-  INLINE string get_subdata(size_type n, size_type count) const;
+  EXTENSION(PyObject *get_data() const);
+  EXTENSION(void set_data(PyObject *data));
+  EXTENSION(PyObject *get_subdata(size_type n, size_type count) const);
   INLINE void set_subdata(size_type n, size_type count, const string &data);
   INLINE void set_subdata(size_type n, size_type count, const string &data);
   INLINE int get_ref_count() const;
   INLINE int get_ref_count() const;
   INLINE int get_node_ref_count() const;
   INLINE int get_node_ref_count() const;
@@ -255,14 +255,12 @@ PUBLISHED:
   INLINE ConstPointerToArray(const PointerToArray<Element> &copy);
   INLINE ConstPointerToArray(const PointerToArray<Element> &copy);
   INLINE ConstPointerToArray(const ConstPointerToArray<Element> &copy);
   INLINE ConstPointerToArray(const ConstPointerToArray<Element> &copy);
 
 
-  EXTENSION(ConstPointerToArray(PyObject *self, PyObject *source));
-
   typedef TYPENAME pvector<Element>::size_type size_type;
   typedef TYPENAME pvector<Element>::size_type size_type;
   INLINE size_type size() const;
   INLINE size_type size() const;
   INLINE const Element &get_element(size_type n) const;
   INLINE const Element &get_element(size_type n) const;
   EXTENSION(const Element &__getitem__(size_type n) const);
   EXTENSION(const Element &__getitem__(size_type n) const);
-  INLINE string get_data() const;
-  INLINE string get_subdata(size_type n, size_type count) const;
+  EXTENSION(PyObject *get_data() const);
+  EXTENSION(PyObject *get_subdata(size_type n, size_type count) const);
   INLINE int get_ref_count() const;
   INLINE int get_ref_count() const;
   INLINE int get_node_ref_count() const;
   INLINE int get_node_ref_count() const;
 
 

+ 164 - 87
panda/src/express/pointerToArray_ext.I

@@ -79,93 +79,26 @@ INLINE void Extension<PointerToArray<Element> >::
 __init__(PyObject *self, PyObject *source) {
 __init__(PyObject *self, PyObject *source) {
 #if PY_VERSION_HEX >= 0x02060000
 #if PY_VERSION_HEX >= 0x02060000
   if (PyObject_CheckBuffer(source)) {
   if (PyObject_CheckBuffer(source)) {
-    // User passed a buffer object.
-    Py_buffer view;
-    if (PyObject_GetBuffer(source, &view, PyBUF_CONTIG_RO) == -1) {
-      PyErr_SetString(PyExc_TypeError,
-                      "PointerToArray constructor requires a contiguous buffer");
-      return;
-    }
-
-    if (view.itemsize != 1 && view.itemsize != sizeof(Element)) {
-      PyErr_SetString(PyExc_TypeError,
-                      "buffer.itemsize does not match PointerToArray element size");
-      return;
-    }
-
-    if (view.len % sizeof(Element) != 0) {
-      PyErr_Format(PyExc_ValueError,
-                   "byte buffer is not a multiple of %zu bytes",
-                   sizeof(Element));
-      return;
-    }
-
-    if (view.len > 0) {
-      this->_this->resize(view.len / sizeof(Element));
-      memcpy(this->_this->p(), view.buf, view.len);
-    }
-
-    PyBuffer_Release(&view);
+#else
+  if (PyString_CheckExact(source)) {
+#endif
+    // It's a byte sequence, or any object that exports the buffer protocol.
+    this->set_data(source);
     return;
     return;
   }
   }
-#endif
 
 
-  if (!PySequence_Check(source)) {
+  // Don't allow a unicode object even though it's a sequence.
+  if (!PySequence_Check(source) || PyUnicode_CheckExact(source)) {
     // If passed with a non-sequence, this isn't the right constructor.
     // If passed with a non-sequence, this isn't the right constructor.
     PyErr_SetString(PyExc_TypeError,
     PyErr_SetString(PyExc_TypeError,
                     "PointerToArray constructor requires a sequence or buffer object");
                     "PointerToArray constructor requires a sequence or buffer object");
     return;
     return;
   }
   }
 
 
-  // If we were passed a Python string, then instead of storing it character-
-  // at-a-time, just load the whole string as a data buffer.  Not sure if this
-  // case is still necessary - don't Python strbytes objects export the buffer
-  // protocol, as above?
-#if PY_MAJOR_VERSION >= 3
-  if (PyBytes_Check(source)) {
-    int size = PyBytes_Size(source);
-    if (size % sizeof(Element) != 0) {
-      PyErr_Format(PyExc_ValueError,
-                   "bytes object is not a multiple of %zu bytes",
-                   sizeof(Element));
-      return;
-    }
-
-    int num_elements = size / sizeof(Element);
-    this->_this->insert(this->_this->begin(), num_elements, Element());
-
-    // Hope there aren't any constructors or destructors involved here.
-    if (size != 0) {
-      const char *data = PyBytes_AsString(source);
-      memcpy(this->_this->p(), data, size);
-    }
-    return;
-  }
-#else
-  if (PyString_CheckExact(source)) {
-    int size = PyString_Size(source);
-    if (size % sizeof(Element) != 0) {
-      PyErr_Format(PyExc_ValueError,
-                   "str object is not a multiple of %zu bytes",
-                   sizeof(Element));
-      return;
-    }
-
-    int num_elements = size / sizeof(Element);
-    this->_this->insert(this->_this->begin(), num_elements, Element());
-
-    // Hope there aren't any constructors or destructors involved here.
-    if (size != 0) {
-      const char *data = PyString_AsString(source);
-      memcpy(this->_this->p(), data, size);
-    }
-    return;
-  }
-#endif
-
   // Now construct the internal list by copying the elements one-at-a-time
   // Now construct the internal list by copying the elements one-at-a-time
   // from Python.
   // from Python.
-  PyObject *push_back = PyObject_GetAttrString(self, "push_back");
+  PyObject *dict = ((Dtool_PyInstDef *)self)->_My_Type->_PyType.tp_dict;
+  PyObject *push_back = PyDict_GetItemString(dict, "push_back");
   if (push_back == NULL) {
   if (push_back == NULL) {
     PyErr_BadArgument();
     PyErr_BadArgument();
     return;
     return;
@@ -174,19 +107,20 @@ __init__(PyObject *self, PyObject *source) {
   // We need to initialize the this pointer before we can call push_back.
   // We need to initialize the this pointer before we can call push_back.
   ((Dtool_PyInstDef *)self)->_ptr_to_object = (void *)this->_this;
   ((Dtool_PyInstDef *)self)->_ptr_to_object = (void *)this->_this;
 
 
-  int size = PySequence_Size(source);
-  for (int i = 0; i < size; ++i) {
+  Py_ssize_t size = PySequence_Size(source);
+  this->_this->reserve(size);
+  for (Py_ssize_t i = 0; i < size; ++i) {
     PyObject *item = PySequence_GetItem(source, i);
     PyObject *item = PySequence_GetItem(source, i);
     if (item == NULL) {
     if (item == NULL) {
       return;
       return;
     }
     }
-    PyObject *result = PyObject_CallFunctionObjArgs(push_back, item, NULL);
+    PyObject *result = PyObject_CallFunctionObjArgs(push_back, self, item, NULL);
     Py_DECREF(item);
     Py_DECREF(item);
     if (result == NULL) {
     if (result == NULL) {
       // Unable to add item--probably it wasn't of the appropriate type.
       // Unable to add item--probably it wasn't of the appropriate type.
       PyErr_Print();
       PyErr_Print();
       PyErr_Format(PyExc_TypeError,
       PyErr_Format(PyExc_TypeError,
-                   "Element %d in sequence passed to PointerToArray "
+                   "Element %zd in sequence passed to PointerToArray "
                    "constructor could not be added", i);
                    "constructor could not be added", i);
       return;
       return;
     }
     }
@@ -213,15 +147,110 @@ __setitem__(size_t n, const Element &value) {
 }
 }
 
 
 /**
 /**
- * This special constructor accepts a Python list of elements, or a Python
- * string (or a bytes object, in Python 3).
+ * This returns the entire contents of the vector as a block of raw data in a
+ * string (or bytes object, in Python 3).
+ *
+ * @deprecated use memoryview(pta) or bytearray(pta) instead.
  */
  */
 template<class Element>
 template<class Element>
-INLINE void Extension<ConstPointerToArray<Element> >::
-__init__(PyObject *self, PyObject *source) {
-  PointerToArray<Element> array;
-  invoke_extension(&array).__init__(self, source);
-  *(this->_this) = MOVE(array);
+INLINE PyObject *Extension<PointerToArray<Element> >::
+get_data() const {
+#if PY_MAJOR_VERSION >= 3
+  return PyBytes_FromStringAndSize((char *)this->_this->p(), sizeof(Element) * this->_this->size());
+#else
+  return PyString_FromStringAndSize((char *)this->_this->p(), sizeof(Element) * this->_this->size());
+#endif
+}
+
+/**
+ * This method exists mainly to access the data of the array easily from a
+ * high-level language such as Python.
+ *
+ * This replaces the entire contents of the vector from a block of raw data
+ * in a string (or bytes object, in Python 3).
+ */
+template<class Element>
+INLINE void Extension<PointerToArray<Element> >::
+set_data(PyObject *data) {
+#if PY_VERSION_HEX >= 0x02060000
+  if (PyObject_CheckBuffer(data)) {
+    // User passed a buffer object.
+    Py_buffer view;
+    if (PyObject_GetBuffer(data, &view, PyBUF_CONTIG_RO) == -1) {
+      PyErr_SetString(PyExc_TypeError,
+                      "PointerToArray.set_data() requires a contiguous buffer");
+      return;
+    }
+
+    if (view.itemsize != 1 && view.itemsize != sizeof(Element)) {
+      PyErr_SetString(PyExc_TypeError,
+                      "buffer.itemsize does not match PointerToArray element size");
+      return;
+    }
+
+    if (view.len % sizeof(Element) != 0) {
+      PyErr_Format(PyExc_ValueError,
+                   "byte buffer is not a multiple of %zu bytes",
+                   sizeof(Element));
+      return;
+    }
+
+    if (view.len > 0) {
+      this->_this->resize(view.len / sizeof(Element));
+      memcpy(this->_this->p(), view.buf, view.len);
+    } else {
+      this->_this->clear();
+    }
+
+    PyBuffer_Release(&view);
+  } else {
+    Dtool_Raise_TypeError("PointerToArray.set_data() requires a buffer object");
+  }
+#else
+  // In Python 2.5 we didn't have the new buffer protocol, only str.
+  if (PyString_CheckExact(data)) {
+    int size = PyString_Size(data);
+    if (size % sizeof(Element) != 0) {
+      PyErr_Format(PyExc_ValueError,
+                   "str object is not a multiple of %zu bytes",
+                   sizeof(Element));
+      return;
+    }
+
+    int num_elements = size / sizeof(Element);
+    this->_this->insert(this->_this->begin(), num_elements, Element());
+
+    // Hope there aren't any constructors or destructors involved here.
+    if (size != 0) {
+      const char *ptr = PyString_AsString(data);
+      memcpy(this->_this->p(), ptr, size);
+    } else {
+      this->_this->clear();
+    }
+  } else {
+    Dtool_Raise_TypeError("PointerToArray.set_data() requires a str");
+  }
+#endif
+}
+
+/**
+ * This returns the contents of a portion of the vector--from element (n)
+ * through element (n + count - 1)--as a block of raw data in a string (or
+ * bytes object, in Python 3).
+ *
+ * @deprecated use memoryview(pta) or bytearray(pta) instead.
+ */
+template<class Element>
+INLINE PyObject *Extension<PointerToArray<Element> >::
+get_subdata(size_t n, size_t count) const {
+  n = min(n, this->_this->size());
+  count = max(count, n);
+  count = min(count, this->_this->size() - n);
+#if PY_MAJOR_VERSION >= 3
+  return PyBytes_FromStringAndSize((char *)(this->_this->p() + n), sizeof(Element) * count);
+#else
+  return PyString_FromStringAndSize((char *)(this->_this->p() + n), sizeof(Element) * count);
+#endif
 }
 }
 
 
 /**
 /**
@@ -233,6 +262,42 @@ __getitem__(size_t n) const {
   return (*this->_this)[n];
   return (*this->_this)[n];
 }
 }
 
 
+/**
+ * This returns the entire contents of the vector as a block of raw data in a
+ * string (or bytes object, in Python 3).
+ *
+ * @deprecated use memoryview(pta) or bytearray(pta) instead.
+ */
+template<class Element>
+INLINE PyObject *Extension<ConstPointerToArray<Element> >::
+get_data() const {
+#if PY_MAJOR_VERSION >= 3
+  return PyBytes_FromStringAndSize((char *)this->_this->p(), sizeof(Element) * this->_this->size());
+#else
+  return PyString_FromStringAndSize((char *)this->_this->p(), sizeof(Element) * this->_this->size());
+#endif
+}
+
+/**
+ * This returns the contents of a portion of the vector--from element (n)
+ * through element (n + count - 1)--as a block of raw data in a string (or
+ * bytes object, in Python 3).
+ *
+ * @deprecated use memoryview(pta) or bytearray(pta) instead.
+ */
+template<class Element>
+INLINE PyObject *Extension<ConstPointerToArray<Element> >::
+get_subdata(size_t n, size_t count) const {
+  n = min(n, this->_this->size());
+  count = max(count, n);
+  count = min(count, this->_this->size() - n);
+#if PY_MAJOR_VERSION >= 3
+  return PyBytes_FromStringAndSize((char *)(this->_this->p() + n), sizeof(Element) * count);
+#else
+  return PyString_FromStringAndSize((char *)(this->_this->p() + n), sizeof(Element) * count);
+#endif
+}
+
 /**
 /**
  * This is used to implement the buffer protocol, in order to allow efficient
  * This is used to implement the buffer protocol, in order to allow efficient
  * access to the array data through a Python multiview object.
  * access to the array data through a Python multiview object.
@@ -461,6 +526,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 #endif
 #endif
 }
 }
 
 
+/**
+ * Specialization on __getbuffer__ for LMatrix3f.
+ */
 template<>
 template<>
 INLINE int Extension<ConstPointerToArray<LMatrix3f> >::
 INLINE int Extension<ConstPointerToArray<LMatrix3f> >::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
@@ -488,6 +556,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 #endif
 #endif
 }
 }
 
 
+/**
+ * Specialization on __getbuffer__ for LMatrix3d.
+ */
 template<>
 template<>
 INLINE int Extension<ConstPointerToArray<LMatrix3d> >::
 INLINE int Extension<ConstPointerToArray<LMatrix3d> >::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
@@ -515,6 +586,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 #endif
 #endif
 }
 }
 
 
+/**
+ * Specialization on __getbuffer__ for UnalignedLMatrix4f.
+ */
 template<>
 template<>
 INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4f> >::
 INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4f> >::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
@@ -542,6 +616,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 #endif
 #endif
 }
 }
 
 
+/**
+ * Specialization on __getbuffer__ for UnalignedLMatrix4d.
+ */
 template<>
 template<>
 INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4d> >::
 INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4d> >::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {

+ 29 - 10
panda/src/express/pointerToArray_ext.h

@@ -36,18 +36,29 @@ public:
   INLINE const Element &__getitem__(size_t n) const;
   INLINE const Element &__getitem__(size_t n) const;
   INLINE void __setitem__(size_t n, const Element &value);
   INLINE void __setitem__(size_t n, const Element &value);
 
 
+  INLINE PyObject *get_data() const;
+  INLINE void set_data(PyObject *data);
+  INLINE PyObject *get_subdata(size_t n, size_t count) const;
+
   INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags);
   INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags);
   INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
   INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
 };
 };
 
 
 template<>
 template<>
-  INLINE int Extension<PointerToArray<LMatrix3f> >::__getbuffer__(PyObject *self, Py_buffer *view, int flags);
+INLINE int Extension<PointerToArray<LMatrix3f> >::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags);
+
 template<>
 template<>
-  INLINE int Extension<PointerToArray<LMatrix3d> >::__getbuffer__(PyObject *self, Py_buffer *view, int flags);
+INLINE int Extension<PointerToArray<LMatrix3d> >::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags);
+
 template<>
 template<>
-  INLINE int Extension<PointerToArray<UnalignedLMatrix4f> >::__getbuffer__(PyObject *self, Py_buffer *view, int flags);
+INLINE int Extension<PointerToArray<UnalignedLMatrix4f> >::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags);
+
 template<>
 template<>
-  INLINE int Extension<PointerToArray<UnalignedLMatrix4d> >::__getbuffer__(PyObject *self, Py_buffer *view, int flags);
+INLINE int Extension<PointerToArray<UnalignedLMatrix4d> >::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags);
 
 
 /**
 /**
  * This class defines the extension methods for ConstPointerToArray, which are
  * This class defines the extension methods for ConstPointerToArray, which are
@@ -59,22 +70,30 @@ template<>
 template<class Element>
 template<class Element>
 class Extension<ConstPointerToArray<Element> > : public ExtensionBase<ConstPointerToArray<Element> > {
 class Extension<ConstPointerToArray<Element> > : public ExtensionBase<ConstPointerToArray<Element> > {
 public:
 public:
-  INLINE void __init__(PyObject *self, PyObject *source);
-
   INLINE const Element &__getitem__(size_t n) const;
   INLINE const Element &__getitem__(size_t n) const;
 
 
+  INLINE PyObject *get_data() const;
+  INLINE PyObject *get_subdata(size_t n, size_t count) const;
+
   INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
   INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
   INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
   INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
 };
 };
 
 
 template<>
 template<>
-  INLINE int Extension<ConstPointerToArray<LMatrix3f> >::__getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
+INLINE int Extension<ConstPointerToArray<LMatrix3f> >::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
+
 template<>
 template<>
-  INLINE int Extension<ConstPointerToArray<LMatrix3d> >::__getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
+INLINE int Extension<ConstPointerToArray<LMatrix3d> >::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
+
 template<>
 template<>
-  INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4f> >::__getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
+INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4f> >::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
+
 template<>
 template<>
-  INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4d> >::__getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
+INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4d> >::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
 
 
 #ifdef _MSC_VER
 #ifdef _MSC_VER
 // Ugh... MSVC needs this because they still don't have a decent linker.
 // Ugh... MSVC needs this because they still don't have a decent linker.

+ 6 - 3
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -174,7 +174,7 @@ static const string default_vshader =
   "void main(void) {\n"
   "void main(void) {\n"
   "  gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;\n"
   "  gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;\n"
   "  texcoord = p3d_MultiTexCoord0;\n"
   "  texcoord = p3d_MultiTexCoord0;\n"
-  "  color = p3d_Color;\n"
+  "  color = p3d_Color * p3d_ColorScale;\n"
   "}\n";
   "}\n";
 
 
 static const string default_fshader =
 static const string default_fshader =
@@ -3127,6 +3127,10 @@ reset() {
   }
   }
 #endif
 #endif
 
 
+  // Do we guarantee that we can apply the color scale via a shader?  We set
+  // this false if there is a chance that the fixed-function pipeline is used.
+  _runtime_color_scale = !has_fixed_function_pipeline();
+
 #ifndef OPENGLES
 #ifndef OPENGLES
   if (_gl_shadlang_ver_major >= 4 || has_extension("GL_NV_gpu_program5")) {
   if (_gl_shadlang_ver_major >= 4 || has_extension("GL_NV_gpu_program5")) {
     // gp5fp - OpenGL fragment profile for GeForce 400 Series and up
     // gp5fp - OpenGL fragment profile for GeForce 400 Series and up
@@ -10341,8 +10345,7 @@ set_state_and_transform(const RenderState *target,
   _target_rs = target;
   _target_rs = target;
 
 
 #ifndef OPENGLES_1
 #ifndef OPENGLES_1
-  _target_shader = (const ShaderAttrib *)
-    _target_rs->get_attrib_def(ShaderAttrib::get_class_slot());
+  determine_target_shader();
   _instance_count = _target_shader->get_instance_count();
   _instance_count = _target_shader->get_instance_count();
 
 
   if (_target_shader != _state_shader) {
   if (_target_shader != _state_shader) {

+ 21 - 0
panda/src/glstuff/glTextureContext_src.cxx

@@ -95,6 +95,27 @@ reset_data() {
 #endif
 #endif
 }
 }
 
 
+/**
+ * Returns an implementation-defined handle or pointer that can be used
+ * to interface directly with the underlying API.
+ * Returns 0 if the underlying implementation does not support this.
+ */
+uint64_t CLP(TextureContext)::
+get_native_id() const {
+  return _index;
+}
+
+/**
+ * Similar to get_native_id, but some implementations use a separate
+ * identifier for the buffer object associated with buffer textures.
+ * Returns 0 if the underlying implementation does not support this, or
+ * if this is not a buffer texture.
+ */
+uint64_t CLP(TextureContext)::
+get_native_buffer_id() const {
+  return _buffer;
+}
+
 /**
 /**
  *
  *
  */
  */

+ 3 - 0
panda/src/glstuff/glTextureContext_src.h

@@ -33,6 +33,9 @@ public:
   virtual void evict_lru();
   virtual void evict_lru();
   void reset_data();
   void reset_data();
 
 
+  virtual uint64_t get_native_id() const;
+  virtual uint64_t get_native_buffer_id() const;
+
 #ifndef OPENGLES
 #ifndef OPENGLES
   void make_handle_resident();
   void make_handle_resident();
   GLuint64 get_handle();
   GLuint64 get_handle();

+ 3 - 0
panda/src/gobj/geomVertexFormat.h

@@ -125,6 +125,9 @@ PUBLISHED:
   MAKE_SEQ_PROPERTY(points, get_num_points, get_point);
   MAKE_SEQ_PROPERTY(points, get_num_points, get_point);
   MAKE_SEQ_PROPERTY(vectors, get_num_vectors, get_vector);
   MAKE_SEQ_PROPERTY(vectors, get_num_vectors, get_vector);
 
 
+  // We also define this as a mapping interface, for lookups by name.
+  MAKE_MAP_PROPERTY(columns, has_column, get_column);
+
   void output(ostream &out) const;
   void output(ostream &out) const;
   void write(ostream &out, int indent_level = 0) const;
   void write(ostream &out, int indent_level = 0) const;
   void write_with_data(ostream &out, int indent_level,
   void write_with_data(ostream &out, int indent_level,

+ 2 - 2
panda/src/gobj/internalName_ext.cxx

@@ -26,7 +26,7 @@ make(PyUnicodeObject *str) {
   if (!PyUnicode_CHECK_INTERNED(str)) {
   if (!PyUnicode_CHECK_INTERNED(str)) {
     // Not an interned string; don't bother.
     // Not an interned string; don't bother.
     Py_ssize_t len = 0;
     Py_ssize_t len = 0;
-    char *c_str = PyUnicode_AsUTF8AndSize((PyObject *)str, &len);
+    const char *c_str = PyUnicode_AsUTF8AndSize((PyObject *)str, &len);
     if (c_str == NULL) {
     if (c_str == NULL) {
       return NULL;
       return NULL;
     }
     }
@@ -43,7 +43,7 @@ make(PyUnicodeObject *str) {
 
 
   } else {
   } else {
     Py_ssize_t len = 0;
     Py_ssize_t len = 0;
-    char *c_str = PyUnicode_AsUTF8AndSize((PyObject *)str, &len);
+    const char *c_str = PyUnicode_AsUTF8AndSize((PyObject *)str, &len);
     string name(c_str, len);
     string name(c_str, len);
 
 
 #else
 #else

+ 2 - 0
panda/src/gobj/texture.h

@@ -539,6 +539,8 @@ PUBLISHED:
   void set_aux_data(const string &key, TypedReferenceCount *aux_data);
   void set_aux_data(const string &key, TypedReferenceCount *aux_data);
   void clear_aux_data(const string &key);
   void clear_aux_data(const string &key);
   TypedReferenceCount *get_aux_data(const string &key) const;
   TypedReferenceCount *get_aux_data(const string &key) const;
+  MAKE_MAP_PROPERTY(aux_data, get_aux_data, get_aux_data,
+                    set_aux_data, clear_aux_data);
 
 
   INLINE static void set_textures_power_2(AutoTextureScale scale);
   INLINE static void set_textures_power_2(AutoTextureScale scale);
   INLINE static AutoTextureScale get_textures_power_2();
   INLINE static AutoTextureScale get_textures_power_2();

+ 21 - 0
panda/src/gobj/textureContext.cxx

@@ -15,6 +15,27 @@
 
 
 TypeHandle TextureContext::_type_handle;
 TypeHandle TextureContext::_type_handle;
 
 
+/**
+ * Returns an implementation-defined handle or pointer that can be used
+ * to interface directly with the underlying API.
+ * Returns 0 if the underlying implementation does not support this.
+ */
+uint64_t TextureContext::
+get_native_id() const {
+  return 0;
+}
+
+/**
+ * Similar to get_native_id, but some implementations use a separate
+ * identifier for the buffer object associated with buffer textures.
+ * Returns 0 if the underlying implementation does not support this, or
+ * if this is not a buffer texture.
+ */
+uint64_t TextureContext::
+get_native_buffer_id() const {
+  return 0;
+}
+
 /**
 /**
  *
  *
  */
  */

+ 2 - 0
panda/src/gobj/textureContext.h

@@ -37,6 +37,8 @@ public:
 PUBLISHED:
 PUBLISHED:
   INLINE Texture *get_texture() const;
   INLINE Texture *get_texture() const;
   INLINE int get_view() const;
   INLINE int get_view() const;
+  virtual uint64_t get_native_id() const;
+  virtual uint64_t get_native_buffer_id() const;
 
 
   INLINE bool was_modified() const;
   INLINE bool was_modified() const;
   INLINE bool was_properties_modified() const;
   INLINE bool was_properties_modified() const;

+ 61 - 15
panda/src/gobj/textureStage.I

@@ -15,7 +15,7 @@
  * Initialize the texture stage from other
  * Initialize the texture stage from other
  */
  */
 INLINE TextureStage::
 INLINE TextureStage::
-TextureStage(TextureStage &copy) {
+TextureStage(const TextureStage &copy) {
   (*this) = copy;
   (*this) = copy;
 }
 }
 
 
@@ -52,6 +52,10 @@ set_sort(int sort) {
   // Update the global flag to indicate that all TextureAttribs in the world
   // Update the global flag to indicate that all TextureAttribs in the world
   // must now re-sort their lists.
   // must now re-sort their lists.
   _sort_seq++;
   _sort_seq++;
+
+  if (_used_by_auto_shader) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+  }
 }
 }
 
 
 /**
 /**
@@ -80,6 +84,10 @@ set_priority(int priority) {
   // Update the global flag to indicate that all TextureAttribs in the world
   // Update the global flag to indicate that all TextureAttribs in the world
   // must now re-sort their lists.
   // must now re-sort their lists.
   _sort_seq++;
   _sort_seq++;
+
+  if (_used_by_auto_shader) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+  }
 }
 }
 
 
 /**
 /**
@@ -99,7 +107,13 @@ get_priority() const {
  */
  */
 INLINE void TextureStage::
 INLINE void TextureStage::
 set_texcoord_name(InternalName *name) {
 set_texcoord_name(InternalName *name) {
-  _texcoord_name = name;
+  if (name != _texcoord_name) {
+    _texcoord_name = name;
+
+    if (_used_by_auto_shader) {
+      GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+    }
+  }
 }
 }
 
 
 /**
 /**
@@ -108,7 +122,7 @@ set_texcoord_name(InternalName *name) {
  */
  */
 INLINE void TextureStage::
 INLINE void TextureStage::
 set_texcoord_name(const string &name) {
 set_texcoord_name(const string &name) {
-  _texcoord_name = InternalName::get_texcoord_name(name);
+  set_texcoord_name(InternalName::get_texcoord_name(name));
 }
 }
 
 
 /**
 /**
@@ -150,13 +164,16 @@ get_binormal_name() const {
  */
  */
 INLINE void TextureStage::
 INLINE void TextureStage::
 set_mode(TextureStage::Mode mode) {
 set_mode(TextureStage::Mode mode) {
-  _mode = mode;
+  if (mode != _mode) {
+    _mode = mode;
 
 
-  if (_mode != M_combine) {
-    _num_combine_rgb_operands = 0;
-    _num_combine_alpha_operands = 0;
+    if (_mode != M_combine) {
+      _num_combine_rgb_operands = 0;
+      _num_combine_alpha_operands = 0;
+    }
+
+    update_color_flags();
   }
   }
-  update_color_flags();
 }
 }
 
 
 /**
 /**
@@ -202,8 +219,14 @@ get_color() const {
  */
  */
 INLINE void TextureStage::
 INLINE void TextureStage::
 set_rgb_scale(int rgb_scale) {
 set_rgb_scale(int rgb_scale) {
-  nassertv(rgb_scale == 1 || rgb_scale == 2 || rgb_scale == 4);
-  _rgb_scale = rgb_scale;
+  if (rgb_scale != _rgb_scale) {
+    nassertv(rgb_scale == 1 || rgb_scale == 2 || rgb_scale == 4);
+    _rgb_scale = rgb_scale;
+
+    if (_used_by_auto_shader) {
+      GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+    }
+  }
 }
 }
 
 
 /**
 /**
@@ -222,8 +245,14 @@ get_rgb_scale() const {
  */
  */
 INLINE void TextureStage::
 INLINE void TextureStage::
 set_alpha_scale(int alpha_scale) {
 set_alpha_scale(int alpha_scale) {
-  nassertv(alpha_scale == 1 || alpha_scale == 2 || alpha_scale == 4);
-  _alpha_scale = alpha_scale;
+  if (alpha_scale != _alpha_scale) {
+    nassertv(alpha_scale == 1 || alpha_scale == 2 || alpha_scale == 4);
+    _alpha_scale = alpha_scale;
+
+    if (_used_by_auto_shader) {
+      GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+    }
+  }
 }
 }
 
 
 /**
 /**
@@ -247,7 +276,13 @@ get_alpha_scale() const {
  */
  */
 INLINE void TextureStage::
 INLINE void TextureStage::
 set_saved_result(bool saved_result) {
 set_saved_result(bool saved_result) {
-  _saved_result = saved_result;
+  if (saved_result != _saved_result) {
+    _saved_result = saved_result;
+
+    if (_used_by_auto_shader) {
+      GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+    }
+  }
 }
 }
 
 
 /**
 /**
@@ -641,6 +676,14 @@ get_sort_seq() {
   return _sort_seq;
   return _sort_seq;
 }
 }
 
 
+/**
+ * Marks this TextureStage as having been used by the auto shader.
+ */
+INLINE void TextureStage::
+mark_used_by_auto_shader() const {
+  _used_by_auto_shader = true;
+}
+
 /**
 /**
  * Updates _uses_color, _involves_color_scale, _uses_primary_color and
  * Updates _uses_color, _involves_color_scale, _uses_primary_color and
  * _uses_last_saved_result appropriately.
  * _uses_last_saved_result appropriately.
@@ -658,8 +701,7 @@ update_color_flags() {
        _combine_alpha_source2 == CS_constant_color_scale)));
        _combine_alpha_source2 == CS_constant_color_scale)));
 
 
   _uses_color =
   _uses_color =
-    (_involves_color_scale ||
-     _mode == M_blend ||
+    (_mode == M_blend ||
      (_mode == M_combine &&
      (_mode == M_combine &&
       (_combine_rgb_source0 == CS_constant ||
       (_combine_rgb_source0 == CS_constant ||
        _combine_rgb_source1 == CS_constant ||
        _combine_rgb_source1 == CS_constant ||
@@ -685,6 +727,10 @@ update_color_flags() {
        _combine_alpha_source0 == CS_last_saved_result ||
        _combine_alpha_source0 == CS_last_saved_result ||
        _combine_alpha_source1 == CS_last_saved_result ||
        _combine_alpha_source1 == CS_last_saved_result ||
        _combine_alpha_source2 == CS_last_saved_result));
        _combine_alpha_source2 == CS_last_saved_result));
+
+  if (_used_by_auto_shader) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+  }
 }
 }
 
 
 INLINE ostream &
 INLINE ostream &

+ 3 - 1
panda/src/gobj/textureStage.cxx

@@ -25,7 +25,7 @@ TypeHandle TextureStage::_type_handle;
  * Initialize the texture stage at construction
  * Initialize the texture stage at construction
  */
  */
 TextureStage::
 TextureStage::
-TextureStage(const string &name) {
+TextureStage(const string &name) : _used_by_auto_shader(false) {
   _name = name;
   _name = name;
   _sort = 0;
   _sort = 0;
   _priority = 0;
   _priority = 0;
@@ -90,6 +90,8 @@ operator = (const TextureStage &other) {
 
 
   _uses_color = other._uses_color;
   _uses_color = other._uses_color;
   _involves_color_scale = other._involves_color_scale;
   _involves_color_scale = other._involves_color_scale;
+
+  _used_by_auto_shader = false;
 }
 }
 
 
 /**
 /**

+ 6 - 1
panda/src/gobj/textureStage.h

@@ -21,6 +21,7 @@
 #include "typedWritableReferenceCount.h"
 #include "typedWritableReferenceCount.h"
 #include "updateSeq.h"
 #include "updateSeq.h"
 #include "luse.h"
 #include "luse.h"
+#include "graphicsStateGuardianBase.h"
 
 
 class FactoryParams;
 class FactoryParams;
 
 
@@ -34,7 +35,7 @@ class FactoryParams;
 class EXPCL_PANDA_GOBJ TextureStage : public TypedWritableReferenceCount {
 class EXPCL_PANDA_GOBJ TextureStage : public TypedWritableReferenceCount {
 PUBLISHED:
 PUBLISHED:
   explicit TextureStage(const string &name);
   explicit TextureStage(const string &name);
-  INLINE TextureStage(TextureStage &copy);
+  INLINE TextureStage(const TextureStage &copy);
   void operator = (const TextureStage &copy);
   void operator = (const TextureStage &copy);
 
 
   virtual ~TextureStage();
   virtual ~TextureStage();
@@ -206,6 +207,8 @@ PUBLISHED:
 public:
 public:
   INLINE static UpdateSeq get_sort_seq();
   INLINE static UpdateSeq get_sort_seq();
 
 
+  INLINE void mark_used_by_auto_shader() const;
+
 private:
 private:
   INLINE void update_color_flags();
   INLINE void update_color_flags();
 
 
@@ -249,6 +252,8 @@ private:
   static PT(TextureStage) _default_stage;
   static PT(TextureStage) _default_stage;
   static UpdateSeq _sort_seq;
   static UpdateSeq _sort_seq;
 
 
+  mutable bool _used_by_auto_shader;
+
 public:
 public:
   // Datagram stuff
   // Datagram stuff
   static void register_with_read_factory();
   static void register_with_read_factory();

+ 11 - 0
panda/src/gobj/transformBlend.I

@@ -142,6 +142,17 @@ get_weight(size_t n) const {
   return _entries[n]._weight;
   return _entries[n]._weight;
 }
 }
 
 
+/**
+ * Removes the nth transform stored in the blend object.
+ */
+INLINE void TransformBlend::
+remove_transform(size_t n) {
+  nassertv(n < _entries.size());
+  _entries.erase(_entries.begin() + n);
+  Thread *current_thread = Thread::get_current_thread();
+  clear_result(current_thread);
+}
+
 /**
 /**
  * Replaces the nth transform stored in the blend object.
  * Replaces the nth transform stored in the blend object.
  */
  */

+ 6 - 0
panda/src/gobj/transformBlend.h

@@ -62,9 +62,15 @@ PUBLISHED:
   INLINE const VertexTransform *get_transform(size_t n) const;
   INLINE const VertexTransform *get_transform(size_t n) const;
   MAKE_SEQ(get_transforms, get_num_transforms, get_transform);
   MAKE_SEQ(get_transforms, get_num_transforms, get_transform);
   INLINE PN_stdfloat get_weight(size_t n) const;
   INLINE PN_stdfloat get_weight(size_t n) const;
+  INLINE void remove_transform(size_t n);
   INLINE void set_transform(size_t n, const VertexTransform *transform);
   INLINE void set_transform(size_t n, const VertexTransform *transform);
   INLINE void set_weight(size_t n, PN_stdfloat weight);
   INLINE void set_weight(size_t n, PN_stdfloat weight);
 
 
+  MAKE_SEQ_PROPERTY(transforms, get_num_transforms, get_transform,
+                    set_transform, remove_transform);
+  MAKE_SEQ_PROPERTY(weights, get_num_transforms, get_weight, set_weight);
+  MAKE_MAP_PROPERTY(weights, has_transform, get_weight);
+
   INLINE void update_blend(Thread *current_thread) const;
   INLINE void update_blend(Thread *current_thread) const;
 
 
   INLINE void get_blend(LMatrix4 &result, Thread *current_thread) const;
   INLINE void get_blend(LMatrix4 &result, Thread *current_thread) const;

+ 1 - 0
panda/src/gsgbase/graphicsStateGuardianBase.cxx

@@ -16,6 +16,7 @@
 #include <algorithm>
 #include <algorithm>
 
 
 AtomicAdjust::Pointer GraphicsStateGuardianBase::_gsg_list;
 AtomicAdjust::Pointer GraphicsStateGuardianBase::_gsg_list;
+UpdateSeq GraphicsStateGuardianBase::_generated_shader_seq;
 TypeHandle GraphicsStateGuardianBase::_type_handle;
 TypeHandle GraphicsStateGuardianBase::_type_handle;
 
 
 /**
 /**

+ 13 - 0
panda/src/gsgbase/graphicsStateGuardianBase.h

@@ -223,6 +223,14 @@ public:
   virtual void bind_light(Spotlight *light_obj, const NodePath &light,
   virtual void bind_light(Spotlight *light_obj, const NodePath &light,
                           int light_id) { }
                           int light_id) { }
 
 
+  virtual void ensure_generated_shader(const RenderState *state)=0;
+
+  static void mark_rehash_generated_shaders() {
+#ifdef HAVE_CG
+    ++_generated_shader_seq;
+#endif
+  }
+
 PUBLISHED:
 PUBLISHED:
   static GraphicsStateGuardianBase *get_default_gsg();
   static GraphicsStateGuardianBase *get_default_gsg();
   static void set_default_gsg(GraphicsStateGuardianBase *default_gsg);
   static void set_default_gsg(GraphicsStateGuardianBase *default_gsg);
@@ -235,6 +243,8 @@ public:
   static void add_gsg(GraphicsStateGuardianBase *gsg);
   static void add_gsg(GraphicsStateGuardianBase *gsg);
   static void remove_gsg(GraphicsStateGuardianBase *gsg);
   static void remove_gsg(GraphicsStateGuardianBase *gsg);
 
 
+  size_t _id;
+
 private:
 private:
   struct GSGList {
   struct GSGList {
     LightMutex _lock;
     LightMutex _lock;
@@ -245,6 +255,9 @@ private:
   };
   };
   static AtomicAdjust::Pointer _gsg_list;
   static AtomicAdjust::Pointer _gsg_list;
 
 
+protected:
+  static UpdateSeq _generated_shader_seq;
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;

+ 21 - 0
panda/src/linmath/lvecBase4_src.I

@@ -903,6 +903,14 @@ FLOATNAME(UnalignedLVecBase4)(const FLOATNAME(LVecBase4) &copy) {
   set(copy[0], copy[1], copy[2], copy[3]);
   set(copy[0], copy[1], copy[2], copy[3]);
 }
 }
 
 
+/**
+ *
+ */
+INLINE_LINMATH FLOATNAME(UnalignedLVecBase4)::
+FLOATNAME(UnalignedLVecBase4)(FLOATTYPE fill_value) {
+  fill(fill_value);
+}
+
 /**
 /**
  *
  *
  */
  */
@@ -912,6 +920,19 @@ FLOATNAME(UnalignedLVecBase4)(FLOATTYPE x, FLOATTYPE y, FLOATTYPE z, FLOATTYPE w
   set(x, y, z, w);
   set(x, y, z, w);
 }
 }
 
 
+/**
+ * Sets each element of the vector to the indicated fill_value.  This is
+ * particularly useful for initializing to zero.
+ */
+INLINE_LINMATH void FLOATNAME(UnalignedLVecBase4)::
+fill(FLOATTYPE fill_value) {
+  TAU_PROFILE("void UnalignedLVecBase4::fill()", " ", TAU_USER);
+  _v(0) = fill_value;
+  _v(1) = fill_value;
+  _v(2) = fill_value;
+  _v(3) = fill_value;
+}
+
 /**
 /**
  *
  *
  */
  */

+ 2 - 0
panda/src/linmath/lvecBase4_src.h

@@ -230,8 +230,10 @@ PUBLISHED:
 
 
   INLINE_LINMATH FLOATNAME(UnalignedLVecBase4)() DEFAULT_CTOR;
   INLINE_LINMATH FLOATNAME(UnalignedLVecBase4)() DEFAULT_CTOR;
   INLINE_LINMATH FLOATNAME(UnalignedLVecBase4)(const FLOATNAME(LVecBase4) &copy);
   INLINE_LINMATH FLOATNAME(UnalignedLVecBase4)(const FLOATNAME(LVecBase4) &copy);
+  INLINE_LINMATH FLOATNAME(UnalignedLVecBase4)(FLOATTYPE fill_value);
   INLINE_LINMATH FLOATNAME(UnalignedLVecBase4)(FLOATTYPE x, FLOATTYPE y, FLOATTYPE z, FLOATTYPE w);
   INLINE_LINMATH FLOATNAME(UnalignedLVecBase4)(FLOATTYPE x, FLOATTYPE y, FLOATTYPE z, FLOATTYPE w);
 
 
+  INLINE_LINMATH void fill(FLOATTYPE fill_value);
   INLINE_LINMATH void set(FLOATTYPE x, FLOATTYPE y, FLOATTYPE z, FLOATTYPE w);
   INLINE_LINMATH void set(FLOATTYPE x, FLOATTYPE y, FLOATTYPE z, FLOATTYPE w);
 
 
   INLINE_LINMATH FLOATTYPE operator [](int i) const;
   INLINE_LINMATH FLOATTYPE operator [](int i) const;

+ 4 - 0
panda/src/pgraph/camera.h

@@ -91,12 +91,16 @@ PUBLISHED:
   void clear_tag_states();
   void clear_tag_states();
   bool has_tag_state(const string &tag_state) const;
   bool has_tag_state(const string &tag_state) const;
   CPT(RenderState) get_tag_state(const string &tag_state) const;
   CPT(RenderState) get_tag_state(const string &tag_state) const;
+  MAKE_MAP_PROPERTY(tag_states, has_tag_state, get_tag_state,
+                    set_tag_state, clear_tag_state);
 
 
   void set_aux_scene_data(const NodePath &node_path, AuxSceneData *data);
   void set_aux_scene_data(const NodePath &node_path, AuxSceneData *data);
   bool clear_aux_scene_data(const NodePath &node_path);
   bool clear_aux_scene_data(const NodePath &node_path);
   AuxSceneData *get_aux_scene_data(const NodePath &node_path) const;
   AuxSceneData *get_aux_scene_data(const NodePath &node_path) const;
   void list_aux_scene_data(ostream &out) const;
   void list_aux_scene_data(ostream &out) const;
   int cleanup_aux_scene_data(Thread *current_thread = Thread::get_current_thread());
   int cleanup_aux_scene_data(Thread *current_thread = Thread::get_current_thread());
+  MAKE_MAP_PROPERTY(aux_scene_data, get_aux_scene_data, get_aux_scene_data,
+                    set_aux_scene_data, clear_aux_scene_data);
 
 
 private:
 private:
   void add_display_region(DisplayRegion *display_region);
   void add_display_region(DisplayRegion *display_region);

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

@@ -213,7 +213,7 @@ make_default() {
 /**
 /**
  * Returns the basic operation type of the ClipPlaneAttrib.  If this is O_set,
  * Returns the basic operation type of the ClipPlaneAttrib.  If this is O_set,
  * the planes listed here completely replace any planes that were already on.
  * the planes listed here completely replace any planes that were already on.
- * If this is O_add, the planes here are added to the set of of planes that
+ * If this is O_add, the planes here are added to the set of planes that
  * were already on, and if O_remove, the planes here are removed from the set
  * were already on, and if O_remove, the planes here are removed from the set
  * of planes that were on.
  * of planes that were on.
  *
  *

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

@@ -227,7 +227,7 @@ ConfigVariableBool uniquify_states
           "are pointerwise equal.  This may improve caching performance, "
           "are pointerwise equal.  This may improve caching performance, "
           "but also adds additional overhead to maintain the cache, "
           "but also adds additional overhead to maintain the cache, "
           "including the need to check for a composition cycle in "
           "including the need to check for a composition cycle in "
-          "the cache."));
+          "the cache.  It is highly recommended to keep this on."));
 
 
 ConfigVariableBool uniquify_attribs
 ConfigVariableBool uniquify_attribs
 ("uniquify-attribs", true,
 ("uniquify-attribs", true,

+ 8 - 3
panda/src/pgraph/cullableObject.cxx

@@ -142,10 +142,15 @@ munge_geom(GraphicsStateGuardianBase *gsg, GeomMunger *munger,
           DCAST(ShaderAttrib, ShaderAttrib::make())->set_flag(ShaderAttrib::F_hardware_skinning, true));
           DCAST(ShaderAttrib, ShaderAttrib::make())->set_flag(ShaderAttrib::F_hardware_skinning, true));
         _state = _state->compose(state);
         _state = _state->compose(state);
       }
       }
-    }
 
 
-    StateMunger *state_munger = (StateMunger *)munger;
-    _state = state_munger->munge_state(_state);
+      gsg->ensure_generated_shader(_state);
+    } else {
+      // We may need to munge the state for the fixed-function pipeline.
+      StateMunger *state_munger = (StateMunger *)munger;
+      if (state_munger->should_munge_state()) {
+        _state = state_munger->munge_state(_state);
+      }
+    }
 
 
     // If there is any animation left in the vertex data after it has been
     // If there is any animation left in the vertex data after it has been
     // munged--that is, we couldn't arrange to handle the animation in
     // munged--that is, we couldn't arrange to handle the animation in

+ 9 - 1
panda/src/pgraph/lightAttrib.cxx

@@ -257,7 +257,7 @@ make_default() {
 /**
 /**
  * Returns the basic operation type of the LightAttrib.  If this is O_set, the
  * Returns the basic operation type of the LightAttrib.  If this is O_set, the
  * lights listed here completely replace any lights that were already on.  If
  * lights listed here completely replace any lights that were already on.  If
- * this is O_add, the lights here are added to the set of of lights that were
+ * this is O_add, the lights here are added to the set of lights that were
  * already on, and if O_remove, the lights here are removed from the set of
  * already on, and if O_remove, the lights here are removed from the set of
  * lights that were on.
  * lights that were on.
  *
  *
@@ -833,6 +833,14 @@ compose_impl(const RenderAttrib *other) const {
     ++result;
     ++result;
   }
   }
 
 
+  // Increase the attrib_ref of all the lights in this new attribute.
+  Lights::const_iterator it;
+  for (it = new_attrib->_on_lights.begin(); it != new_attrib->_on_lights.end(); ++it) {
+    Light *lobj = (*it).node()->as_light();
+    nassertd(lobj != nullptr) continue;
+    lobj->attrib_ref();
+  }
+
   return return_new(new_attrib);
   return return_new(new_attrib);
 }
 }
 
 

+ 11 - 3
panda/src/pgraph/nodePath.h

@@ -630,9 +630,12 @@ PUBLISHED:
   void clear_shader();
   void clear_shader();
 
 
   void set_shader_input(ShaderInput input);
   void set_shader_input(ShaderInput input);
-  INLINE void set_shader_input(CPT_InternalName id, Texture *tex, int priority=0);
+
   INLINE void set_shader_input(CPT_InternalName id, Texture *tex, const SamplerState &sampler, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, Texture *tex, const SamplerState &sampler, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, Texture *tex, bool read, bool write, int z=-1, int n=0, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, Texture *tex, bool read, bool write, int z=-1, int n=0, int priority=0);
+
+public:
+  INLINE void set_shader_input(CPT_InternalName id, Texture *tex, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, ShaderBuffer *buf, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, ShaderBuffer *buf, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const NodePath &np, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const NodePath &np, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const PTA_float &v, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const PTA_float &v, int priority=0);
@@ -654,11 +657,13 @@ PUBLISHED:
   INLINE void set_shader_input(CPT_InternalName id, const LVecBase4i &v, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const LVecBase4i &v, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const LVecBase3i &v, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const LVecBase3i &v, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const LVecBase2i &v, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const LVecBase2i &v, int priority=0);
-  INLINE void set_shader_input(CPT_InternalName id, int n1, int n2=0, int n3=0,
+PUBLISHED:
+  INLINE void set_shader_input(CPT_InternalName id, int n1, int n2, int n3=0,
                                                     int n4=0, int priority=0);
                                                     int n4=0, int priority=0);
-  INLINE void set_shader_input(CPT_InternalName id, PN_stdfloat n1, PN_stdfloat n2=0,
+  INLINE void set_shader_input(CPT_InternalName id, PN_stdfloat n1, PN_stdfloat n2,
                                PN_stdfloat n3=0, PN_stdfloat n4=0, int priority=0);
                                PN_stdfloat n3=0, PN_stdfloat n4=0, int priority=0);
 
 
+  EXTENSION(void set_shader_input(CPT_InternalName, PyObject *, int priority=0));
   EXTENSION(void set_shader_inputs(PyObject *args, PyObject *kwargs));
   EXTENSION(void set_shader_inputs(PyObject *args, PyObject *kwargs));
 
 
   void clear_shader_input(CPT_InternalName id);
   void clear_shader_input(CPT_InternalName id);
@@ -908,6 +913,9 @@ PUBLISHED:
   INLINE bool has_net_tag(const string &key) const;
   INLINE bool has_net_tag(const string &key) const;
   NodePath find_net_tag(const string &key) const;
   NodePath find_net_tag(const string &key) const;
 
 
+  MAKE_MAP_PROPERTY(tags, has_tag, get_tag, set_tag, clear_tag);
+  MAKE_MAP_PROPERTY(net_tags, has_net_tag, get_net_tag);
+
   EXTENSION(INLINE PyObject *get_tag_keys() const);
   EXTENSION(INLINE PyObject *get_tag_keys() const);
 
 
   EXTENSION(PyObject *get_python_tags());
   EXTENSION(PyObject *get_python_tags());

+ 28 - 173
panda/src/pgraph/nodePath_ext.cxx

@@ -13,6 +13,7 @@
 
 
 #include "nodePath_ext.h"
 #include "nodePath_ext.h"
 #include "typedWritable_ext.h"
 #include "typedWritable_ext.h"
+#include "shaderInput_ext.h"
 #include "shaderAttrib.h"
 #include "shaderAttrib.h"
 
 
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
@@ -25,35 +26,7 @@ extern struct Dtool_PyTypedObject Dtool_LPoint3d;
 #else
 #else
 extern struct Dtool_PyTypedObject Dtool_LPoint3f;
 extern struct Dtool_PyTypedObject Dtool_LPoint3f;
 #endif
 #endif
-extern struct Dtool_PyTypedObject Dtool_Texture;
 extern struct Dtool_PyTypedObject Dtool_NodePath;
 extern struct Dtool_PyTypedObject Dtool_NodePath;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_float;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_double;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_int;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_UnalignedLVecBase4f;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase3f;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase2f;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_UnalignedLMatrix4f;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_LMatrix3f;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_UnalignedLVecBase4d;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase3d;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase2d;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_UnalignedLMatrix4d;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_LMatrix3d;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_UnalignedLVecBase4i;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase3i;
-extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase2i;
-extern struct Dtool_PyTypedObject Dtool_LVecBase4f;
-extern struct Dtool_PyTypedObject Dtool_LVecBase3f;
-extern struct Dtool_PyTypedObject Dtool_LVecBase2f;
-extern struct Dtool_PyTypedObject Dtool_LVecBase4d;
-extern struct Dtool_PyTypedObject Dtool_LVecBase3d;
-extern struct Dtool_PyTypedObject Dtool_LVecBase2d;
-extern struct Dtool_PyTypedObject Dtool_LVecBase4i;
-extern struct Dtool_PyTypedObject Dtool_LVecBase3i;
-extern struct Dtool_PyTypedObject Dtool_LVecBase2i;
-extern struct Dtool_PyTypedObject Dtool_ShaderBuffer;
-extern struct Dtool_PyTypedObject Dtool_ParamValueBase;
 #endif  // CPPPARSER
 #endif  // CPPPARSER
 
 
 /**
 /**
@@ -241,6 +214,28 @@ py_decode_NodePath_from_bam_stream_persist(PyObject *unpickler, const string &da
   return NodePath::decode_from_bam_stream(data, reader);
   return NodePath::decode_from_bam_stream(data, reader);
 }
 }
 
 
+/**
+ * Sets a single shader input.
+ */
+void Extension<NodePath>::
+set_shader_input(CPT_InternalName name, PyObject *value, int priority) {
+  PT(PandaNode) node = _this->node();
+  CPT(RenderAttrib) prev_attrib = node->get_attrib(ShaderAttrib::get_class_slot());
+  PT(ShaderAttrib) attrib;
+  if (prev_attrib == nullptr) {
+    attrib = new ShaderAttrib();
+  } else {
+    attrib = new ShaderAttrib(*(const ShaderAttrib *)prev_attrib.p());
+  }
+
+  ShaderInput &input = attrib->_inputs[name];
+  invoke_extension(&input).__init__(move(name), value);
+
+  if (!_PyErr_OCCURRED()) {
+    node->set_attrib(ShaderAttrib::return_new(attrib));
+  }
+}
+
 /**
 /**
  * Sets multiple shader inputs at the same time.  This can be significantly
  * Sets multiple shader inputs at the same time.  This can be significantly
  * more efficient if many inputs need to be set at the same time.
  * more efficient if many inputs need to be set at the same time.
@@ -278,153 +273,13 @@ set_shader_inputs(PyObject *args, PyObject *kwargs) {
     }
     }
 
 
     CPT_InternalName name(string(buffer, length));
     CPT_InternalName name(string(buffer, length));
-    ShaderInput input(nullptr, 0);
-
-    if (PyTuple_CheckExact(value)) {
-      // A tuple is interpreted as a vector.
-      Py_ssize_t size = PyTuple_GET_SIZE(value);
-      if (size > 4) {
-        Dtool_Raise_TypeError("NodePath.set_shader_inputs tuple input should not have more than 4 scalars");
-        return;
-      }
-      // If any of them is a float, we are storing it as a float vector.
-      bool is_float = false;
-      for (Py_ssize_t i = 0; i < size; ++i) {
-        if (PyFloat_CheckExact(PyTuple_GET_ITEM(value, i))) {
-          is_float = true;
-          break;
-        }
-      }
-      if (is_float) {
-        LVecBase4 vec(0);
-        for (Py_ssize_t i = 0; i < size; ++i) {
-          vec[i] = (PN_stdfloat)PyFloat_AsDouble(PyTuple_GET_ITEM(value, i));
-        }
-        input = ShaderInput(move(name), vec);
-      } else {
-        LVecBase4i vec(0);
-        for (Py_ssize_t i = 0; i < size; ++i) {
-          vec[i] = (int)PyLong_AsLong(PyTuple_GET_ITEM(value, i));
-        }
-        input = ShaderInput(move(name), vec);
-      }
-
-    } else if (DtoolCanThisBeAPandaInstance(value)) {
-      Dtool_PyInstDef *inst = (Dtool_PyInstDef *)value;
-      void *ptr;
-
-      if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_Texture))) {
-        input = ShaderInput(move(name), (Texture *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_NodePath))) {
-        input = ShaderInput(move(name), *(const NodePath *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_float))) {
-        input = ShaderInput(move(name), *(const PTA_float *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_double))) {
-        input = ShaderInput(move(name), *(const PTA_double *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_int))) {
-        input = ShaderInput(move(name), *(const PTA_int *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLVecBase4f))) {
-        input = ShaderInput(move(name), *(const PTA_LVecBase4f *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase3f))) {
-        input = ShaderInput(move(name), *(const PTA_LVecBase3f *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase2f))) {
-        input = ShaderInput(move(name), *(const PTA_LVecBase2f *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLMatrix4f))) {
-        input = ShaderInput(move(name), *(const PTA_LMatrix4f *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LMatrix3f))) {
-        input = ShaderInput(move(name), *(const PTA_LMatrix3f *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLVecBase4d))) {
-        input = ShaderInput(move(name), *(const PTA_LVecBase4d *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase3d))) {
-        input = ShaderInput(move(name), *(const PTA_LVecBase3d *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase2d))) {
-        input = ShaderInput(move(name), *(const PTA_LVecBase2d *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLMatrix4d))) {
-        input = ShaderInput(move(name), *(const PTA_LMatrix4d *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LMatrix3d))) {
-        input = ShaderInput(move(name), *(const PTA_LMatrix3d *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLVecBase4i))) {
-        input = ShaderInput(move(name), *(const PTA_LVecBase4i *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase3i))) {
-        input = ShaderInput(move(name), *(const PTA_LVecBase3i *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase2i))) {
-        input = ShaderInput(move(name), *(const PTA_LVecBase2i *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase4f))) {
-        input = ShaderInput(move(name), *(const LVecBase4f *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase3f))) {
-        input = ShaderInput(move(name), *(const LVecBase3f *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase2f))) {
-        input = ShaderInput(move(name), *(const LVecBase2f *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase4d))) {
-        input = ShaderInput(move(name), *(const LVecBase4d *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase3d))) {
-        input = ShaderInput(move(name), *(const LVecBase3d *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase2d))) {
-        input = ShaderInput(move(name), *(const LVecBase2d *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase4i))) {
-        input = ShaderInput(move(name), *(const LVecBase4i *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase3i))) {
-        input = ShaderInput(move(name), *(const LVecBase3i *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase2i))) {
-        input = ShaderInput(move(name), *(const LVecBase2i *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_ShaderBuffer))) {
-        input = ShaderInput(move(name), (ShaderBuffer *)ptr);
-
-      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_ParamValueBase))) {
-        input = ShaderInput(move(name), (ParamValueBase *)ptr);
-
-      } else {
-        Dtool_Raise_TypeError("unknown type passed to NodePath.set_shader_inputs");
-        return;
-      }
-
-    } else if (PyFloat_Check(value)) {
-      input = ShaderInput(move(name), LVecBase4(PyFloat_AS_DOUBLE(value), 0, 0, 0));
-
-#if PY_MAJOR_VERSION < 3
-    } else if (PyInt_Check(value)) {
-      input = ShaderInput(move(name), LVecBase4i((int)PyInt_AS_LONG(value), 0, 0, 0));
-#endif
-
-    } else if (PyLong_Check(value)) {
-      input = ShaderInput(move(name), LVecBase4i((int)PyLong_AsLong(value), 0, 0, 0));
-
-    } else {
-      Dtool_Raise_TypeError("unknown type passed to NodePath.set_shader_inputs");
-      return;
-    }
-
-    attrib->_inputs[input.get_name()] = move(input);
+    ShaderInput &input = attrib->_inputs[name];
+    invoke_extension(&input).__init__(move(name), value);
   }
   }
 
 
-  node->set_attrib(ShaderAttrib::return_new(attrib));
+  if (!_PyErr_OCCURRED()) {
+    node->set_attrib(ShaderAttrib::return_new(attrib));
+  }
 }
 }
 
 
 /**
 /**

+ 1 - 0
panda/src/pgraph/nodePath_ext.h

@@ -49,6 +49,7 @@ public:
   // This is defined to implement cycle detection in Python tags.
   // This is defined to implement cycle detection in Python tags.
   INLINE int __traverse__(visitproc visit, void *arg);
   INLINE int __traverse__(visitproc visit, void *arg);
 
 
+  void set_shader_input(CPT_InternalName id, PyObject *value, int priority=0);
   void set_shader_inputs(PyObject *args, PyObject *kwargs);
   void set_shader_inputs(PyObject *args, PyObject *kwargs);
 
 
   PyObject *get_tight_bounds(const NodePath &other = NodePath()) const;
   PyObject *get_tight_bounds(const NodePath &other = NodePath()) const;

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

@@ -2,4 +2,6 @@
 #include "nodePathCollection_ext.cxx"
 #include "nodePathCollection_ext.cxx"
 #include "pandaNode_ext.cxx"
 #include "pandaNode_ext.cxx"
 #include "renderState_ext.cxx"
 #include "renderState_ext.cxx"
+#include "shaderAttrib_ext.cxx"
+#include "shaderInput_ext.cxx"
 #include "transformState_ext.cxx"
 #include "transformState_ext.cxx"

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

@@ -389,7 +389,7 @@ return_unique(RenderAttrib *attrib) {
     // deleted while it's in it.
     // deleted while it's in it.
     attrib->ref();
     attrib->ref();
   }
   }
-  si = _attribs->store(attrib, Empty());
+  si = _attribs->store(attrib, nullptr);
 
 
   // Save the index and return the input attrib.
   // Save the index and return the input attrib.
   attrib->_saved_entry = si;
   attrib->_saved_entry = si;

+ 1 - 3
panda/src/pgraph/renderAttrib.h

@@ -185,9 +185,7 @@ public:
 private:
 private:
   // This mutex protects _attribs.
   // This mutex protects _attribs.
   static LightReMutex *_attribs_lock;
   static LightReMutex *_attribs_lock;
-  class Empty {
-  };
-  typedef SimpleHashMap<const RenderAttrib *, Empty, indirect_compare_to_hash<const RenderAttrib *> > Attribs;
+  typedef SimpleHashMap<const RenderAttrib *, nullptr_t, indirect_compare_to_hash<const RenderAttrib *> > Attribs;
   static Attribs *_attribs;
   static Attribs *_attribs;
 
 
   int _saved_entry;
   int _saved_entry;

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

@@ -93,7 +93,7 @@ register_slot(TypeHandle type_handle, int sort, RenderAttrib *default_attrib) {
       // If this attribute was already registered, something odd is going on.
       // If this attribute was already registered, something odd is going on.
       nassertr(RenderAttrib::_attribs->find(default_attrib) == -1, 0);
       nassertr(RenderAttrib::_attribs->find(default_attrib) == -1, 0);
       default_attrib->_saved_entry =
       default_attrib->_saved_entry =
-        RenderAttrib::_attribs->store(default_attrib, RenderAttrib::Empty());
+        RenderAttrib::_attribs->store(default_attrib, nullptr);
     }
     }
 
 
     // It effectively lives forever.  Might as well make it official.
     // It effectively lives forever.  Might as well make it official.

+ 3 - 2
panda/src/pgraph/renderState.cxx

@@ -987,6 +987,7 @@ clear_munger_cache() {
   for (size_t si = 0; si < size; ++si) {
   for (size_t si = 0; si < size; ++si) {
     RenderState *state = (RenderState *)(_states->get_key(si));
     RenderState *state = (RenderState *)(_states->get_key(si));
     state->_mungers.clear();
     state->_mungers.clear();
+    state->_munged_states.clear();
     state->_last_mi = -1;
     state->_last_mi = -1;
   }
   }
 }
 }
@@ -1338,7 +1339,7 @@ return_unique(RenderState *state) {
     // deleted while it's in it.
     // deleted while it's in it.
     state->cache_ref();
     state->cache_ref();
   }
   }
-  si = _states->store(state, Empty());
+  si = _states->store(state, nullptr);
 
 
   // Save the index and return the input state.
   // Save the index and return the input state.
   state->_saved_entry = si;
   state->_saved_entry = si;
@@ -1865,7 +1866,7 @@ init_states() {
   // is declared globally, and lives forever.
   // is declared globally, and lives forever.
   RenderState *state = new RenderState;
   RenderState *state = new RenderState;
   state->local_object();
   state->local_object();
-  state->_saved_entry = _states->store(state, Empty());
+  state->_saved_entry = _states->store(state, nullptr);
   _empty_state = state;
   _empty_state = state;
 }
 }
 
 

+ 12 - 6
panda/src/pgraph/renderState.h

@@ -30,10 +30,8 @@
 #include "lightMutex.h"
 #include "lightMutex.h"
 #include "deletedChain.h"
 #include "deletedChain.h"
 #include "simpleHashMap.h"
 #include "simpleHashMap.h"
-#include "weakKeyHashMap.h"
 #include "cacheStats.h"
 #include "cacheStats.h"
 #include "renderAttribRegistry.h"
 #include "renderAttribRegistry.h"
-#include "graphicsStateGuardianBase.h"
 
 
 class FactoryParams;
 class FactoryParams;
 class ShaderAttrib;
 class ShaderAttrib;
@@ -109,6 +107,8 @@ PUBLISHED:
   INLINE int get_override(TypeHandle type) const;
   INLINE int get_override(TypeHandle type) const;
   INLINE int get_override(int slot) const;
   INLINE int get_override(int slot) const;
 
 
+  MAKE_MAP_PROPERTY(attribs, has_attrib, get_attrib);
+
   INLINE CPT(RenderState) get_unique() const;
   INLINE CPT(RenderState) get_unique() const;
 
 
   virtual bool unref() const;
   virtual bool unref() const;
@@ -219,15 +219,14 @@ public:
   // declare this as a ShaderAttrib because that would create a circular
   // declare this as a ShaderAttrib because that would create a circular
   // include-file dependency problem.  Aaargh.
   // include-file dependency problem.  Aaargh.
   mutable CPT(RenderAttrib) _generated_shader;
   mutable CPT(RenderAttrib) _generated_shader;
+  mutable UpdateSeq _generated_shader_seq;
 
 
 private:
 private:
   // This mutex protects _states.  It also protects any modification to the
   // This mutex protects _states.  It also protects any modification to the
   // cache, which is encoded in _composition_cache and
   // cache, which is encoded in _composition_cache and
   // _invert_composition_cache.
   // _invert_composition_cache.
   static LightReMutex *_states_lock;
   static LightReMutex *_states_lock;
-  class Empty {
-  };
-  typedef SimpleHashMap<const RenderState *, Empty, indirect_compare_to_hash<const RenderState *> > States;
+  typedef SimpleHashMap<const RenderState *, nullptr_t, indirect_compare_to_hash<const RenderState *> > States;
   static States *_states;
   static States *_states;
   static const RenderState *_empty_state;
   static const RenderState *_empty_state;
 
 
@@ -264,10 +263,15 @@ private:
   // in the RenderState pointer than vice-versa, since there are likely to be
   // in the RenderState pointer than vice-versa, since there are likely to be
   // far fewer GSG's than RenderStates.  The code to manage this map lives in
   // far fewer GSG's than RenderStates.  The code to manage this map lives in
   // GraphicsStateGuardian::get_geom_munger().
   // GraphicsStateGuardian::get_geom_munger().
-  typedef WeakKeyHashMap<GraphicsStateGuardianBase, PT(GeomMunger) > Mungers;
+  typedef SimpleHashMap<size_t, PT(GeomMunger), size_t_hash> Mungers;
   mutable Mungers _mungers;
   mutable Mungers _mungers;
   mutable int _last_mi;
   mutable int _last_mi;
 
 
+  // Similarly, this is a cache of munged states.  This map is managed by
+  // StateMunger::munge_state().
+  typedef SimpleHashMap<size_t, WCPT(RenderState), size_t_hash> MungedStates;
+  mutable MungedStates _munged_states;
+
   // This is used to mark nodes as we visit them to detect cycles.
   // This is used to mark nodes as we visit them to detect cycles.
   UpdateSeq _cycle_detect;
   UpdateSeq _cycle_detect;
   static UpdateSeq _last_cycle_detect;
   static UpdateSeq _last_cycle_detect;
@@ -360,6 +364,8 @@ private:
   friend class GraphicsStateGuardian;
   friend class GraphicsStateGuardian;
   friend class RenderAttribRegistry;
   friend class RenderAttribRegistry;
   friend class Extension<RenderState>;
   friend class Extension<RenderState>;
+  friend class ShaderGenerator;
+  friend class StateMunger;
 };
 };
 
 
 // We can safely redefine this as a no-op.
 // We can safely redefine this as a no-op.

+ 6 - 0
panda/src/pgraph/shaderAttrib.h

@@ -73,6 +73,7 @@ PUBLISHED:
   // Shader Inputs
   // Shader Inputs
   CPT(RenderAttrib) set_shader_input(ShaderInput input) const;
   CPT(RenderAttrib) set_shader_input(ShaderInput input) const;
 
 
+public:
   INLINE CPT(RenderAttrib) set_shader_input(CPT_InternalName id, Texture *tex,       int priority=0) const;
   INLINE CPT(RenderAttrib) set_shader_input(CPT_InternalName id, Texture *tex,       int priority=0) const;
   INLINE CPT(RenderAttrib) set_shader_input(CPT_InternalName id, const NodePath &np, int priority=0) const;
   INLINE CPT(RenderAttrib) set_shader_input(CPT_InternalName id, const NodePath &np, int priority=0) const;
   INLINE CPT(RenderAttrib) set_shader_input(CPT_InternalName id, const PTA_float &v, int priority=0) const;
   INLINE CPT(RenderAttrib) set_shader_input(CPT_InternalName id, const PTA_float &v, int priority=0) const;
@@ -90,6 +91,10 @@ PUBLISHED:
   INLINE CPT(RenderAttrib) set_shader_input(CPT_InternalName id, double n1=0, double n2=0, double n3=0, double n4=1,
   INLINE CPT(RenderAttrib) set_shader_input(CPT_InternalName id, double n1=0, double n2=0, double n3=0, double n4=1,
                                             int priority=0) const;
                                             int priority=0) const;
 
 
+PUBLISHED:
+  EXTENSION(CPT(RenderAttrib) set_shader_input(CPT_InternalName, PyObject *, int priority=0) const);
+  EXTENSION(CPT(RenderAttrib) set_shader_inputs(PyObject *args, PyObject *kwargs) const);
+
   CPT(RenderAttrib) set_instance_count(int instance_count) const;
   CPT(RenderAttrib) set_instance_count(int instance_count) const;
 
 
   CPT(RenderAttrib) set_flag(int flag, bool value) const;
   CPT(RenderAttrib) set_flag(int flag, bool value) const;
@@ -150,6 +155,7 @@ private:
   Inputs _inputs;
   Inputs _inputs;
 
 
   friend class Extension<NodePath>;
   friend class Extension<NodePath>;
+  friend class Extension<ShaderAttrib>;
 
 
 PUBLISHED:
 PUBLISHED:
   static int get_class_slot() {
   static int get_class_slot() {

+ 71 - 0
panda/src/pgraph/shaderAttrib_ext.cxx

@@ -0,0 +1,71 @@
+/**
+ * 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."
+ *
+ * @file shaderAttrib_ext.cxx
+ * @author rdb
+ * @date 2017-10-08
+ */
+
+#include "shaderAttrib_ext.h"
+#include "shaderInput_ext.h"
+
+#ifdef HAVE_PYTHON
+
+/**
+ * Returns a new ShaderAttrib with the given shader input set.
+ */
+CPT(RenderAttrib) Extension<ShaderAttrib>::
+set_shader_input(CPT_InternalName name, PyObject *value, int priority) const {
+  ShaderAttrib *attrib = new ShaderAttrib(*_this);
+
+  ShaderInput &input = attrib->_inputs[name];
+  invoke_extension(&input).__init__(move(name), value);
+
+  return ShaderAttrib::return_new(attrib);
+}
+
+/**
+ * Returns a new ShaderAttrib with the given shader inputs set.  This is a
+ * more efficient way to set multiple shader inputs than calling
+ * set_shader_input multiple times.
+ */
+CPT(RenderAttrib) Extension<ShaderAttrib>::
+set_shader_inputs(PyObject *args, PyObject *kwargs) const {
+  if (PyObject_Size(args) > 0) {
+    Dtool_Raise_TypeError("ShaderAttrib.set_shader_inputs takes only keyword arguments");
+    return nullptr;
+  }
+
+  ShaderAttrib *attrib = new ShaderAttrib(*_this);
+
+  PyObject *key, *value;
+  Py_ssize_t pos = 0;
+
+  while (PyDict_Next(kwargs, &pos, &key, &value)) {
+    char *buffer;
+    Py_ssize_t length;
+#if PY_MAJOR_VERSION >= 3
+    buffer = (char *)PyUnicode_AsUTF8AndSize(key, &length);
+    if (buffer == nullptr) {
+#else
+    if (PyString_AsStringAndSize(key, &buffer, &length) == -1) {
+#endif
+      Dtool_Raise_TypeError("ShaderAttrib.set_shader_inputs accepts only string keywords");
+      delete attrib;
+      return nullptr;
+    }
+
+    CPT_InternalName name(string(buffer, length));
+    ShaderInput &input = attrib->_inputs[name];
+    invoke_extension(&input).__init__(move(name), value);
+  }
+
+  return ShaderAttrib::return_new(attrib);
+}
+
+#endif  // HAVE_PYTHON

+ 38 - 0
panda/src/pgraph/shaderAttrib_ext.h

@@ -0,0 +1,38 @@
+/**
+ * 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."
+ *
+ * @file shaderAttrib_ext.h
+ * @author rdb
+ * @date 2017-10-08
+ */
+
+#ifndef SHADERATTRIB_EXT_H
+#define SHADERATTRIB_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "shaderAttrib.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for ShaderAttrib, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<ShaderAttrib> : public ExtensionBase<ShaderAttrib> {
+public:
+  CPT(RenderAttrib) set_shader_input(CPT_InternalName id, PyObject *value, int priority=0) const;
+  CPT(RenderAttrib) set_shader_inputs(PyObject *args, PyObject *kwargs) const;
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // SHADERATTRIB_EXT_H

+ 8 - 0
panda/src/pgraph/shaderInput.I

@@ -417,6 +417,14 @@ ShaderInput(CPT_InternalName name, const LVecBase2i &vec, int priority) :
 {
 {
 }
 }
 
 
+/**
+ *
+ */
+INLINE ShaderInput::
+operator bool () const {
+  return _type != M_invalid;
+}
+
 /**
 /**
  *
  *
  */
  */

+ 9 - 1
panda/src/pgraph/shaderInput.h

@@ -46,7 +46,11 @@ PUBLISHED:
   };
   };
 
 
   static const ShaderInput &get_blank();
   static const ShaderInput &get_blank();
-  INLINE ShaderInput(CPT_InternalName name, int priority=0);
+  INLINE explicit ShaderInput(CPT_InternalName name, int priority=0);
+
+  EXTENSION(explicit ShaderInput(CPT_InternalName name, PyObject *value, int priority=0));
+
+public:
   INLINE ShaderInput(CPT_InternalName name, Texture *tex, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, Texture *tex, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, ParamValueBase *param, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, ParamValueBase *param, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, ShaderBuffer *buf, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, ShaderBuffer *buf, int priority=0);
@@ -83,6 +87,8 @@ PUBLISHED:
   INLINE ShaderInput(CPT_InternalName name, const LVecBase2i &vec, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, const LVecBase2i &vec, int priority=0);
 
 
   ShaderInput(CPT_InternalName name, const NodePath &np, int priority=0);
   ShaderInput(CPT_InternalName name, const NodePath &np, int priority=0);
+
+PUBLISHED:
   ShaderInput(CPT_InternalName name, Texture *tex, bool read, bool write, int z=-1, int n=0, int priority=0);
   ShaderInput(CPT_InternalName name, Texture *tex, bool read, bool write, int z=-1, int n=0, int priority=0);
   ShaderInput(CPT_InternalName name, Texture *tex, const SamplerState &sampler, int priority=0);
   ShaderInput(CPT_InternalName name, Texture *tex, const SamplerState &sampler, int priority=0);
 
 
@@ -98,6 +104,7 @@ PUBLISHED:
     M_buffer,
     M_buffer,
   };
   };
 
 
+  INLINE operator bool() const;
   INLINE bool operator == (const ShaderInput &other) const;
   INLINE bool operator == (const ShaderInput &other) const;
   INLINE bool operator != (const ShaderInput &other) const;
   INLINE bool operator != (const ShaderInput &other) const;
   INLINE bool operator < (const ShaderInput &other) const;
   INLINE bool operator < (const ShaderInput &other) const;
@@ -132,6 +139,7 @@ private:
   int _type;
   int _type;
 
 
   friend class ShaderAttrib;
   friend class ShaderAttrib;
+  friend class Extension<ShaderInput>;
 };
 };
 
 
 #include "shaderInput.I"
 #include "shaderInput.I"

+ 539 - 0
panda/src/pgraph/shaderInput_ext.cxx

@@ -0,0 +1,539 @@
+/**
+ * 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."
+ *
+ * @file shaderInput_ext.cxx
+ * @author rdb
+ * @date 2017-10-06
+ */
+
+#include "shaderInput_ext.h"
+#include "paramNodePath.h"
+
+#ifdef HAVE_PYTHON
+
+#ifndef CPPPARSER
+extern struct Dtool_PyTypedObject Dtool_Texture;
+extern struct Dtool_PyTypedObject Dtool_NodePath;
+extern struct Dtool_PyTypedObject Dtool_PointerToVoid;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_float;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_double;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_int;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_UnalignedLVecBase4f;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase3f;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase2f;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_UnalignedLMatrix4f;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_LMatrix3f;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_UnalignedLVecBase4d;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase3d;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase2d;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_UnalignedLMatrix4d;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_LMatrix3d;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_UnalignedLVecBase4i;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase3i;
+extern struct Dtool_PyTypedObject Dtool_PointerToArray_LVecBase2i;
+extern struct Dtool_PyTypedObject Dtool_LMatrix4f;
+extern struct Dtool_PyTypedObject Dtool_LMatrix3f;
+extern struct Dtool_PyTypedObject Dtool_LMatrix4d;
+extern struct Dtool_PyTypedObject Dtool_LMatrix3d;
+extern struct Dtool_PyTypedObject Dtool_LVecBase4f;
+extern struct Dtool_PyTypedObject Dtool_LVecBase3f;
+extern struct Dtool_PyTypedObject Dtool_LVecBase2f;
+extern struct Dtool_PyTypedObject Dtool_LVecBase4d;
+extern struct Dtool_PyTypedObject Dtool_LVecBase3d;
+extern struct Dtool_PyTypedObject Dtool_LVecBase2d;
+extern struct Dtool_PyTypedObject Dtool_LVecBase4i;
+extern struct Dtool_PyTypedObject Dtool_LVecBase3i;
+extern struct Dtool_PyTypedObject Dtool_LVecBase2i;
+extern struct Dtool_PyTypedObject Dtool_ShaderBuffer;
+extern struct Dtool_PyTypedObject Dtool_ParamValueBase;
+#endif  // CPPPARSER
+
+/**
+ * Sets a shader input from an arbitrary Python object.
+ */
+void Extension<ShaderInput>::
+__init__(CPT_InternalName name, PyObject *value, int priority) {
+  _this->_name = move(name);
+  _this->_priority = priority;
+
+  if (PyTuple_CheckExact(value) && PyTuple_GET_SIZE(value) <= 4) {
+    // A tuple is interpreted as a vector.
+    Py_ssize_t size = PyTuple_GET_SIZE(value);
+
+    // If any of them is a float, we are storing it as a float vector.
+    bool is_float = false;
+    for (Py_ssize_t i = 0; i < size; ++i) {
+      if (PyFloat_CheckExact(PyTuple_GET_ITEM(value, i))) {
+        is_float = true;
+        break;
+      }
+    }
+    if (is_float) {
+      LVecBase4 vec(0);
+      for (Py_ssize_t i = 0; i < size; ++i) {
+        vec[i] = (PN_stdfloat)PyFloat_AsDouble(PyTuple_GET_ITEM(value, i));
+      }
+      _this->_type = ShaderInput::M_vector;
+      _this->_stored_ptr = vec;
+      _this->_stored_vector = vec;
+    } else {
+      LVecBase4i vec(0);
+      for (Py_ssize_t i = 0; i < size; ++i) {
+        vec[i] = (int)PyLong_AsLong(PyTuple_GET_ITEM(value, i));
+      }
+      _this->_type = ShaderInput::M_numeric;
+      _this->_stored_ptr = vec;
+      _this->_stored_vector = LCAST(PN_stdfloat, vec);
+    }
+
+  } else if (DtoolCanThisBeAPandaInstance(value)) {
+    Dtool_PyInstDef *inst = (Dtool_PyInstDef *)value;
+    void *ptr;
+
+    if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_Texture))) {
+      _this->_type = ShaderInput::M_texture;
+      _this->_value = (Texture *)ptr;
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_NodePath))) {
+      _this->_type = ShaderInput::M_nodepath;
+      _this->_value = new ParamNodePath(*(const NodePath *)ptr);
+
+    } else if (inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToVoid)) {
+      if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_float))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_float *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_double))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_double *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_int))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_int *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLVecBase4f))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LVecBase4f *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase3f))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LVecBase3f *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase2f))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LVecBase2f *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLMatrix4f))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LMatrix4f *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LMatrix3f))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LMatrix3f *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLVecBase4d))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LVecBase4d *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase3d))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LVecBase3d *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase2d))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LVecBase2d *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLMatrix4d))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LMatrix4d *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LMatrix3d))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LMatrix3d *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_UnalignedLVecBase4i))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LVecBase4i *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase3i))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LVecBase3i *)ptr;
+
+      } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_PointerToArray_LVecBase2i))) {
+        _this->_type = ShaderInput::M_numeric;
+        _this->_stored_ptr = *(const PTA_LVecBase2i *)ptr;
+
+      } else {
+        Dtool_Raise_TypeError("unknown type passed to ShaderInput");
+        return;
+      }
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LMatrix4f))) {
+      _this->_type = ShaderInput::M_numeric;
+      _this->_stored_ptr = *(const LMatrix4f *)ptr;
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LMatrix3f))) {
+      _this->_type = ShaderInput::M_numeric;
+      _this->_stored_ptr = *(const LMatrix3f *)ptr;
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LMatrix4d))) {
+      _this->_type = ShaderInput::M_numeric;
+      _this->_stored_ptr = *(const LMatrix4d *)ptr;
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LMatrix3d))) {
+      _this->_type = ShaderInput::M_numeric;
+      _this->_stored_ptr = *(const LMatrix3d *)ptr;
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase4f))) {
+      const LVecBase4f &vec = *(const LVecBase4f *)ptr;
+      _this->_type = ShaderInput::M_vector;
+      _this->_stored_ptr = vec;
+      _this->_stored_vector = LCAST(PN_stdfloat, vec);
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase3f))) {
+      const LVecBase3f &vec = *(const LVecBase3f *)ptr;
+      _this->_type = ShaderInput::M_vector;
+      _this->_stored_ptr = vec;
+      _this->_stored_vector.set(vec[0], vec[1], vec[2], 0);
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase2f))) {
+      const LVecBase2f &vec = *(const LVecBase2f *)ptr;
+      _this->_type = ShaderInput::M_vector;
+      _this->_stored_ptr = vec;
+      _this->_stored_vector.set(vec[0], vec[1], 0, 0);
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase4d))) {
+      const LVecBase4d &vec = *(const LVecBase4d *)ptr;
+      _this->_type = ShaderInput::M_vector;
+      _this->_stored_ptr = vec;
+      _this->_stored_vector = LCAST(PN_stdfloat, vec);
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase3d))) {
+      const LVecBase3d &vec = *(const LVecBase3d *)ptr;
+      _this->_type = ShaderInput::M_vector;
+      _this->_stored_ptr = vec;
+      _this->_stored_vector.set(vec[0], vec[1], vec[2], 0);
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase2d))) {
+      const LVecBase2d &vec = *(const LVecBase2d *)ptr;
+      _this->_type = ShaderInput::M_vector;
+      _this->_stored_ptr = vec;
+      _this->_stored_vector.set(vec[0], vec[1], 0, 0);
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase4i))) {
+      const LVecBase4i &vec = *(const LVecBase4i *)ptr;
+      _this->_type = ShaderInput::M_numeric;
+      _this->_stored_ptr = vec;
+      _this->_stored_vector = LCAST(PN_stdfloat, vec);
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase3i))) {
+      const LVecBase3i &vec = *(const LVecBase3i *)ptr;
+      _this->_type = ShaderInput::M_numeric;
+      _this->_stored_ptr = vec;
+      _this->_stored_vector.set(vec[0], vec[1], vec[2], 0);
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_LVecBase2i))) {
+      const LVecBase2i &vec = *(const LVecBase2i *)ptr;
+      _this->_type = ShaderInput::M_numeric;
+      _this->_stored_ptr = vec;
+      _this->_stored_vector.set(vec[0], vec[1], 0, 0);
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_ShaderBuffer))) {
+      _this->_type = ShaderInput::M_buffer;
+      _this->_value = (ShaderBuffer *)ptr;
+
+    } else if ((ptr = inst->_My_Type->_Dtool_UpcastInterface(value, &Dtool_ParamValueBase))) {
+      _this->_type = ShaderInput::M_param;
+      _this->_value = (ParamValueBase *)ptr;
+
+    } else {
+      Dtool_Raise_TypeError("unknown type passed to ShaderInput");
+      return;
+    }
+
+  } else if (PyFloat_Check(value)) {
+    LVecBase4 vec(PyFloat_AS_DOUBLE(value), 0, 0, 0);
+    _this->_type = ShaderInput::M_vector;
+    _this->_stored_ptr = vec;
+    _this->_stored_vector = vec;
+
+#if PY_MAJOR_VERSION < 3
+  } else if (PyInt_Check(value)) {
+    LVecBase4i vec((int)PyInt_AS_LONG(value), 0, 0, 0);
+    _this->_type = ShaderInput::M_numeric;
+    _this->_stored_ptr = vec;
+    _this->_stored_vector.set((PN_stdfloat)vec[0], 0, 0, 0);
+#endif
+
+  } else if (PyLong_Check(value)) {
+    LVecBase4i vec((int)PyLong_AsLong(value), 0, 0, 0);
+    _this->_type = ShaderInput::M_numeric;
+    _this->_stored_ptr = vec;
+    _this->_stored_vector.set((PN_stdfloat)vec[0], 0, 0, 0);
+
+  } else if (PySequence_Check(value) && !PyUnicode_CheckExact(value)) {
+    // Iterate over the sequence to make sure all have the same type.
+    PyObject *fast = PySequence_Fast(value, "unknown type passed to ShaderInput");
+    if (fast == nullptr) {
+      return;
+    }
+
+    Py_ssize_t num_items = PySequence_Fast_GET_SIZE(value);
+    if (num_items <= 0) {
+      // We can't determine the type of a list of size 0.
+      _this->_type = ShaderInput::M_numeric;
+      Py_DECREF(fast);
+      return;
+    }
+
+    bool has_float = false;
+    Py_ssize_t known_itemsize = -1;
+
+    PyObject **items = PySequence_Fast_ITEMS(fast);
+    for (Py_ssize_t i = 0; i < num_items; ++i) {
+      PyObject *item = items[i];
+
+      if (PySequence_Check(item)) {
+        Py_ssize_t itemsize = PySequence_Size(item);
+        if (known_itemsize >= 0 && itemsize != known_itemsize) {
+          Dtool_Raise_TypeError("inconsistent sequence length among elements of sequence passed to ShaderInput");
+          Py_DECREF(fast);
+          return;
+        }
+        known_itemsize = itemsize;
+
+        // Check their types.
+        for (Py_ssize_t j = 0; j < itemsize; ++j) {
+          PyObject *subitem = PySequence_ITEM(item, j);
+          if (PyFloat_CheckExact(subitem)) {
+            Py_DECREF(subitem);
+            has_float = true;
+            break;
+          } else if (PyLongOrInt_Check(subitem)) {
+          } else {
+            Dtool_Raise_TypeError("unknown element type in sequence passed as element of sequence passed to ShaderInput");
+            Py_DECREF(subitem);
+            Py_DECREF(fast);
+            break;
+          }
+          Py_DECREF(subitem);
+        }
+      } else if (PyFloat_CheckExact(item)) {
+        has_float = true;
+      } else if (PyLongOrInt_Check(item)) {
+      } else {
+        Dtool_Raise_TypeError("unknown element type in sequence passed to ShaderInput");
+        Py_DECREF(fast);
+        return;
+      }
+    }
+
+    // Now that we have verified the dimensions and type of the PTA, we can
+    // read in the actual elements.
+    switch (known_itemsize) {
+    case -1:
+      if (has_float) {
+        PTA_float pta;
+        pta.reserve(num_items);
+        for (Py_ssize_t i = 0; i < num_items; ++i) {
+          pta.push_back(PyFloat_AsDouble(items[i]));
+        }
+        _this->_stored_ptr = pta;
+      } else {
+        PTA_int pta;
+        pta.reserve(num_items);
+        for (Py_ssize_t i = 0; i < num_items; ++i) {
+          pta.push_back((int)PyLongOrInt_AS_LONG(items[i]));
+        }
+        _this->_stored_ptr = pta;
+      }
+      break;
+
+    case 1:
+      if (has_float) {
+        PTA_float pta;
+        pta.reserve(num_items);
+        for (Py_ssize_t i = 0; i < num_items; ++i) {
+          PyObject *item = items[i];
+          if (PySequence_Check(item)) {
+            PyObject *subitem = PySequence_ITEM(item, 0);
+            pta.push_back(PyFloat_AsDouble(subitem));
+            Py_DECREF(subitem);
+          } else {
+            pta.push_back(PyFloat_AsDouble(item));
+          }
+        }
+        _this->_stored_ptr = pta;
+      } else {
+        PTA_int pta;
+        pta.reserve(num_items);
+        for (Py_ssize_t i = 0; i < num_items; ++i) {
+          PyObject *item = items[i];
+          if (PySequence_Check(item)) {
+            PyObject *subitem = PySequence_ITEM(item, 0);
+            pta.push_back((int)PyLongOrInt_AS_LONG(subitem));
+            Py_DECREF(subitem);
+          } else {
+            pta.push_back((int)PyLongOrInt_AS_LONG(item));
+          }
+        }
+        _this->_stored_ptr = pta;
+      }
+      break;
+
+    case 2:
+      if (has_float) {
+        PTA_LVecBase2f pta;
+        pta.reserve(num_items);
+        for (Py_ssize_t i = 0; i < num_items; ++i) {
+          PyObject *item = items[i];
+          if (PySequence_Check(item)) {
+            PyObject *subitem0 = PySequence_ITEM(item, 0);
+            PyObject *subitem1 = PySequence_ITEM(item, 1);
+            pta.push_back(LVecBase2f(PyFloat_AsDouble(subitem0),
+                                     PyFloat_AsDouble(subitem1)));
+            Py_DECREF(subitem0);
+            Py_DECREF(subitem1);
+          } else {
+            pta.push_back(PyFloat_AsDouble(item));
+          }
+        }
+        _this->_stored_ptr = pta;
+      } else {
+        PTA_LVecBase2i pta;
+        pta.reserve(num_items);
+        for (Py_ssize_t i = 0; i < num_items; ++i) {
+          PyObject *item = items[i];
+          if (PySequence_Check(item)) {
+            PyObject *subitem0 = PySequence_ITEM(item, 0);
+            PyObject *subitem1 = PySequence_ITEM(item, 1);
+            pta.push_back(LVecBase2i((int)PyLongOrInt_AS_LONG(subitem0),
+                                     (int)PyLongOrInt_AS_LONG(subitem1)));
+            Py_DECREF(subitem0);
+            Py_DECREF(subitem1);
+          } else {
+            pta.push_back((int)PyLongOrInt_AS_LONG(item));
+          }
+        }
+        _this->_stored_ptr = pta;
+      }
+      break;
+
+    case 3:
+      if (has_float) {
+        PTA_LVecBase3f pta;
+        pta.reserve(num_items);
+        for (Py_ssize_t i = 0; i < num_items; ++i) {
+          PyObject *item = items[i];
+          if (PySequence_Check(item)) {
+            PyObject *subitem0 = PySequence_ITEM(item, 0);
+            PyObject *subitem1 = PySequence_ITEM(item, 1);
+            PyObject *subitem2 = PySequence_ITEM(item, 2);
+            pta.push_back(LVecBase3f(PyFloat_AsDouble(subitem0),
+                                     PyFloat_AsDouble(subitem1),
+                                     PyFloat_AsDouble(subitem2)));
+            Py_DECREF(subitem0);
+            Py_DECREF(subitem1);
+            Py_DECREF(subitem2);
+          } else {
+            pta.push_back(PyFloat_AsDouble(item));
+          }
+        }
+        _this->_stored_ptr = pta;
+      } else {
+        PTA_LVecBase3i pta;
+        pta.reserve(num_items);
+        for (Py_ssize_t i = 0; i < num_items; ++i) {
+          PyObject *item = items[i];
+          if (PySequence_Check(item)) {
+            PyObject *subitem0 = PySequence_ITEM(item, 0);
+            PyObject *subitem1 = PySequence_ITEM(item, 1);
+            PyObject *subitem2 = PySequence_ITEM(item, 2);
+            pta.push_back(LVecBase3i((int)PyLongOrInt_AS_LONG(subitem0),
+                                     (int)PyLongOrInt_AS_LONG(subitem1),
+                                     (int)PyLongOrInt_AS_LONG(subitem2)));
+            Py_DECREF(subitem0);
+            Py_DECREF(subitem1);
+            Py_DECREF(subitem2);
+          } else {
+            pta.push_back((int)PyLongOrInt_AS_LONG(item));
+          }
+        }
+        _this->_stored_ptr = pta;
+      }
+      break;
+
+    case 4:
+      if (has_float) {
+        PTA_LVecBase4f pta;
+        pta.reserve(num_items);
+        for (Py_ssize_t i = 0; i < num_items; ++i) {
+          PyObject *item = items[i];
+          if (PySequence_Check(item)) {
+            PyObject *subitem0 = PySequence_ITEM(item, 0);
+            PyObject *subitem1 = PySequence_ITEM(item, 1);
+            PyObject *subitem2 = PySequence_ITEM(item, 2);
+            PyObject *subitem3 = PySequence_ITEM(item, 3);
+            pta.push_back(LVecBase4f(PyFloat_AsDouble(subitem0),
+                                     PyFloat_AsDouble(subitem1),
+                                     PyFloat_AsDouble(subitem2),
+                                     PyFloat_AsDouble(subitem3)));
+            Py_DECREF(subitem0);
+            Py_DECREF(subitem1);
+            Py_DECREF(subitem2);
+            Py_DECREF(subitem3);
+          } else {
+            pta.push_back(PyFloat_AsDouble(item));
+          }
+        }
+        _this->_stored_ptr = pta;
+      } else {
+        PTA_LVecBase4i pta;
+        pta.reserve(num_items);
+        for (Py_ssize_t i = 0; i < num_items; ++i) {
+          PyObject *item = items[i];
+          if (PySequence_Check(item)) {
+            PyObject *subitem0 = PySequence_ITEM(item, 0);
+            PyObject *subitem1 = PySequence_ITEM(item, 1);
+            PyObject *subitem2 = PySequence_ITEM(item, 2);
+            PyObject *subitem3 = PySequence_ITEM(item, 3);
+            pta.push_back(LVecBase4i((int)PyLongOrInt_AS_LONG(subitem0),
+                                     (int)PyLongOrInt_AS_LONG(subitem1),
+                                     (int)PyLongOrInt_AS_LONG(subitem2),
+                                     (int)PyLongOrInt_AS_LONG(subitem3)));
+            Py_DECREF(subitem0);
+            Py_DECREF(subitem1);
+            Py_DECREF(subitem2);
+            Py_DECREF(subitem3);
+          } else {
+            pta.push_back((int)PyLongOrInt_AS_LONG(item));
+          }
+        }
+        _this->_stored_ptr = pta;
+      }
+      break;
+
+    case 0:
+      Dtool_Raise_TypeError("sequence passed to ShaderInput contains an empty sequence");
+      break;
+
+    default:
+      Dtool_Raise_TypeError("sequence passed to ShaderInput contains a sequence of more than 4 elements");
+      break;
+    }
+
+    _this->_type = ShaderInput::M_numeric;
+
+    Py_DECREF(fast);
+
+  } else {
+    Dtool_Raise_TypeError("unknown type passed to ShaderInput");
+  }
+}
+
+#endif  // HAVE_PYTHON

+ 37 - 0
panda/src/pgraph/shaderInput_ext.h

@@ -0,0 +1,37 @@
+/**
+ * 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."
+ *
+ * @file shaderInput_ext.h
+ * @author rdb
+ * @date 2017-10-06
+ */
+
+#ifndef SHADERINPUT_EXT_H
+#define SHADERINPUT_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "shaderInput.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for NodePath, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<ShaderInput> : public ExtensionBase<ShaderInput> {
+public:
+  void __init__(CPT_InternalName name, PyObject *value, int priority=0);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // SHADERINPUT_EXT_H

+ 10 - 1
panda/src/pgraph/stateMunger.I

@@ -16,6 +16,15 @@
  */
  */
 INLINE StateMunger::
 INLINE StateMunger::
 StateMunger(GraphicsStateGuardianBase *gsg) :
 StateMunger(GraphicsStateGuardianBase *gsg) :
-  GeomMunger(gsg)
+  GeomMunger(gsg),
+  _should_munge_state(false)
 {
 {
 }
 }
+
+/**
+ * Returns true if this munger has something interesting to do to the state.
+ */
+INLINE bool StateMunger::
+should_munge_state() const {
+  return _should_munge_state;
+}

+ 9 - 4
panda/src/pgraph/stateMunger.cxx

@@ -27,15 +27,20 @@ StateMunger::
  */
  */
 CPT(RenderState) StateMunger::
 CPT(RenderState) StateMunger::
 munge_state(const RenderState *state) {
 munge_state(const RenderState *state) {
-  int mi = _state_map.find(state);
+  RenderState::MungedStates &munged_states = state->_munged_states;
+
+  int id = get_gsg()->_id;
+  int mi = munged_states.find(id);
   if (mi != -1) {
   if (mi != -1) {
-    if (!_state_map.get_data(mi).was_deleted()) {
-      return _state_map.get_data(mi).p();
+    if (!munged_states.get_data(mi).was_deleted()) {
+      return munged_states.get_data(mi).p();
+    } else {
+      munged_states.remove_element(mi);
     }
     }
   }
   }
 
 
   CPT(RenderState) result = munge_state_impl(state);
   CPT(RenderState) result = munge_state_impl(state);
-  _state_map.store(state, result.p());
+  munged_states.store(id, result.p());
 
 
   return result;
   return result;
 }
 }

+ 3 - 2
panda/src/pgraph/stateMunger.h

@@ -30,11 +30,12 @@ public:
   virtual ~StateMunger();
   virtual ~StateMunger();
   CPT(RenderState) munge_state(const RenderState *state);
   CPT(RenderState) munge_state(const RenderState *state);
 
 
+  INLINE bool should_munge_state() const;
+
 protected:
 protected:
   virtual CPT(RenderState) munge_state_impl(const RenderState *state);
   virtual CPT(RenderState) munge_state_impl(const RenderState *state);
 
 
-  typedef WeakKeyHashMap<RenderState, WCPT(RenderState) > StateMap;
-  StateMap _state_map;
+  bool _should_munge_state;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 6 - 0
panda/src/pgraph/textureAttrib.h

@@ -64,12 +64,18 @@ PUBLISHED:
 
 
   int find_on_stage(const TextureStage *stage) const;
   int find_on_stage(const TextureStage *stage) const;
 
 
+  MAKE_SEQ_PROPERTY(on_stages, get_num_on_stages, get_on_stage);
+  MAKE_MAP_PROPERTY(on_stages, find_on_stage, get_on_stage);
+
   INLINE int get_num_off_stages() const;
   INLINE int get_num_off_stages() const;
   INLINE TextureStage *get_off_stage(int n) const;
   INLINE TextureStage *get_off_stage(int n) const;
   MAKE_SEQ(get_off_stages, get_num_off_stages, get_off_stage);
   MAKE_SEQ(get_off_stages, get_num_off_stages, get_off_stage);
   INLINE bool has_off_stage(TextureStage *stage) const;
   INLINE bool has_off_stage(TextureStage *stage) const;
   INLINE bool has_all_off() const;
   INLINE bool has_all_off() const;
 
 
+  MAKE_SEQ_PROPERTY(off_stages, get_num_off_stages, get_off_stage);
+  MAKE_MAP_PROPERTY(off_stages, has_off_stage, get_off_stage);
+
   INLINE bool is_identity() const;
   INLINE bool is_identity() const;
 
 
   CPT(RenderAttrib) add_on_stage(TextureStage *stage, Texture *tex, int override = 0) const;
   CPT(RenderAttrib) add_on_stage(TextureStage *stage, Texture *tex, int override = 0) const;

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

@@ -1512,7 +1512,7 @@ return_unique(TransformState *state) {
     // deleted while it's in it.
     // deleted while it's in it.
     state->cache_ref();
     state->cache_ref();
   }
   }
-  si = _states->store(state, Empty());
+  si = _states->store(state, nullptr);
 
 
   // Save the index and return the input state.
   // Save the index and return the input state.
   state->_saved_entry = si;
   state->_saved_entry = si;

+ 1 - 3
panda/src/pgraph/transformState.h

@@ -253,9 +253,7 @@ private:
   // cache, which is encoded in _composition_cache and
   // cache, which is encoded in _composition_cache and
   // _invert_composition_cache.
   // _invert_composition_cache.
   static LightReMutex *_states_lock;
   static LightReMutex *_states_lock;
-  class Empty {
-  };
-  typedef SimpleHashMap<const TransformState *, Empty, indirect_equals_hash<const TransformState *> > States;
+  typedef SimpleHashMap<const TransformState *, nullptr_t, indirect_equals_hash<const TransformState *> > States;
   static States *_states;
   static States *_states;
   static CPT(TransformState) _identity_state;
   static CPT(TransformState) _identity_state;
   static CPT(TransformState) _invalid_state;
   static CPT(TransformState) _invalid_state;

+ 218 - 123
panda/src/pgraphnodes/shaderGenerator.cxx

@@ -51,6 +51,14 @@ TypeHandle ShaderGenerator::_type_handle;
 
 
 #ifdef HAVE_CG
 #ifdef HAVE_CG
 
 
+#define PACK_COMBINE(src0, op0, src1, op1, src2, op2) ( \
+  ((uint16_t)src0) | ((((uint16_t)op0 - 1u) & 3u) << 3u) | \
+  ((uint16_t)src1 << 5u) | ((((uint16_t)op1 - 1u) & 3u) << 8u) | \
+  ((uint16_t)src2 << 10u) | ((((uint16_t)op2 - 1u) & 3u) << 13u))
+
+#define UNPACK_COMBINE_SRC(from, n) (TextureStage::CombineSource)((from >> ((uint16_t)n * 5u)) & 7u)
+#define UNPACK_COMBINE_OP(from, n) (TextureStage::CombineOperand)(((from >> (((uint16_t)n * 5u) + 3u)) & 3u) + 1u)
+
 static PStatCollector lookup_collector("*:Munge:ShaderGen:Lookup");
 static PStatCollector lookup_collector("*:Munge:ShaderGen:Lookup");
 static PStatCollector synthesize_collector("*:Munge:ShaderGen:Synthesize");
 static PStatCollector synthesize_collector("*:Munge:ShaderGen:Synthesize");
 
 
@@ -60,7 +68,7 @@ static PStatCollector synthesize_collector("*:Munge:ShaderGen:Synthesize");
  * shader generator belongs.
  * shader generator belongs.
  */
  */
 ShaderGenerator::
 ShaderGenerator::
-ShaderGenerator(GraphicsStateGuardianBase *gsg) {
+ShaderGenerator(const GraphicsStateGuardianBase *gsg) {
   // The ATTR# input semantics seem to map to generic vertex attributes in
   // The ATTR# input semantics seem to map to generic vertex attributes in
   // both arbvp1 and glslv, which behave more consistently.  However, they
   // both arbvp1 and glslv, which behave more consistently.  However, they
   // don't exist in Direct3D 9.  Use this silly little check for now.
   // don't exist in Direct3D 9.  Use this silly little check for now.
@@ -298,10 +306,17 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) {
     Texture *tex = texture->get_on_texture(stage);
     Texture *tex = texture->get_on_texture(stage);
     nassertd(tex != nullptr) continue;
     nassertd(tex != nullptr) continue;
 
 
+    // Mark this TextureStage as having been used by the shader generator, so
+    // that the next time its properties change, it will cause the state to be
+    // rehashed to ensure that the shader is regenerated if needed.
+    stage->mark_used_by_auto_shader();
+
     ShaderKey::TextureInfo info;
     ShaderKey::TextureInfo info;
     info._type = tex->get_texture_type();
     info._type = tex->get_texture_type();
     info._mode = stage->get_mode();
     info._mode = stage->get_mode();
     info._flags = 0;
     info._flags = 0;
+    info._combine_rgb = 0u;
+    info._combine_alpha = 0u;
 
 
     // While we look at the mode, determine whether we need to change the mode
     // While we look at the mode, determine whether we need to change the mode
     // in order to reflect disabled features.
     // in order to reflect disabled features.
@@ -357,6 +372,40 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) {
         info._flags = ShaderKey::TF_map_normal | ShaderKey::TF_map_gloss;
         info._flags = ShaderKey::TF_map_normal | ShaderKey::TF_map_gloss;
       }
       }
       break;
       break;
+
+    case TextureStage::M_combine:
+      // If we have this rare, special mode, we encode all these extra
+      // parameters as flags to prevent bloating the shader key.
+      info._flags |= (uint32_t)stage->get_combine_rgb_mode() << ShaderKey::TF_COMBINE_RGB_MODE_SHIFT;
+      info._flags |= (uint32_t)stage->get_combine_alpha_mode() << ShaderKey::TF_COMBINE_ALPHA_MODE_SHIFT;
+      if (stage->get_rgb_scale() == 2) {
+        info._flags |= ShaderKey::TF_rgb_scale_2;
+      }
+      if (stage->get_rgb_scale() == 4) {
+        info._flags |= ShaderKey::TF_rgb_scale_4;
+      }
+      if (stage->get_alpha_scale() == 2) {
+        info._flags |= ShaderKey::TF_alpha_scale_2;
+      }
+      if (stage->get_alpha_scale() == 4) {
+        info._flags |= ShaderKey::TF_alpha_scale_4;
+      }
+      info._combine_rgb = PACK_COMBINE(
+        stage->get_combine_rgb_source0(), stage->get_combine_rgb_operand0(),
+        stage->get_combine_rgb_source1(), stage->get_combine_rgb_operand1(),
+        stage->get_combine_rgb_source2(), stage->get_combine_rgb_operand2());
+      info._combine_alpha = PACK_COMBINE(
+        stage->get_combine_alpha_source0(), stage->get_combine_alpha_operand0(),
+        stage->get_combine_alpha_source1(), stage->get_combine_alpha_operand1(),
+        stage->get_combine_alpha_source2(), stage->get_combine_alpha_operand2());
+
+      if (stage->uses_primary_color()) {
+        info._flags |= ShaderKey::TF_uses_primary_color;
+      }
+      if (stage->uses_last_saved_result()) {
+        info._flags |= ShaderKey::TF_uses_last_saved_result;
+      }
+      break;
     }
     }
 
 
     // In fact, perhaps this stage should be disabled altogether?
     // In fact, perhaps this stage should be disabled altogether?
@@ -417,17 +466,31 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) {
       info._gen_mode = TexGenAttrib::M_off;
       info._gen_mode = TexGenAttrib::M_off;
     }
     }
 
 
-    // If we have this rare, special mode, just include a pointer to the
-    // TextureStage object, because I can't be bothered to bloat the shader
-    // key with all these extra relevant properties.
-    if (stage->get_mode() == TextureStage::M_combine) {
-      info._stage = stage;
+    // Does this stage require saving its result?
+    if (stage->get_saved_result()) {
+      info._flags |= ShaderKey::TF_saved_result;
+    }
+
+    // Does this stage need a texcolor_# input?
+    if (stage->uses_color()) {
+      info._flags |= ShaderKey::TF_uses_color;
     }
     }
 
 
     key._textures.push_back(info);
     key._textures.push_back(info);
     key._texture_flags |= info._flags;
     key._texture_flags |= info._flags;
   }
   }
 
 
+  // Does nothing use the saved result?  If so, don't bother saving it.
+  if ((key._texture_flags & ShaderKey::TF_uses_last_saved_result) == 0 &&
+      (key._texture_flags & ShaderKey::TF_saved_result) != 0) {
+
+    pvector<ShaderKey::TextureInfo>::iterator it;
+    for (it = key._textures.begin(); it != key._textures.end(); ++it) {
+      (*it)._flags &= ~ShaderKey::TF_saved_result;
+    }
+    key._texture_flags &= ~ShaderKey::TF_saved_result;
+  }
+
   // Decide whether to separate ambient and diffuse calculations.
   // Decide whether to separate ambient and diffuse calculations.
   if (have_ambient) {
   if (have_ambient) {
     if (key._material_flags & Material::F_ambient) {
     if (key._material_flags & Material::F_ambient) {
@@ -463,6 +526,74 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) {
   }
   }
 }
 }
 
 
+/**
+ * Rehashes all the states with generated shaders, removing the ones that are
+ * no longer fresh.
+ *
+ * Call this if certain state has changed in such a way as to require a rerun
+ * of the shader generator.  This should be rare because in most cases, the
+ * shader generator will automatically regenerate shaders as necessary.
+ */
+void ShaderGenerator::
+rehash_generated_shaders() {
+  LightReMutexHolder holder(*RenderState::_states_lock);
+
+  // With uniquify-states turned on, we can actually go through all the states
+  // and check whether their generated shader is still OK.
+  size_t size = RenderState::_states->get_num_entries();
+  for (size_t si = 0; si < size; ++si) {
+    const RenderState *state = RenderState::_states->get_key(si);
+
+    if (state->_generated_shader != nullptr) {
+      ShaderKey key;
+      analyze_renderstate(key, state);
+
+      GeneratedShaders::const_iterator si;
+      si = _generated_shaders.find(key);
+      if (si != _generated_shaders.end()) {
+        if (si->second != state->_generated_shader) {
+          state->_generated_shader = si->second;
+          state->_munged_states.clear();
+        }
+      } else {
+        // We have not yet generated a shader for this modified state.
+        state->_generated_shader.clear();
+        state->_munged_states.clear();
+      }
+    }
+  }
+
+  // If we don't have uniquify-states, however, the above list won't contain
+  // all the state.  We can change a global seq value to require Panda to
+  // rehash the states the next time it tries to render an object with it.
+  if (!uniquify_states) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+  }
+}
+
+/**
+ * Removes all previously generated shaders, requiring all shaders to be
+ * regenerated.  Does not clear cache of compiled shaders.
+ */
+void ShaderGenerator::
+clear_generated_shaders() {
+  LightReMutexHolder holder(*RenderState::_states_lock);
+
+  size_t size = RenderState::_states->get_num_entries();
+  for (size_t si = 0; si < size; ++si) {
+    const RenderState *state = RenderState::_states->get_key(si);
+    state->_generated_shader.clear();
+  }
+
+  _generated_shaders.clear();
+
+  // If we don't have uniquify-states, we can't clear all the ShaderAttribs
+  // that are cached on the states, but we can simulate the effect of that.
+  if (!uniquify_states) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+  }
+}
+
 /**
 /**
  * This is the routine that implements the next-gen fixed function pipeline by
  * This is the routine that implements the next-gen fixed function pipeline by
  * synthesizing a shader.  It also takes care of setting up any buffers needed
  * synthesizing a shader.  It also takes care of setting up any buffers needed
@@ -827,7 +958,7 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
       text << "\t uniform float4x4 texmat_" << i << ",\n";
       text << "\t uniform float4x4 texmat_" << i << ",\n";
     }
     }
 
 
-    if (tex._mode == TextureStage::M_blend) {
+    if (tex._flags & ShaderKey::TF_uses_color) {
       text << "\t uniform float4 texcolor_" << i << ",\n";
       text << "\t uniform float4 texcolor_" << i << ",\n";
     }
     }
   }
   }
@@ -1251,24 +1382,12 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
     }
     }
   }
   }
 
 
-  // Loop first to see if something is using primary_color or
-  // last_saved_result.
-  bool have_saved_result = false;
-  bool have_primary_color = false;
-  for (size_t i = 0; i < key._textures.size(); ++i) {
-    const ShaderKey::TextureInfo &tex = key._textures[i];
-    if (tex._stage == nullptr) {
-      continue;
-    }
-
-    if (tex._stage->uses_primary_color() && !have_primary_color) {
-      text << "\t float4 primary_color = result;\n";
-      have_primary_color = true;
-    }
-    if (tex._stage->uses_last_saved_result() && !have_saved_result) {
-      text << "\t float4 last_saved_result = result;\n";
-      have_saved_result = true;
-    }
+  // Store these if any stages will use it.
+  if (key._texture_flags & ShaderKey::TF_uses_primary_color) {
+    text << "\t float4 primary_color = result;\n";
+  }
+  if (key._texture_flags & ShaderKey::TF_uses_last_saved_result) {
+    text << "\t float4 last_saved_result = result;\n";
   }
   }
 
 
   // Now loop through the textures to compose our magic blending formulas.
   // Now loop through the textures to compose our magic blending formulas.
@@ -1310,24 +1429,21 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
       }
       }
       break;
       break;
     case TextureStage::M_combine:
     case TextureStage::M_combine:
-      // Only in the case of M_combine have we filled in the _stage pointer.
       text << "\t result.rgb = ";
       text << "\t result.rgb = ";
-      if (tex._stage->get_combine_rgb_mode() != TextureStage::CM_undefined) {
-        text << combine_mode_as_string(tex._stage, tex._stage->get_combine_rgb_mode(), false, i);
-      } else {
-        text << "tex" << i << ".rgb";
+      text << combine_mode_as_string(tex, (TextureStage::CombineMode)((tex._flags & ShaderKey::TF_COMBINE_RGB_MODE_MASK) >> ShaderKey::TF_COMBINE_RGB_MODE_SHIFT), false, i);
+      if (tex._flags & ShaderKey::TF_rgb_scale_2) {
+        text << " * 2";
       }
       }
-      if (tex._stage->get_rgb_scale() != 1) {
-        text << " * " << tex._stage->get_rgb_scale();
+      if (tex._flags & ShaderKey::TF_rgb_scale_4) {
+        text << " * 4";
       }
       }
       text << ";\n\t result.a = ";
       text << ";\n\t result.a = ";
-      if (tex._stage->get_combine_alpha_mode() != TextureStage::CM_undefined) {
-        text << combine_mode_as_string(tex._stage, tex._stage->get_combine_alpha_mode(), true, i);
-      } else {
-        text << "tex" << i << ".a";
+      text << combine_mode_as_string(tex, (TextureStage::CombineMode)((tex._flags & ShaderKey::TF_COMBINE_ALPHA_MODE_MASK) >> ShaderKey::TF_COMBINE_ALPHA_MODE_SHIFT), false, i);
+      if (tex._flags & ShaderKey::TF_alpha_scale_2) {
+        text << " * 2";
       }
       }
-      if (tex._stage->get_alpha_scale() != 1) {
-        text << " * " << tex._stage->get_alpha_scale();
+      if (tex._flags & ShaderKey::TF_alpha_scale_4) {
+        text << " * 4";
       }
       }
       text << ";\n";
       text << ";\n";
       break;
       break;
@@ -1337,7 +1453,7 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
     default:
     default:
       break;
       break;
     }
     }
-    if ((tex._flags & ShaderKey::TF_saved_result) != 0 && have_saved_result) {
+    if (tex._flags & ShaderKey::TF_saved_result) {
       text << "\t last_saved_result = result;\n";
       text << "\t last_saved_result = result;\n";
     }
     }
   }
   }
@@ -1464,53 +1580,53 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
  * This 'synthesizes' a combine mode into a string.
  * This 'synthesizes' a combine mode into a string.
  */
  */
 const string ShaderGenerator::
 const string ShaderGenerator::
-combine_mode_as_string(CPT(TextureStage) stage, TextureStage::CombineMode c_mode, bool alpha, short texindex) {
+combine_mode_as_string(const ShaderKey::TextureInfo &info, TextureStage::CombineMode c_mode, bool alpha, short texindex) {
   ostringstream text;
   ostringstream text;
   switch (c_mode) {
   switch (c_mode) {
-    case TextureStage::CM_modulate:
-      text << combine_source_as_string(stage, 0, alpha, alpha, texindex);
-      text << " * ";
-      text << combine_source_as_string(stage, 1, alpha, alpha, texindex);
-      break;
-    case TextureStage::CM_add:
-      text << combine_source_as_string(stage, 0, alpha, alpha, texindex);
-      text << " + ";
-      text << combine_source_as_string(stage, 1, alpha, alpha, texindex);
-      break;
-    case TextureStage::CM_add_signed:
-      text << combine_source_as_string(stage, 0, alpha, alpha, texindex);
-      text << " + ";
-      text << combine_source_as_string(stage, 1, alpha, alpha, texindex);
-      if (alpha) {
-        text << " - 0.5";
-      } else {
-        text << " - float3(0.5, 0.5, 0.5)";
-      }
-      break;
-    case TextureStage::CM_interpolate:
-      text << "lerp(";
-      text << combine_source_as_string(stage, 1, alpha, alpha, texindex);
-      text << ", ";
-      text << combine_source_as_string(stage, 0, alpha, alpha, texindex);
-      text << ", ";
-      text << combine_source_as_string(stage, 2, alpha, true, texindex);
-      text << ")";
-      break;
-    case TextureStage::CM_subtract:
-      text << combine_source_as_string(stage, 0, alpha, alpha, texindex);
-      text << " + ";
-      text << combine_source_as_string(stage, 1, alpha, alpha, texindex);
-      break;
-    case TextureStage::CM_dot3_rgb:
-      pgraphnodes_cat.error() << "TextureStage::CombineMode DOT3_RGB not yet supported in per-pixel mode.\n";
-      break;
-    case TextureStage::CM_dot3_rgba:
-      pgraphnodes_cat.error() << "TextureStage::CombineMode DOT3_RGBA not yet supported in per-pixel mode.\n";
-      break;
-    case TextureStage::CM_replace:
-    default: // Not sure if this is correct as default value.
-      text << combine_source_as_string(stage, 0, alpha, alpha, texindex);
-      break;
+  case TextureStage::CM_modulate:
+    text << combine_source_as_string(info, 0, alpha, alpha, texindex);
+    text << " * ";
+    text << combine_source_as_string(info, 1, alpha, alpha, texindex);
+    break;
+  case TextureStage::CM_add:
+    text << combine_source_as_string(info, 0, alpha, alpha, texindex);
+    text << " + ";
+    text << combine_source_as_string(info, 1, alpha, alpha, texindex);
+    break;
+  case TextureStage::CM_add_signed:
+    text << combine_source_as_string(info, 0, alpha, alpha, texindex);
+    text << " + ";
+    text << combine_source_as_string(info, 1, alpha, alpha, texindex);
+    if (alpha) {
+      text << " - 0.5";
+    } else {
+      text << " - float3(0.5, 0.5, 0.5)";
+    }
+    break;
+  case TextureStage::CM_interpolate:
+    text << "lerp(";
+    text << combine_source_as_string(info, 1, alpha, alpha, texindex);
+    text << ", ";
+    text << combine_source_as_string(info, 0, alpha, alpha, texindex);
+    text << ", ";
+    text << combine_source_as_string(info, 2, alpha, true, texindex);
+    text << ")";
+    break;
+  case TextureStage::CM_subtract:
+    text << combine_source_as_string(info, 0, alpha, alpha, texindex);
+    text << " + ";
+    text << combine_source_as_string(info, 1, alpha, alpha, texindex);
+    break;
+  case TextureStage::CM_dot3_rgb:
+    pgraphnodes_cat.error() << "TextureStage::CombineMode DOT3_RGB not yet supported in per-pixel mode.\n";
+    break;
+  case TextureStage::CM_dot3_rgba:
+    pgraphnodes_cat.error() << "TextureStage::CombineMode DOT3_RGBA not yet supported in per-pixel mode.\n";
+    break;
+  case TextureStage::CM_replace:
+  default: // Not sure if this is correct as default value.
+    text << combine_source_as_string(info, 0, alpha, alpha, texindex);
+    break;
   }
   }
   return text.str();
   return text.str();
 }
 }
@@ -1519,39 +1635,15 @@ combine_mode_as_string(CPT(TextureStage) stage, TextureStage::CombineMode c_mode
  * This 'synthesizes' a combine source into a string.
  * This 'synthesizes' a combine source into a string.
  */
  */
 const string ShaderGenerator::
 const string ShaderGenerator::
-combine_source_as_string(CPT(TextureStage) stage, short num, bool alpha, bool single_value, short texindex) {
-  TextureStage::CombineSource c_src = TextureStage::CS_undefined;
-  TextureStage::CombineOperand c_op = TextureStage::CO_undefined;
-  if (alpha) {
-    switch (num) {
-      case 0:
-        c_src = stage->get_combine_alpha_source0();
-        c_op = stage->get_combine_alpha_operand0();
-        break;
-      case 1:
-        c_src = stage->get_combine_alpha_source1();
-        c_op = stage->get_combine_alpha_operand1();
-        break;
-      case 2:
-        c_src = stage->get_combine_alpha_source2();
-        c_op = stage->get_combine_alpha_operand2();
-        break;
-    }
+combine_source_as_string(const ShaderKey::TextureInfo &info, short num, bool alpha, bool single_value, short texindex) {
+  TextureStage::CombineSource c_src;
+  TextureStage::CombineOperand c_op;
+  if (!alpha) {
+    c_src = UNPACK_COMBINE_SRC(info._combine_rgb, num);
+    c_op = UNPACK_COMBINE_OP(info._combine_rgb, num);
   } else {
   } else {
-    switch (num) {
-      case 0:
-        c_src = stage->get_combine_rgb_source0();
-        c_op = stage->get_combine_rgb_operand0();
-        break;
-      case 1:
-        c_src = stage->get_combine_rgb_source1();
-        c_op = stage->get_combine_rgb_operand1();
-        break;
-      case 2:
-        c_src = stage->get_combine_rgb_source2();
-        c_op = stage->get_combine_rgb_operand2();
-        break;
-    }
+    c_src = UNPACK_COMBINE_SRC(info._combine_alpha, num);
+    c_op = UNPACK_COMBINE_OP(info._combine_alpha, num);
   }
   }
   ostringstream csource;
   ostringstream csource;
   if (c_op == TextureStage::CO_one_minus_src_color ||
   if (c_op == TextureStage::CO_one_minus_src_color ||
@@ -1562,10 +1654,9 @@ combine_source_as_string(CPT(TextureStage) stage, short num, bool alpha, bool si
     case TextureStage::CS_texture:
     case TextureStage::CS_texture:
       csource << "tex" << texindex;
       csource << "tex" << texindex;
       break;
       break;
-    case TextureStage::CS_constant: {
-      LVecBase4 c = stage->get_color();
-      csource << "float4(" << c[0] << ", " << c[1] << ", " << c[2] << ", " << c[3] << ")";
-      break; }
+    case TextureStage::CS_constant:
+      csource << "texcolor_" << texindex;
+      break;
     case TextureStage::CS_primary_color:
     case TextureStage::CS_primary_color:
       csource << "primary_color";
       csource << "primary_color";
       break;
       break;
@@ -1684,8 +1775,11 @@ operator < (const ShaderKey &other) const {
     if (tex._flags != other_tex._flags) {
     if (tex._flags != other_tex._flags) {
       return tex._flags < other_tex._flags;
       return tex._flags < other_tex._flags;
     }
     }
-    if (tex._stage != other_tex._stage) {
-      return tex._stage < other_tex._stage;
+    if (tex._combine_rgb != other_tex._combine_rgb) {
+      return tex._combine_rgb < other_tex._combine_rgb;
+    }
+    if (tex._combine_alpha != other_tex._combine_alpha) {
+      return tex._combine_alpha < other_tex._combine_alpha;
     }
     }
   }
   }
   if (_lights.size() != other._lights.size()) {
   if (_lights.size() != other._lights.size()) {
@@ -1759,7 +1853,8 @@ operator == (const ShaderKey &other) const {
         tex._mode != other_tex._mode ||
         tex._mode != other_tex._mode ||
         tex._gen_mode != other_tex._gen_mode ||
         tex._gen_mode != other_tex._gen_mode ||
         tex._flags != other_tex._flags ||
         tex._flags != other_tex._flags ||
-        tex._stage != other_tex._stage) {
+        tex._combine_rgb != other_tex._combine_rgb ||
+        tex._combine_alpha != other_tex._combine_alpha) {
       return false;
       return false;
     }
     }
   }
   }

+ 34 - 19
panda/src/pgraphnodes/shaderGenerator.h

@@ -65,18 +65,15 @@ class GeomVertexAnimationSpec;
  */
  */
 class EXPCL_PANDA_PGRAPHNODES ShaderGenerator : public TypedReferenceCount {
 class EXPCL_PANDA_PGRAPHNODES ShaderGenerator : public TypedReferenceCount {
 PUBLISHED:
 PUBLISHED:
-  ShaderGenerator(GraphicsStateGuardianBase *gsg);
+  ShaderGenerator(const GraphicsStateGuardianBase *gsg);
   virtual ~ShaderGenerator();
   virtual ~ShaderGenerator();
   virtual CPT(ShaderAttrib) synthesize_shader(const RenderState *rs,
   virtual CPT(ShaderAttrib) synthesize_shader(const RenderState *rs,
                                               const GeomVertexAnimationSpec &anim);
                                               const GeomVertexAnimationSpec &anim);
 
 
-protected:
-  static const string combine_mode_as_string(CPT(TextureStage) stage,
-                      TextureStage::CombineMode c_mode, bool alpha, short texindex);
-  static const string combine_source_as_string(CPT(TextureStage) stage,
-                         short num, bool alpha, bool single_value, short texindex);
-  static const string texture_type_as_string(Texture::TextureType ttype);
+  void rehash_generated_shaders();
+  void clear_generated_shaders();
 
 
+protected:
   // Shader register allocation:
   // Shader register allocation:
 
 
   bool _use_generic_attr;
   bool _use_generic_attr;
@@ -101,15 +98,28 @@ protected:
 
 
     GeomVertexAnimationSpec _anim_spec;
     GeomVertexAnimationSpec _anim_spec;
     enum TextureFlags {
     enum TextureFlags {
-      TF_has_rgb = 1,
-      TF_has_alpha = 2,
-      TF_has_texscale = 4,
-      TF_has_texmat = 8,
-      TF_saved_result = 16,
-      TF_map_normal = 32,
-      TF_map_height = 64,
-      TF_map_glow = 128,
-      TF_map_gloss = 256,
+      TF_has_rgb      = 0x001,
+      TF_has_alpha    = 0x002,
+      TF_has_texscale = 0x004,
+      TF_has_texmat   = 0x008,
+      TF_saved_result = 0x010,
+      TF_map_normal   = 0x020,
+      TF_map_height   = 0x040,
+      TF_map_glow     = 0x080,
+      TF_map_gloss    = 0x100,
+      TF_uses_color   = 0x200,
+      TF_uses_primary_color = 0x400,
+      TF_uses_last_saved_result = 0x800,
+
+      TF_rgb_scale_2 = 0x1000,
+      TF_rgb_scale_4 = 0x2000,
+      TF_alpha_scale_2 = 0x4000,
+      TF_alpha_scale_4 = 0x8000,
+
+      TF_COMBINE_RGB_MODE_SHIFT = 16,
+      TF_COMBINE_RGB_MODE_MASK = 0x0000f0000,
+      TF_COMBINE_ALPHA_MODE_SHIFT = 20,
+      TF_COMBINE_ALPHA_MODE_MASK = 0x000f00000,
     };
     };
 
 
     ColorAttrib::Type _color_type;
     ColorAttrib::Type _color_type;
@@ -122,9 +132,8 @@ protected:
       TextureStage::Mode _mode;
       TextureStage::Mode _mode;
       TexGenAttrib::Mode _gen_mode;
       TexGenAttrib::Mode _gen_mode;
       int _flags;
       int _flags;
-
-      // Stored only if combine modes / blend color is used
-      CPT(TextureStage) _stage;
+      uint16_t _combine_rgb;
+      uint16_t _combine_alpha;
     };
     };
     pvector<TextureInfo> _textures;
     pvector<TextureInfo> _textures;
 
 
@@ -159,6 +168,12 @@ protected:
 
 
   void analyze_renderstate(ShaderKey &key, const RenderState *rs);
   void analyze_renderstate(ShaderKey &key, const RenderState *rs);
 
 
+  static const string combine_mode_as_string(const ShaderKey::TextureInfo &info,
+                      TextureStage::CombineMode c_mode, bool alpha, short texindex);
+  static const string combine_source_as_string(const ShaderKey::TextureInfo &info,
+                         short num, bool alpha, bool single_value, short texindex);
+  static const string texture_type_as_string(Texture::TextureType ttype);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;

+ 20 - 9
panda/src/putil/simpleHashMap.I

@@ -128,7 +128,7 @@ store(const Key &key, const Value &data) {
   }
   }
   if (is_element(index, key)) {
   if (is_element(index, key)) {
     // This element is already in the map; replace the data at that key.
     // This element is already in the map; replace the data at that key.
-    _table[index]._data = data;
+    set_data(index, data);
 #ifdef _DEBUG
 #ifdef _DEBUG
     nassertr(validate(), index);
     nassertr(validate(), index);
 #endif
 #endif
@@ -151,7 +151,7 @@ store(const Key &key, const Value &data) {
       return index;
       return index;
     }
     }
     if (is_element(index, key)) {
     if (is_element(index, key)) {
-      _table[index]._data = data;
+      set_data(index, data);
 #ifdef _DEBUG
 #ifdef _DEBUG
       nassertr(validate(), index);
       nassertr(validate(), index);
 #endif
 #endif
@@ -200,8 +200,7 @@ remove(const Key &key) {
 
 
     // Swap it with the last one, so that we don't get any gaps in the table
     // Swap it with the last one, so that we don't get any gaps in the table
     // of entries.
     // of entries.
-    _table[index]._key = move(_table[last]._key);
-    _table[index]._data = move(_table[last]._data);
+    _table[index] = move(_table[last]);
     index_array[(size_t)other_slot] = index;
     index_array[(size_t)other_slot] = index;
   }
   }
 
 
@@ -311,8 +310,8 @@ get_key(size_t n) const {
 template<class Key, class Value, class Compare>
 template<class Key, class Value, class Compare>
 INLINE const Value &SimpleHashMap<Key, Value, Compare>::
 INLINE const Value &SimpleHashMap<Key, Value, Compare>::
 get_data(size_t n) const {
 get_data(size_t n) const {
-  nassertr(n < _num_entries, _table[n]._data);
-  return _table[n]._data;
+  nassertr(n < _num_entries, _table[n].get_data());
+  return _table[n].get_data();
 }
 }
 
 
 /**
 /**
@@ -323,8 +322,8 @@ get_data(size_t n) const {
 template<class Key, class Value, class Compare>
 template<class Key, class Value, class Compare>
 INLINE Value &SimpleHashMap<Key, Value, Compare>::
 INLINE Value &SimpleHashMap<Key, Value, Compare>::
 modify_data(size_t n) {
 modify_data(size_t n) {
-  nassertr(n < _num_entries, _table[n]._data);
-  return _table[n]._data;
+  nassertr(n < _num_entries, _table[n].modify_data());
+  return _table[n].modify_data();
 }
 }
 
 
 /**
 /**
@@ -336,7 +335,19 @@ template<class Key, class Value, class Compare>
 INLINE void SimpleHashMap<Key, Value, Compare>::
 INLINE void SimpleHashMap<Key, Value, Compare>::
 set_data(size_t n, const Value &data) {
 set_data(size_t n, const Value &data) {
   nassertv(n < _num_entries);
   nassertv(n < _num_entries);
-  _table[n]._data = data;
+  _table[n].set_data(data);
+}
+
+/**
+ * Changes the data for the nth entry of the table.
+ *
+ * @param n should be in the range 0 <= n < size().
+ */
+template<class Key, class Value, class Compare>
+INLINE void SimpleHashMap<Key, Value, Compare>::
+set_data(size_t n, Value &&data) {
+  nassertv(n < _num_entries);
+  _table[n].set_data(move(data));
 }
 }
 
 
 /**
 /**

+ 51 - 19
panda/src/putil/simpleHashMap.h

@@ -18,6 +18,52 @@
 #include "pvector.h"
 #include "pvector.h"
 #include "config_util.h"
 #include "config_util.h"
 
 
+/**
+ * Entry in the SimpleHashMap.
+ */
+template<class Key, class Value>
+class SimpleKeyValuePair {
+public:
+  INLINE SimpleKeyValuePair(const Key &key, const Value &data) :
+    _key(key),
+    _data(data) {}
+
+  Key _key;
+
+  ALWAYS_INLINE const Value &get_data() const {
+    return _data;
+  }
+  ALWAYS_INLINE Value &modify_data() {
+    return _data;
+  }
+  ALWAYS_INLINE void set_data(const Value &data) {
+    _data = data;
+  }
+  ALWAYS_INLINE void set_data(Value &&data) {
+    _data = move(data);
+  }
+
+private:
+  Value _data;
+};
+
+/**
+ * Specialisation of SimpleKeyValuePair to not waste memory for nullptr_t
+ * values.  This allows effectively using SimpleHashMap as a set.
+ */
+template<class Key>
+class SimpleKeyValuePair<Key, nullptr_t> {
+public:
+  INLINE SimpleKeyValuePair(const Key &key, nullptr_t data) :
+    _key(key) {}
+
+  Key _key;
+
+  ALWAYS_INLINE_CONSTEXPR static nullptr_t get_data() { return nullptr; }
+  ALWAYS_INLINE_CONSTEXPR static nullptr_t modify_data() { return nullptr; }
+  ALWAYS_INLINE static void set_data(nullptr_t) {}
+};
+
 /**
 /**
  * This template class implements an unordered map of keys to data,
  * This template class implements an unordered map of keys to data,
  * implemented as a hashtable.  It is similar to STL's hash_map, but
  * implemented as a hashtable.  It is similar to STL's hash_map, but
@@ -28,6 +74,8 @@
  * (d) it allows for efficient iteration over the entries,
  * (d) it allows for efficient iteration over the entries,
  * (e) permits removal and resizing during forward iteration, and
  * (e) permits removal and resizing during forward iteration, and
  * (f) it has a constexpr constructor.
  * (f) it has a constexpr constructor.
+ *
+ * It can also be used as a set, by using nullptr_t as Value typename.
  */
  */
 template<class Key, class Value, class Compare = method_hash<Key, less<Key> > >
 template<class Key, class Value, class Compare = method_hash<Key, less<Key> > >
 class SimpleHashMap {
 class SimpleHashMap {
@@ -55,6 +103,7 @@ public:
   INLINE const Value &get_data(size_t n) const;
   INLINE const Value &get_data(size_t n) const;
   INLINE Value &modify_data(size_t n);
   INLINE Value &modify_data(size_t n);
   INLINE void set_data(size_t n, const Value &data);
   INLINE void set_data(size_t n, const Value &data);
+  INLINE void set_data(size_t n, Value &&data);
   void remove_element(size_t n);
   void remove_element(size_t n);
 
 
   INLINE size_t get_num_entries() const;
   INLINE size_t get_num_entries() const;
@@ -67,8 +116,6 @@ public:
   INLINE bool consider_shrink_table();
   INLINE bool consider_shrink_table();
 
 
 private:
 private:
-  class TableEntry;
-
   INLINE size_t get_hash(const Key &key) const;
   INLINE size_t get_hash(const Key &key) const;
   INLINE size_t next_hash(size_t hash) const;
   INLINE size_t next_hash(size_t hash) const;
 
 
@@ -82,23 +129,8 @@ private:
   INLINE bool consider_expand_table();
   INLINE bool consider_expand_table();
   void resize_table(size_t new_size);
   void resize_table(size_t new_size);
 
 
-  class TableEntry {
-  public:
-    INLINE TableEntry(const Key &key, const Value &data) :
-      _key(key),
-      _data(data) {}
-    INLINE TableEntry(const TableEntry &copy) :
-      _key(copy._key),
-      _data(copy._data) {}
-#ifdef USE_MOVE_SEMANTICS
-    INLINE TableEntry(TableEntry &&from) NOEXCEPT :
-      _key(move(from._key)),
-      _data(move(from._data)) {}
-#endif
-    Key _key;
-    Value _data;
-  };
-
+public:
+  typedef SimpleKeyValuePair<Key, Value> TableEntry;
   TableEntry *_table;
   TableEntry *_table;
   DeletedBufferChain *_deleted_chain;
   DeletedBufferChain *_deleted_chain;
   size_t _table_size;
   size_t _table_size;

+ 1 - 1
pandatool/src/eggbase/eggReader.cxx

@@ -193,7 +193,7 @@ handle_args(ProgramBase::Args &args) {
     file_path.append_directory(filename.get_dirname());
     file_path.append_directory(filename.get_dirname());
 
 
     if (_force_complete) {
     if (_force_complete) {
-      if (!file_data.load_externals()) {
+      if (!file_data.load_externals(file_path)) {
         exit(1);
         exit(1);
       }
       }
     }
     }

+ 1 - 1
samples/fireflies/main.py

@@ -338,7 +338,7 @@ class FireflyDemo(ShowBase):
         color_g = random.uniform(0.8, 1.0)
         color_g = random.uniform(0.8, 1.0)
         color_b = min(color_g, random.uniform(0.5, 1.0))
         color_b = min(color_g, random.uniform(0.5, 1.0))
         fly.setColor(color_r, color_g, color_b, 1.0)
         fly.setColor(color_r, color_g, color_b, 1.0)
-        fly.setShaderInput("lightcolor", color_r, color_g, color_b, 1.0)
+        fly.setShaderInput("lightcolor", (color_r, color_g, color_b, 1.0))
         int1 = fly.posInterval(random.uniform(7, 12), pos1, pos2)
         int1 = fly.posInterval(random.uniform(7, 12), pos1, pos2)
         int2 = fly.posInterval(random.uniform(7, 12), pos2, pos1)
         int2 = fly.posInterval(random.uniform(7, 12), pos2, pos1)
         si1 = fly.scaleInterval(random.uniform(0.8, 1.5),
         si1 = fly.scaleInterval(random.uniform(0.8, 1.5),

+ 5 - 5
samples/shadows/advanced.py

@@ -103,7 +103,7 @@ class World(DirectObject):
         self.pandaModel = Actor.Actor('panda-model', {'walk': 'panda-walk4'})
         self.pandaModel = Actor.Actor('panda-model', {'walk': 'panda-walk4'})
         self.pandaModel.reparentTo(self.pandaAxis)
         self.pandaModel.reparentTo(self.pandaAxis)
         self.pandaModel.setPos(9, 0, 0)
         self.pandaModel.setPos(9, 0, 0)
-        self.pandaModel.setShaderInput("scale", 0.01, 0.01, 0.01, 1.0)
+        self.pandaModel.setShaderInput("scale", (0.01, 0.01, 0.01, 1.0))
         self.pandaWalk = self.pandaModel.actorInterval('walk', playRate=1.8)
         self.pandaWalk = self.pandaModel.actorInterval('walk', playRate=1.8)
         self.pandaWalk.loop()
         self.pandaWalk.loop()
         self.pandaMovement = self.pandaAxis.hprInterval(
         self.pandaMovement = self.pandaAxis.hprInterval(
@@ -113,7 +113,7 @@ class World(DirectObject):
         self.teapot = loader.loadModel('teapot')
         self.teapot = loader.loadModel('teapot')
         self.teapot.reparentTo(render)
         self.teapot.reparentTo(render)
         self.teapot.setPos(0, -20, 10)
         self.teapot.setPos(0, -20, 10)
-        self.teapot.setShaderInput("texDisable", 1, 1, 1, 1)
+        self.teapot.setShaderInput("texDisable", (1, 1, 1, 1))
         self.teapotMovement = self.teapot.hprInterval(50, LPoint3(0, 360, 360))
         self.teapotMovement = self.teapot.hprInterval(50, LPoint3(0, 360, 360))
         self.teapotMovement.loop()
         self.teapotMovement.loop()
 
 
@@ -145,9 +145,9 @@ class World(DirectObject):
         # setting up shader
         # setting up shader
         render.setShaderInput('light', self.LCam)
         render.setShaderInput('light', self.LCam)
         render.setShaderInput('Ldepthmap', Ldepthmap)
         render.setShaderInput('Ldepthmap', Ldepthmap)
-        render.setShaderInput('ambient', self.ambient, 0, 0, 1.0)
-        render.setShaderInput('texDisable', 0, 0, 0, 0)
-        render.setShaderInput('scale', 1, 1, 1, 1)
+        render.setShaderInput('ambient', (self.ambient, 0, 0, 1.0))
+        render.setShaderInput('texDisable', (0, 0, 0, 0))
+        render.setShaderInput('scale', (1, 1, 1, 1))
 
 
         # Put a shader on the Light camera.
         # Put a shader on the Light camera.
         lci = NodePath(PandaNode("Light Camera Initializer"))
         lci = NodePath(PandaNode("Light Camera Initializer"))

+ 0 - 1
samples/shadows/basic.py

@@ -80,7 +80,6 @@ class World(DirectObject):
         self.teapot = loader.loadModel('teapot')
         self.teapot = loader.loadModel('teapot')
         self.teapot.reparentTo(render)
         self.teapot.reparentTo(render)
         self.teapot.setPos(0, -20, 10)
         self.teapot.setPos(0, -20, 10)
-        self.teapot.setShaderInput("texDisable", 1, 1, 1, 1)
         self.teapotMovement = self.teapot.hprInterval(50, LPoint3(0, 360, 360))
         self.teapotMovement = self.teapot.hprInterval(50, LPoint3(0, 360, 360))
         self.teapotMovement.loop()
         self.teapotMovement.loop()
 
 

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