Browse Source

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

rdb 8 years ago
parent
commit
fba41dafae
92 changed files with 4493 additions and 2735 deletions
  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));
 #if PY_MAJOR_VERSION >= 3
   } else if (PyUnicode_Check(object)) {
-    char *buffer;
+    const char *buffer;
     Py_ssize_t length;
     buffer = PyUnicode_AsUTF8AndSize(object, &length);
     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> enum_element_type
 %type <u.type> maybe_trailing_return_type
+%type <u.identifier> maybe_comma_identifier
 /*%type <u.type> typedefname*/
 %type <u.identifier> name
 %type <u.identifier> name_no_final
@@ -548,202 +549,264 @@ declaration:
 {
   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);
-  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);
-
   } 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);
   }
 }
         | KW_MAKE_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
   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);
 
   } 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);
     } 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);
-    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);
-      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();
     }
+
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
 }
         | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
   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);
-    length_getter = NULL;
+    length_getter = nullptr;
   }
 
   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);
+    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 ')' ';'
 {
   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);
-    length_getter = NULL;
+    length_getter = nullptr;
   }
 
   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);
+    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);
     } 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);
   }
 }
         | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
   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);
     length_getter = NULL;
   }
 
   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);
+    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);
     } 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);
-    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);
-      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();
     }
+
     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);
-  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);
-  }
 
-  if (hasser && getter) {
+  } else {
     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);
   }
 }
-        | 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);
-  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);
-  }
 
-  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;
-    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);
+    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);
   }
 }
@@ -1723,6 +1786,18 @@ maybe_trailing_return_type:
         ;
 
 
+maybe_comma_identifier:
+        empty
+{
+  $$ = NULL;
+}
+        | ',' IDENTIFIER
+{
+  $$ = $2;
+}
+        ;
+
+
 function_parameter_list:
         empty
 {

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

@@ -18,37 +18,17 @@
  *
  */
 CPPMakeProperty::
-CPPMakeProperty(CPPIdentifier *ident,
-                CPPFunctionGroup *getter, CPPFunctionGroup *setter,
+CPPMakeProperty(CPPIdentifier *ident, Type type,
                 CPPScope *current_scope, const CPPFile &file) :
   CPPDeclaration(file),
   _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;
 }

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

@@ -23,16 +23,72 @@
  * This is a MAKE_PROPERTY() declaration appearing within a class body.  It
  * means to generate a property within Python, replacing (for instance)
  * 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 {
 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);
 
   virtual string get_simple_name() const;
@@ -46,8 +102,7 @@ public:
   virtual CPPMakeProperty *as_make_property();
 
   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 *_has_function;
   CPPFunctionGroup *_get_function;

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

@@ -94,6 +94,14 @@
 #define RETURNS_ALIGNED(x)
 #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
   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_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_MAP_PROPERTY(property_name, ...) __make_map_property(property_name, __VA_ARGS__)
 #define EXTENSION(x) __extension x
 #define EXTEND __extension
 #else
@@ -466,6 +475,7 @@ typedef struct _object PyObject;
 #define MAKE_PROPERTY2(property_name, ...)
 #define MAKE_SEQ(seq_name, num_name, element_name)
 #define MAKE_SEQ_PROPERTY(property_name, ...)
+#define MAKE_MAP_PROPERTY(property_name, ...)
 #define EXTENSION(x)
 #define EXTEND
 #endif

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

@@ -51,6 +51,10 @@ PUBLISHED:
 
   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_PROPERTY(binary_name, get_binary_name, set_binary_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) :
   _name(name),
   _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::
 Property(const InterrogateElement &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)
 {
 }

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

@@ -126,9 +126,9 @@ public:
     Property(const InterrogateElement &ielement);
 
     const InterrogateElement &_ielement;
+    vector<FunctionRemap *> _getter_remaps;
+    vector<FunctionRemap *> _setter_remaps;
     Function *_length_function;
-    Function *_getter;
-    Function *_setter;
     Function *_has_function;
     Function *_clear_function;
     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)
       << "Notify *notify = Notify::ptr();\n";
     indent(out, indent_level)
-      << "if (notify->has_assert_failed()) {\n";
+      << "if (UNLIKELY(notify->has_assert_failed())) {\n";
     indent(out, indent_level + 2)
       << "PyErr_SetString(PyExc_AssertionError, notify->get_assert_error_message().c_str());\n";
     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) {
       Property *property = (*pit);
       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;
       }
 
@@ -2652,8 +2651,7 @@ write_module_class(ostream &out, Object *obj) {
 
       string getter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter";
       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";
       }
 
@@ -3190,8 +3188,7 @@ write_module_class(ostream &out, Object *obj) {
   for (pit = obj->_properties.begin(); pit != obj->_properties.end(); ++pit) {
     Property *property = (*pit);
     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;
     }
 
@@ -3200,8 +3197,7 @@ write_module_class(ostream &out, Object *obj) {
 
     string getter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter";
     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";
     }
 
@@ -4389,57 +4385,6 @@ write_function_forset(ostream &out,
     std::sort(remaps.begin(), remaps.end(), RemapCompareLess);
     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;
     sii = remaps.begin();
     while (sii != remaps.end()) {
@@ -4474,7 +4419,7 @@ write_function_forset(ostream &out,
       write_function_instance(out, remap, min_num_args, max_num_args,
                               expected_params, indent_level + 2,
                               false, false, args_type, return_flags,
-                              check_exceptions, first_pexpr2);
+                              check_exceptions, first_pexpr);
 
       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,
                                 ignore_expected_params, indent_level + 2,
                                 true, false, args_type, return_flags,
-                                check_exceptions, first_pexpr2);
+                                check_exceptions, first_pexpr);
 
         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 {
     // There is only one possible overload with this number of parameters.
     // Just call it.
@@ -4891,7 +4819,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
                                       << default_value->_str.size() << ";\n";
           }
         } 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";
         }
 
@@ -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
           // starts with py_decode_, we take a bytes object instead of a str.
           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";
             indent(out, indent_level + 2) << param_name << "_str = NULL;\n";
             indent(out, indent_level) << "}\n";
@@ -4910,7 +4838,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
               << param_name << "_len);\n";
           }
           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";
           indent(out, indent_level + 2) << param_name << "_str = NULL;\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
     // functions which already check this on their own.  Generated getters
     // 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_setter) {
       out << "#ifndef NDEBUG\n";
       indent(out, indent_level)
         << "Notify *notify = Notify::ptr();\n";
       indent(out, indent_level)
-        << "if (notify->has_assert_failed()) {\n";
+        << "if (UNLIKELY(notify->has_assert_failed())) {\n";
 
       if (manage_return) {
         // 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) << "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.
@@ -6466,19 +6415,21 @@ write_make_seq(ostream &out, Object *obj, const std::string &ClassName,
  */
 void InterfaceMakerPythonNative::
 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());
   std::string cClassName = obj->_itype.get_true_name();
 
   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.
-    FunctionRemap *len_remap = property->_length_function->_remaps.front();
-    vector_string pexprs;
+    len_remap = property->_length_function->_remaps.front();
 
     out << "/**\n"
-           " * sequence length function for property " << cClassName << "::" << ielem.get_name() << "\n"
+           " * sequence length function for property " << ielem.get_scoped_name() << "\n"
            " */\n"
            "static Py_ssize_t Dtool_" + ClassName + "_" + ielem.get_name() + "_Len(PyObject *self) {\n";
     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 << "}\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 <<
-        "/**\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.
-    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) {
         out << "  " << cClassName  << " *local_this = NULL;\n";
         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.
       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) {
         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 <= 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);
         }
       }
@@ -6619,8 +6571,192 @@ write_getset(ostream &out, Object *obj, Property *property) {
       out << "  return -1;\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";
     if (property->_has_this) {
       out << "  nassertr(self != NULL, NULL);\n"
@@ -6629,21 +6765,21 @@ write_getset(ostream &out, Object *obj, Property *property) {
       out << "  Py_XINCREF(self);\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->_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 {
       out << "  wrap->_setitem_func = NULL;\n";
     }
     out << "  return (PyObject *)wrap;\n"
-           "}\n\n";
+            "}\n\n";
 
-  } else if (property->_getter != NULL) {
+  } else {
     // Write out a regular, unwrapped getter.
     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->_const_method) {
@@ -6679,7 +6815,7 @@ write_getset(ostream &out, Object *obj, Property *property) {
     out << "}\n\n";
 
     // 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";
       if (remap->_has_this) {
         out << "  " << cClassName  << " *local_this = NULL;\n";
@@ -6698,7 +6834,7 @@ write_getset(ostream &out, Object *obj, Property *property) {
             << "    return 0;\n";
       } else {
         out << "    Dtool_Raise_TypeError(\"can't delete " << ielem.get_name() << " attribute\");\n"
-               "    return -1;\n";
+                "    return -1;\n";
       }
       out << "  }\n";
 
@@ -6717,9 +6853,9 @@ write_getset(ostream &out, Object *obj, Property *property) {
 
       // Extract only the setters that take one argument.
       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;
         int min_num_args = remap->get_min_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);
     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);
     } else {
       // No use exporting a property without a getter.
@@ -6918,6 +7000,96 @@ record_object(TypeIndex type_index) {
   }
   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
  * 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 Object *record_object(TypeIndex type_index);
+  Property *record_property(const InterrogateType &itype, ElementIndex element_index);
 
 protected:
   virtual string get_wrapper_prefix();
@@ -111,6 +112,9 @@ private:
 
     // Decref temporary args object before returning.
     RF_decref_args = 0x1000,
+
+    // This raises a KeyError on falsey (or -1) return value.
+    RF_raise_keyerror = 0x4000,
   };
 
   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);
+  InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
 
   // First, check to see if it's already there.
+  ElementIndex index = 0;
   PropertiesByName::const_iterator tni =
     _properties_by_name.find(property_name);
   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
   // find the function that will give us the length.
   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;
   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) {
     CPPFunctionGroup::Instances::const_iterator 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;
 
-      size_t expected_num_args = (size_t)is_seq;
+      size_t expected_num_args = 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" &&
           TypeManager::is_pointer_to_PyObject(params[0]->_type)) {
         // 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[expected_num_args]->_initializer != NULL)) {
         // 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;
         }
 
@@ -1899,13 +1919,14 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
       CPPInstance *function = (*fi);
       CPPFunctionType *ftype =
         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;
         break;
       }
     }
 
-    if (hasser == NULL || return_type == NULL) {
+    if (hasser == nullptr) {
       cerr << "No instance of has-function '"
            << fgroup->_name << "' is suitable!\n";
       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) {
       CPPInstance *function = (*fi);
       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;
         break;
       }
     }
 
-    if (deleter == NULL || return_type == NULL) {
+    if (deleter == nullptr) {
       cerr << "No instance of delete-function '"
            << fgroup->_name << "' is suitable!\n";
       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) {
-    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 {
     iproperty._type = 0;
   }
 
-  if (length_function != 0) {
+  if (make_property->_type & CPPMakeProperty::T_sequence) {
     iproperty._flags |= InterrogateElement::F_sequence;
     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) {
@@ -2011,7 +2065,6 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
     }
   }
 
-  idb->add_element(index, iproperty);
   return index;
 }
 
@@ -2621,7 +2674,9 @@ define_struct_type(InterrogateType &itype, CPPStructType *cpptype,
 
     } else if ((*di)->get_subtype() == CPPDeclaration::ST_make_property) {
       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) {
       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;
   _del_function = 0;
   _length_function = 0;
+  _make_property = nullptr;
 }
 
 /**
@@ -52,6 +53,7 @@ operator = (const InterrogateElement &copy) {
   _clear_function = copy._clear_function;
   _del_function = copy._del_function;
   _length_function = copy._length_function;
+  _make_property = copy._make_property;
 }
 
 /**
@@ -199,6 +201,14 @@ get_length_function() const {
   return _length_function;
 }
 
+/**
+ *
+ */
+INLINE bool InterrogateElement::
+is_mapping() const {
+  return (_flags & F_mapping) != 0;
+}
+
 
 INLINE ostream &
 operator << (ostream &out, const InterrogateElement &element) {

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

@@ -19,6 +19,7 @@
 #include "interrogateComponent.h"
 
 class IndexRemapper;
+class CPPMakeProperty;
 
 /**
  * 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 bool is_sequence() const;
   INLINE FunctionIndex get_length_function() const;
+  INLINE bool is_mapping() const;
 
   void output(ostream &out) const;
   void input(istream &in);
@@ -80,6 +82,8 @@ private:
   FunctionIndex _clear_function;
   FunctionIndex _del_function;
 
+  CPPMakeProperty *_make_property;
+
   friend class InterrogateBuilder;
 };
 

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

@@ -322,11 +322,11 @@ PyObject *_Dtool_Raise_BadArgumentsError() {
  * NULL, otherwise Py_None.
  */
 PyObject *_Dtool_Return_None() {
-  if (_PyErr_OCCURRED()) {
+  if (UNLIKELY(_PyErr_OCCURRED())) {
     return NULL;
   }
 #ifndef NDEBUG
-  if (Notify::ptr()->has_assert_failed()) {
+  if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
     return Dtool_Raise_AssertionError();
   }
 #endif
@@ -339,11 +339,11 @@ PyObject *_Dtool_Return_None() {
  * NULL, otherwise the given boolean value as a PyObject *.
  */
 PyObject *Dtool_Return_Bool(bool value) {
-  if (_PyErr_OCCURRED()) {
+  if (UNLIKELY(_PyErr_OCCURRED())) {
     return NULL;
   }
 #ifndef NDEBUG
-  if (Notify::ptr()->has_assert_failed()) {
+  if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
     return Dtool_Raise_AssertionError();
   }
 #endif
@@ -358,11 +358,11 @@ PyObject *Dtool_Return_Bool(bool value) {
  * increased.
  */
 PyObject *_Dtool_Return(PyObject *value) {
-  if (_PyErr_OCCURRED()) {
+  if (UNLIKELY(_PyErr_OCCURRED())) {
     return NULL;
   }
 #ifndef NDEBUG
-  if (Notify::ptr()->has_assert_failed()) {
+  if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
     return Dtool_Raise_AssertionError();
   }
 #endif
@@ -614,6 +614,14 @@ PyObject *Dtool_PyModuleInitHelper(LibraryDef *defs[], const char *modulename) {
       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) {
       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);
-  Py_XDECREF(wrap->_base);
+  Py_XDECREF(wrap->_self);
 }
 
 static Py_ssize_t Dtool_SequenceWrapper_length(PyObject *self) {
   Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
   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) {
   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) {
   Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
   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 = {
@@ -1071,12 +1124,27 @@ static PySequenceMethods Dtool_SequenceWrapper_SequenceMethods = {
   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 = {
   PyVarObject_HEAD_INIT(NULL, 0)
   "sequence wrapper",
   sizeof(Dtool_SequenceWrapper),
   0, // tp_itemsize
-  Dtool_SequenceWrapper_dealloc,
+  Dtool_WrapperBase_dealloc,
   0, // tp_print
   0, // tp_getattr
   0, // tp_setattr
@@ -1262,4 +1330,128 @@ PyTypeObject Dtool_StaticProperty_Type = {
 #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

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

@@ -320,9 +320,9 @@ EXPCL_INTERROGATEDB bool _Dtool_CheckErrorOccurred();
 #endif
 
 #ifdef NDEBUG
-#define Dtool_CheckErrorOccurred() (_PyErr_OCCURRED() != NULL)
+#define Dtool_CheckErrorOccurred() (UNLIKELY(_PyErr_OCCURRED() != NULL))
 #else
-#define Dtool_CheckErrorOccurred() _Dtool_CheckErrorOccurred()
+#define Dtool_CheckErrorOccurred() (UNLIKELY(_Dtool_CheckErrorOccurred()))
 #endif
 
 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)
 #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_Bool(bool value);
 EXPCL_INTERROGATEDB PyObject *_Dtool_Return(PyObject *value);
@@ -465,18 +468,36 @@ EXPCL_INTERROGATEDB PyObject *
 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 {
-  PyObject_HEAD
-  PyObject *_base;
+  Dtool_WrapperBase _base;
   lenfunc _len_func;
   ssizeargfunc _getitem_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_MappingWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SeqMapWrapper_Type;
 EXPCL_INTERROGATEDB extern PyTypeObject Dtool_StaticProperty_Type;
 
 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::
 is_spam() const {
   // 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::
 is_debug() const {
   // 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
 /**

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

@@ -70,11 +70,7 @@ template<class GetCategory>
 INLINE bool NotifyCategoryProxy<GetCategory>::
 is_spam() {
   // 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
 template<class GetCategory>
@@ -92,11 +88,7 @@ template<class GetCategory>
 INLINE bool NotifyCategoryProxy<GetCategory>::
 is_debug() {
   // 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
 template<class GetCategory>

+ 1 - 6
makepanda/makepanda.py

@@ -3179,7 +3179,6 @@ if (PkgSkip("DIRECT")==0):
     CopyAllHeaders('direct/src/distributed')
     CopyAllHeaders('direct/src/interval')
     CopyAllHeaders('direct/src/showbase')
-    CopyAllHeaders('direct/metalibs/direct')
     CopyAllHeaders('direct/src/dcparse')
 
 if (RUNTIME or RTDIST):
@@ -5244,10 +5243,6 @@ 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='p3showbase_showBase.obj')
   if GetTarget() == 'darwin':
@@ -5259,7 +5254,7 @@ if (PkgSkip("DIRECT")==0):
   TargetAdd('libp3direct.dll', input=COMMON_PANDA_LIBS)
   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='libp3showbase.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):
                 SYS_INC_DIRS.append(pcbsd_inc)
 
+        null.close()
+
         # Print out the search paths
         if GetVerbose():
             print("System library search path:")

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

@@ -19,6 +19,34 @@ ptr() const {
   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.
  */
-int BulletTriangleMesh::
+size_t BulletTriangleMesh::
 get_num_triangles() const {
   return _indices.size() / 3;
 }

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

@@ -48,15 +48,24 @@ PUBLISHED:
   void set_welding_distance(PN_stdfloat distance);
   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;
 
   virtual void output(ostream &out) 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_SEQ_PROPERTY(vertices, get_num_vertices, get_vertex);
+  MAKE_SEQ_PROPERTY(triangles, get_num_triangles, get_triangle);
+
 public:
   INLINE btStridingMeshInterface *ptr() const;
 

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

@@ -44,12 +44,13 @@ PUBLISHED:
   AnimGroup *get_child(int n) const;
   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 *find_child(const string &name) const;
   void sort_descendants();
 
+  MAKE_SEQ_PROPERTY(children, get_num_children, get_child);
+  MAKE_MAP_PROPERTY(children, get_child_named, get_child_named);
+
 public:
   virtual TypeHandle get_value_type() const;
 

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

@@ -70,12 +70,14 @@ PUBLISHED:
   int get_num_children() const;
   PartGroup *get_child(int n) const;
   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 *find_child(const string &name) const;
   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);
   virtual bool apply_freeze_matrix(const LVecBase3 &pos, const LVecBase3 &hpr, const LVecBase3 &scale);
   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.
  */
 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.
  */
 void GraphicsEngine::
-cull_and_draw_together(const GraphicsEngine::Windows &wlist,
+cull_and_draw_together(GraphicsEngine::Windows wlist,
                        Thread *current_thread) {
   PStatTimer timer(_cull_pcollector, current_thread);
 
@@ -1380,7 +1380,7 @@ cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr,
  * drawing.
  */
 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);
 
   _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 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,
                               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,
                     DisplayRegion *dr, SceneSetup *scene_setup,
                     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
  * 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
  * 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
  * 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).
 
   if (plane == RTP_depth) {
-    tex->set_format(Texture::F_depth_component);
+    _fb_properties.setup_depth_texture(tex);
     tex->set_match_framebuffer_format(true);
+
   } else if (plane == RTP_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);
-  } 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);
-  } 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_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_component_type(Texture::T_float);
     tex->set_match_framebuffer_format(true);
+
   } else {
     display_cat.error() <<
       "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;
   _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() {
   remove_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
     // in the world anyway.
     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);
       if (munger->is_registered()) {
         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.
-    mi = mungers.find(this);
+    mi = mungers.find(_id);
     if (mi >= 0) {
       PT(GeomMunger) munger = mungers.get_data(mi);
       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->is_of_type(StateMunger::get_class_type()), munger);
 
-  state->_last_mi = mungers.store(this, munger);
+  state->_last_mi = mungers.store(_id, munger);
   return munger;
 }
 
@@ -3021,6 +3039,19 @@ determine_target_texture() {
   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.
  */
@@ -3364,6 +3395,43 @@ make_shadow_buffer(const NodePath &light_np, GraphicsOutputBase *host) {
   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
  * 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) make_shadow_buffer(const NodePath &light_np, GraphicsOutputBase *host);
 
+  virtual void ensure_generated_shader(const RenderState *state);
+
 #ifdef DO_PSTATS
   static void init_frame_pstats();
 #endif
@@ -446,6 +448,7 @@ protected:
   virtual void end_bind_clip_planes();
 
   void determine_target_texture();
+  void determine_target_shader();
 
   virtual void free_pointers();
   virtual void close_gsg();

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

@@ -38,7 +38,17 @@ StandardMunger(GraphicsStateGuardianBase *gsg, const RenderState *state,
   _auto_shader(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.
     const ColorAttrib *color_attrib;
     const ColorScaleAttrib *color_scale_attrib;
@@ -60,6 +70,7 @@ StandardMunger(GraphicsStateGuardianBase *gsg, const RenderState *state,
                      _color[3] * cs[3]);
         }
         _munge_color = true;
+        _should_munge_state = true;
       }
 
     } 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()) ||
           (color_scale_attrib->has_alpha_scale() && !get_gsg()->get_alpha_scale_via_texture(tex_attrib))) {
         _munge_color_scale = true;
+        _should_munge_state = true;
       }
 
       // 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.
     }
   }
-
-  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());
   }
 
-#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;
 }

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

@@ -3074,7 +3074,7 @@ set_state_and_transform(const RenderState *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();
   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);
   }
 
-  if (stage->uses_color()) {
+  if (stage->uses_color() || stage->involves_color_scale()) {
     // Set up the constant color for this stage.
 
     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
  * 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,
                 void *data);
   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, EventCallbackFunction *function,
                    void *data);

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

@@ -105,9 +105,9 @@ PUBLISHED:
   INLINE void set_element(size_type n, const Element &value);
   EXTENSION(const Element &__getitem__(size_type n) const);
   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 int get_ref_count() const;
   INLINE int get_node_ref_count() const;
@@ -255,14 +255,12 @@ PUBLISHED:
   INLINE ConstPointerToArray(const PointerToArray<Element> &copy);
   INLINE ConstPointerToArray(const ConstPointerToArray<Element> &copy);
 
-  EXTENSION(ConstPointerToArray(PyObject *self, PyObject *source));
-
   typedef TYPENAME pvector<Element>::size_type size_type;
   INLINE size_type size() const;
   INLINE const Element &get_element(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_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) {
 #if PY_VERSION_HEX >= 0x02060000
   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;
   }
-#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.
     PyErr_SetString(PyExc_TypeError,
                     "PointerToArray constructor requires a sequence or buffer object");
     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
   // 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) {
     PyErr_BadArgument();
     return;
@@ -174,19 +107,20 @@ __init__(PyObject *self, PyObject *source) {
   // We need to initialize the this pointer before we can call push_back.
   ((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);
     if (item == NULL) {
       return;
     }
-    PyObject *result = PyObject_CallFunctionObjArgs(push_back, item, NULL);
+    PyObject *result = PyObject_CallFunctionObjArgs(push_back, self, item, NULL);
     Py_DECREF(item);
     if (result == NULL) {
       // Unable to add item--probably it wasn't of the appropriate type.
       PyErr_Print();
       PyErr_Format(PyExc_TypeError,
-                   "Element %d in sequence passed to PointerToArray "
+                   "Element %zd in sequence passed to PointerToArray "
                    "constructor could not be added", i);
       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>
-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];
 }
 
+/**
+ * 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
  * access to the array data through a Python multiview object.
@@ -461,6 +526,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 #endif
 }
 
+/**
+ * Specialization on __getbuffer__ for LMatrix3f.
+ */
 template<>
 INLINE int Extension<ConstPointerToArray<LMatrix3f> >::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
@@ -488,6 +556,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 #endif
 }
 
+/**
+ * Specialization on __getbuffer__ for LMatrix3d.
+ */
 template<>
 INLINE int Extension<ConstPointerToArray<LMatrix3d> >::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
@@ -515,6 +586,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 #endif
 }
 
+/**
+ * Specialization on __getbuffer__ for UnalignedLMatrix4f.
+ */
 template<>
 INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4f> >::
 __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
@@ -542,6 +616,9 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 #endif
 }
 
+/**
+ * Specialization on __getbuffer__ for UnalignedLMatrix4d.
+ */
 template<>
 INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4d> >::
 __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 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 void __releasebuffer__(PyObject *self, Py_buffer *view) const;
 };
 
 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<>
-  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<>
-  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<>
-  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
@@ -59,22 +70,30 @@ template<>
 template<class Element>
 class Extension<ConstPointerToArray<Element> > : public ExtensionBase<ConstPointerToArray<Element> > {
 public:
-  INLINE void __init__(PyObject *self, PyObject *source);
-
   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 void __releasebuffer__(PyObject *self, Py_buffer *view) const;
 };
 
 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<>
-  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<>
-  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<>
-  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
 // 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"
   "  gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;\n"
   "  texcoord = p3d_MultiTexCoord0;\n"
-  "  color = p3d_Color;\n"
+  "  color = p3d_Color * p3d_ColorScale;\n"
   "}\n";
 
 static const string default_fshader =
@@ -3127,6 +3127,10 @@ reset() {
   }
 #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
   if (_gl_shadlang_ver_major >= 4 || has_extension("GL_NV_gpu_program5")) {
     // gp5fp - OpenGL fragment profile for GeForce 400 Series and up
@@ -10341,8 +10345,7 @@ set_state_and_transform(const RenderState *target,
   _target_rs = target;
 
 #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();
 
   if (_target_shader != _state_shader) {

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

@@ -95,6 +95,27 @@ reset_data() {
 #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();
   void reset_data();
 
+  virtual uint64_t get_native_id() const;
+  virtual uint64_t get_native_buffer_id() const;
+
 #ifndef OPENGLES
   void make_handle_resident();
   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(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 write(ostream &out, int indent_level = 0) const;
   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)) {
     // Not an interned string; don't bother.
     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) {
       return NULL;
     }
@@ -43,7 +43,7 @@ make(PyUnicodeObject *str) {
 
   } else {
     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);
 
 #else

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

@@ -539,6 +539,8 @@ PUBLISHED:
   void set_aux_data(const string &key, TypedReferenceCount *aux_data);
   void clear_aux_data(const string &key);
   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 AutoTextureScale get_textures_power_2();

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

@@ -15,6 +15,27 @@
 
 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:
   INLINE Texture *get_texture() 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_properties_modified() const;

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

@@ -15,7 +15,7 @@
  * Initialize the texture stage from other
  */
 INLINE TextureStage::
-TextureStage(TextureStage &copy) {
+TextureStage(const TextureStage &copy) {
   (*this) = copy;
 }
 
@@ -52,6 +52,10 @@ set_sort(int sort) {
   // Update the global flag to indicate that all TextureAttribs in the world
   // must now re-sort their lists.
   _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
   // must now re-sort their lists.
   _sort_seq++;
+
+  if (_used_by_auto_shader) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+  }
 }
 
 /**
@@ -99,7 +107,13 @@ get_priority() const {
  */
 INLINE void TextureStage::
 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::
 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::
 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::
 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::
 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::
 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;
 }
 
+/**
+ * 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
  * _uses_last_saved_result appropriately.
@@ -658,8 +701,7 @@ update_color_flags() {
        _combine_alpha_source2 == CS_constant_color_scale)));
 
   _uses_color =
-    (_involves_color_scale ||
-     _mode == M_blend ||
+    (_mode == M_blend ||
      (_mode == M_combine &&
       (_combine_rgb_source0 == CS_constant ||
        _combine_rgb_source1 == CS_constant ||
@@ -685,6 +727,10 @@ update_color_flags() {
        _combine_alpha_source0 == CS_last_saved_result ||
        _combine_alpha_source1 == CS_last_saved_result ||
        _combine_alpha_source2 == CS_last_saved_result));
+
+  if (_used_by_auto_shader) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+  }
 }
 
 INLINE ostream &

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

@@ -25,7 +25,7 @@ TypeHandle TextureStage::_type_handle;
  * Initialize the texture stage at construction
  */
 TextureStage::
-TextureStage(const string &name) {
+TextureStage(const string &name) : _used_by_auto_shader(false) {
   _name = name;
   _sort = 0;
   _priority = 0;
@@ -90,6 +90,8 @@ operator = (const TextureStage &other) {
 
   _uses_color = other._uses_color;
   _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 "updateSeq.h"
 #include "luse.h"
+#include "graphicsStateGuardianBase.h"
 
 class FactoryParams;
 
@@ -34,7 +35,7 @@ class FactoryParams;
 class EXPCL_PANDA_GOBJ TextureStage : public TypedWritableReferenceCount {
 PUBLISHED:
   explicit TextureStage(const string &name);
-  INLINE TextureStage(TextureStage &copy);
+  INLINE TextureStage(const TextureStage &copy);
   void operator = (const TextureStage &copy);
 
   virtual ~TextureStage();
@@ -206,6 +207,8 @@ PUBLISHED:
 public:
   INLINE static UpdateSeq get_sort_seq();
 
+  INLINE void mark_used_by_auto_shader() const;
+
 private:
   INLINE void update_color_flags();
 
@@ -249,6 +252,8 @@ private:
   static PT(TextureStage) _default_stage;
   static UpdateSeq _sort_seq;
 
+  mutable bool _used_by_auto_shader;
+
 public:
   // Datagram stuff
   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;
 }
 
+/**
+ * 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.
  */

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

@@ -62,9 +62,15 @@ PUBLISHED:
   INLINE const VertexTransform *get_transform(size_t n) const;
   MAKE_SEQ(get_transforms, get_num_transforms, get_transform);
   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_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 get_blend(LMatrix4 &result, Thread *current_thread) const;

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

@@ -16,6 +16,7 @@
 #include <algorithm>
 
 AtomicAdjust::Pointer GraphicsStateGuardianBase::_gsg_list;
+UpdateSeq GraphicsStateGuardianBase::_generated_shader_seq;
 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,
                           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:
   static GraphicsStateGuardianBase *get_default_gsg();
   static void set_default_gsg(GraphicsStateGuardianBase *default_gsg);
@@ -235,6 +243,8 @@ public:
   static void add_gsg(GraphicsStateGuardianBase *gsg);
   static void remove_gsg(GraphicsStateGuardianBase *gsg);
 
+  size_t _id;
+
 private:
   struct GSGList {
     LightMutex _lock;
@@ -245,6 +255,9 @@ private:
   };
   static AtomicAdjust::Pointer _gsg_list;
 
+protected:
+  static UpdateSeq _generated_shader_seq;
+
 public:
   static TypeHandle get_class_type() {
     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]);
 }
 
+/**
+ *
+ */
+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);
 }
 
+/**
+ * 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)(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 void fill(FLOATTYPE fill_value);
   INLINE_LINMATH void set(FLOATTYPE x, FLOATTYPE y, FLOATTYPE z, FLOATTYPE w);
 
   INLINE_LINMATH FLOATTYPE operator [](int i) const;

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

@@ -91,12 +91,16 @@ PUBLISHED:
   void clear_tag_states();
   bool has_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);
   bool clear_aux_scene_data(const NodePath &node_path);
   AuxSceneData *get_aux_scene_data(const NodePath &node_path) const;
   void list_aux_scene_data(ostream &out) const;
   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:
   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,
  * 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
  * 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, "
           "but also adds additional overhead to maintain the cache, "
           "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
 ("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));
         _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
     // 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
  * 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
  * lights that were on.
  *
@@ -833,6 +833,14 @@ compose_impl(const RenderAttrib *other) const {
     ++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);
 }
 

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

@@ -630,9 +630,12 @@ PUBLISHED:
   void clear_shader();
 
   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, 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, const NodePath &np, 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 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, 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);
-  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);
 
+  EXTENSION(void set_shader_input(CPT_InternalName, PyObject *, int priority=0));
   EXTENSION(void set_shader_inputs(PyObject *args, PyObject *kwargs));
 
   void clear_shader_input(CPT_InternalName id);
@@ -908,6 +913,9 @@ PUBLISHED:
   INLINE bool has_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(PyObject *get_python_tags());

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

@@ -13,6 +13,7 @@
 
 #include "nodePath_ext.h"
 #include "typedWritable_ext.h"
+#include "shaderInput_ext.h"
 #include "shaderAttrib.h"
 
 #ifdef HAVE_PYTHON
@@ -25,35 +26,7 @@ extern struct Dtool_PyTypedObject Dtool_LPoint3d;
 #else
 extern struct Dtool_PyTypedObject Dtool_LPoint3f;
 #endif
-extern struct Dtool_PyTypedObject Dtool_Texture;
 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
 
 /**
@@ -241,6 +214,28 @@ py_decode_NodePath_from_bam_stream_persist(PyObject *unpickler, const string &da
   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
  * 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));
-    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.
   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);
 
   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 "pandaNode_ext.cxx"
 #include "renderState_ext.cxx"
+#include "shaderAttrib_ext.cxx"
+#include "shaderInput_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.
     attrib->ref();
   }
-  si = _attribs->store(attrib, Empty());
+  si = _attribs->store(attrib, nullptr);
 
   // Save the index and return the input attrib.
   attrib->_saved_entry = si;

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

@@ -185,9 +185,7 @@ public:
 private:
   // This mutex protects _attribs.
   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;
 
   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.
       nassertr(RenderAttrib::_attribs->find(default_attrib) == -1, 0);
       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.

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

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

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

@@ -30,10 +30,8 @@
 #include "lightMutex.h"
 #include "deletedChain.h"
 #include "simpleHashMap.h"
-#include "weakKeyHashMap.h"
 #include "cacheStats.h"
 #include "renderAttribRegistry.h"
-#include "graphicsStateGuardianBase.h"
 
 class FactoryParams;
 class ShaderAttrib;
@@ -109,6 +107,8 @@ PUBLISHED:
   INLINE int get_override(TypeHandle type) const;
   INLINE int get_override(int slot) const;
 
+  MAKE_MAP_PROPERTY(attribs, has_attrib, get_attrib);
+
   INLINE CPT(RenderState) get_unique() const;
 
   virtual bool unref() const;
@@ -219,15 +219,14 @@ public:
   // declare this as a ShaderAttrib because that would create a circular
   // include-file dependency problem.  Aaargh.
   mutable CPT(RenderAttrib) _generated_shader;
+  mutable UpdateSeq _generated_shader_seq;
 
 private:
   // This mutex protects _states.  It also protects any modification to the
   // cache, which is encoded in _composition_cache and
   // _invert_composition_cache.
   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 const RenderState *_empty_state;
 
@@ -264,10 +263,15 @@ private:
   // 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
   // GraphicsStateGuardian::get_geom_munger().
-  typedef WeakKeyHashMap<GraphicsStateGuardianBase, PT(GeomMunger) > Mungers;
+  typedef SimpleHashMap<size_t, PT(GeomMunger), size_t_hash> Mungers;
   mutable Mungers _mungers;
   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.
   UpdateSeq _cycle_detect;
   static UpdateSeq _last_cycle_detect;
@@ -360,6 +364,8 @@ private:
   friend class GraphicsStateGuardian;
   friend class RenderAttribRegistry;
   friend class Extension<RenderState>;
+  friend class ShaderGenerator;
+  friend class StateMunger;
 };
 
 // We can safely redefine this as a no-op.

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

@@ -73,6 +73,7 @@ PUBLISHED:
   // Shader Inputs
   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, const NodePath &np, 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,
                                             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_flag(int flag, bool value) const;
@@ -150,6 +155,7 @@ private:
   Inputs _inputs;
 
   friend class Extension<NodePath>;
+  friend class Extension<ShaderAttrib>;
 
 PUBLISHED:
   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();
-  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, ParamValueBase *param, 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);
 
   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, const SamplerState &sampler, int priority=0);
 
@@ -98,6 +104,7 @@ PUBLISHED:
     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;
@@ -132,6 +139,7 @@ private:
   int _type;
 
   friend class ShaderAttrib;
+  friend class Extension<ShaderInput>;
 };
 
 #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::
 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::
 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 (!_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);
-  _state_map.store(state, result.p());
+  munged_states.store(id, result.p());
 
   return result;
 }

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

@@ -30,11 +30,12 @@ public:
   virtual ~StateMunger();
   CPT(RenderState) munge_state(const RenderState *state);
 
+  INLINE bool should_munge_state() const;
+
 protected:
   virtual CPT(RenderState) munge_state_impl(const RenderState *state);
 
-  typedef WeakKeyHashMap<RenderState, WCPT(RenderState) > StateMap;
-  StateMap _state_map;
+  bool _should_munge_state;
 
 public:
   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;
 
+  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 TextureStage *get_off_stage(int n) const;
   MAKE_SEQ(get_off_stages, get_num_off_stages, get_off_stage);
   INLINE bool has_off_stage(TextureStage *stage) 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;
 
   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.
     state->cache_ref();
   }
-  si = _states->store(state, Empty());
+  si = _states->store(state, nullptr);
 
   // Save the index and return the input state.
   state->_saved_entry = si;

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

@@ -253,9 +253,7 @@ private:
   // cache, which is encoded in _composition_cache and
   // _invert_composition_cache.
   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 CPT(TransformState) _identity_state;
   static CPT(TransformState) _invalid_state;

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

@@ -51,6 +51,14 @@ TypeHandle ShaderGenerator::_type_handle;
 
 #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 synthesize_collector("*:Munge:ShaderGen:Synthesize");
 
@@ -60,7 +68,7 @@ static PStatCollector synthesize_collector("*:Munge:ShaderGen:Synthesize");
  * shader generator belongs.
  */
 ShaderGenerator::
-ShaderGenerator(GraphicsStateGuardianBase *gsg) {
+ShaderGenerator(const GraphicsStateGuardianBase *gsg) {
   // The ATTR# input semantics seem to map to generic vertex attributes in
   // both arbvp1 and glslv, which behave more consistently.  However, they
   // 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);
     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;
     info._type = tex->get_texture_type();
     info._mode = stage->get_mode();
     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
     // 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;
       }
       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?
@@ -417,17 +466,31 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) {
       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._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.
   if (have_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
  * 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";
     }
 
-    if (tex._mode == TextureStage::M_blend) {
+    if (tex._flags & ShaderKey::TF_uses_color) {
       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.
@@ -1310,24 +1429,21 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
       }
       break;
     case TextureStage::M_combine:
-      // Only in the case of M_combine have we filled in the _stage pointer.
       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 = ";
-      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";
       break;
@@ -1337,7 +1453,7 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
     default:
       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";
     }
   }
@@ -1464,53 +1580,53 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
  * This 'synthesizes' a combine mode into a string.
  */
 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;
   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();
 }
@@ -1519,39 +1635,15 @@ combine_mode_as_string(CPT(TextureStage) stage, TextureStage::CombineMode c_mode
  * This 'synthesizes' a combine source into a string.
  */
 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 {
-    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;
   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:
       csource << "tex" << texindex;
       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:
       csource << "primary_color";
       break;
@@ -1684,8 +1775,11 @@ operator < (const ShaderKey &other) const {
     if (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()) {
@@ -1759,7 +1853,8 @@ operator == (const ShaderKey &other) const {
         tex._mode != other_tex._mode ||
         tex._gen_mode != other_tex._gen_mode ||
         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;
     }
   }

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

@@ -65,18 +65,15 @@ class GeomVertexAnimationSpec;
  */
 class EXPCL_PANDA_PGRAPHNODES ShaderGenerator : public TypedReferenceCount {
 PUBLISHED:
-  ShaderGenerator(GraphicsStateGuardianBase *gsg);
+  ShaderGenerator(const GraphicsStateGuardianBase *gsg);
   virtual ~ShaderGenerator();
   virtual CPT(ShaderAttrib) synthesize_shader(const RenderState *rs,
                                               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:
 
   bool _use_generic_attr;
@@ -101,15 +98,28 @@ protected:
 
     GeomVertexAnimationSpec _anim_spec;
     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;
@@ -122,9 +132,8 @@ protected:
       TextureStage::Mode _mode;
       TexGenAttrib::Mode _gen_mode;
       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;
 
@@ -159,6 +168,12 @@ protected:
 
   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:
   static TypeHandle get_class_type() {
     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)) {
     // This element is already in the map; replace the data at that key.
-    _table[index]._data = data;
+    set_data(index, data);
 #ifdef _DEBUG
     nassertr(validate(), index);
 #endif
@@ -151,7 +151,7 @@ store(const Key &key, const Value &data) {
       return index;
     }
     if (is_element(index, key)) {
-      _table[index]._data = data;
+      set_data(index, data);
 #ifdef _DEBUG
       nassertr(validate(), index);
 #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
     // 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;
   }
 
@@ -311,8 +310,8 @@ get_key(size_t n) const {
 template<class Key, class Value, class Compare>
 INLINE const Value &SimpleHashMap<Key, Value, Compare>::
 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>
 INLINE Value &SimpleHashMap<Key, Value, Compare>::
 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>::
 set_data(size_t n, const Value &data) {
   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 "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,
  * 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,
  * (e) permits removal and resizing during forward iteration, and
  * (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> > >
 class SimpleHashMap {
@@ -55,6 +103,7 @@ public:
   INLINE const Value &get_data(size_t n) const;
   INLINE Value &modify_data(size_t n);
   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);
 
   INLINE size_t get_num_entries() const;
@@ -67,8 +116,6 @@ public:
   INLINE bool consider_shrink_table();
 
 private:
-  class TableEntry;
-
   INLINE size_t get_hash(const Key &key) const;
   INLINE size_t next_hash(size_t hash) const;
 
@@ -82,23 +129,8 @@ private:
   INLINE bool consider_expand_table();
   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;
   DeletedBufferChain *_deleted_chain;
   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());
 
     if (_force_complete) {
-      if (!file_data.load_externals()) {
+      if (!file_data.load_externals(file_path)) {
         exit(1);
       }
     }

+ 1 - 1
samples/fireflies/main.py

@@ -338,7 +338,7 @@ class FireflyDemo(ShowBase):
         color_g = random.uniform(0.8, 1.0)
         color_b = min(color_g, random.uniform(0.5, 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)
         int2 = fly.posInterval(random.uniform(7, 12), pos2, pos1)
         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.reparentTo(self.pandaAxis)
         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.loop()
         self.pandaMovement = self.pandaAxis.hprInterval(
@@ -113,7 +113,7 @@ class World(DirectObject):
         self.teapot = loader.loadModel('teapot')
         self.teapot.reparentTo(render)
         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.loop()
 
@@ -145,9 +145,9 @@ class World(DirectObject):
         # setting up shader
         render.setShaderInput('light', self.LCam)
         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.
         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.reparentTo(render)
         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.loop()
 

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