Просмотр исходного кода

Merge branch 'master' into vulkan

rdb 9 лет назад
Родитель
Сommit
dc3799db25
57 измененных файлов с 691 добавлено и 130 удалено
  1. 8 2
      direct/src/showbase/ShowBase.py
  2. 1 3
      dtool/src/interrogate/interrogateBuilder.cxx
  3. 1 1
      dtool/src/interrogatedb/py_panda.cxx
  4. 2 1
      dtool/src/interrogatedb/py_panda.h
  5. 7 0
      panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
  6. 4 0
      panda/src/egg/eggRenderMode.cxx
  7. 2 1
      panda/src/egg/eggRenderMode.h
  8. 5 0
      panda/src/egg2pg/eggLoader.cxx
  9. 4 0
      panda/src/egg2pg/eggRenderState.cxx
  10. 5 3
      panda/src/egg2pg/eggSaver.cxx
  11. 10 2
      panda/src/express/virtualFileMountRamdisk.cxx
  12. 2 0
      panda/src/framework/config_framework.cxx
  13. 1 0
      panda/src/framework/config_framework.h
  14. 5 1
      panda/src/framework/pandaFramework.cxx
  15. 27 6
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  16. 2 0
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  17. 21 18
      panda/src/glstuff/glShaderContext_src.cxx
  18. 0 7
      panda/src/gobj/config_gobj.cxx
  19. 0 1
      panda/src/gobj/config_gobj.h
  20. 58 2
      panda/src/gobj/lens.cxx
  21. 38 0
      panda/src/gobj/matrixLens.cxx
  22. 2 0
      panda/src/gobj/matrixLens.h
  23. 5 2
      panda/src/gobj/texture.I
  24. 25 9
      panda/src/gobj/texture.cxx
  25. 1 1
      panda/src/gobj/texture.h
  26. 8 0
      panda/src/gobj/texturePeeker.I
  27. 72 2
      panda/src/gobj/texturePeeker.cxx
  28. 3 0
      panda/src/gobj/texturePeeker.h
  29. 24 2
      panda/src/pgraph/camera.cxx
  30. 2 0
      panda/src/pgraph/camera.h
  31. 1 0
      panda/src/pgraph/cullResult.cxx
  32. 1 1
      panda/src/pgraph/cullTraverserData.cxx
  33. 35 5
      panda/src/pgraph/lensNode.cxx
  34. 2 0
      panda/src/pgraph/pandaNode.cxx
  35. 5 4
      panda/src/pgraph/renderState.cxx
  36. 4 3
      panda/src/pgraph/transparencyAttrib.cxx
  37. 1 1
      panda/src/pgraph/transparencyAttrib.h
  38. 24 2
      panda/src/pgraphnodes/pointLight.I
  39. 13 2
      panda/src/pgraphnodes/pointLight.cxx
  40. 5 0
      panda/src/pgraphnodes/pointLight.h
  41. 1 0
      panda/src/pgraphnodes/shaderGenerator.cxx
  42. 24 2
      panda/src/pgraphnodes/spotlight.I
  43. 13 2
      panda/src/pgraphnodes/spotlight.cxx
  44. 5 0
      panda/src/pgraphnodes/spotlight.h
  45. 115 6
      panda/src/pnmimage/pfmFile.cxx
  46. 3 0
      panda/src/pnmimage/pfmFile.h
  47. 2 2
      panda/src/pnmimage/pnmImage.cxx
  48. 34 35
      panda/src/putil/bam.h
  49. 2 1
      panda/src/putil/bamReader.cxx
  50. 16 0
      panda/src/putil/bamWriter.I
  51. 4 0
      panda/src/putil/bamWriter.cxx
  52. 4 0
      panda/src/putil/bamWriter.h
  53. 7 0
      panda/src/putil/config_util.cxx
  54. 1 0
      panda/src/putil/config_util.h
  55. 8 0
      panda/src/putil/loaderOptions.cxx
  56. 1 0
      panda/src/putil/loaderOptions.h
  57. 15 0
      panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx

+ 8 - 2
direct/src/showbase/ShowBase.py

@@ -532,12 +532,19 @@ class ShowBase(DirectObject.DirectObject):
             del self.winList
             del self.pipe
 
-    def makeDefaultPipe(self, printPipeTypes = True):
+    def makeDefaultPipe(self, printPipeTypes = None):
         """
         Creates the default GraphicsPipe, which will be used to make
         windows unless otherwise specified.
         """
         assert self.pipe == None
+
+        if printPipeTypes is None:
+            # When the user didn't specify an explicit setting, take the value
+            # from the config variable. We could just omit the parameter, however
+            # this way we can keep backward compatibility.
+            printPipeTypes = ConfigVariableBool("print-pipe-types", True)
+
         selection = GraphicsPipeSelection.getGlobalPtr()
         if printPipeTypes:
             selection.printPipeTypes()
@@ -567,7 +574,6 @@ class ShowBase(DirectObject.DirectObject):
         Creates all GraphicsPipes that the system knows about and fill up
         self.pipeList with them.
         """
-        shouldPrintPipes = 0
         selection = GraphicsPipeSelection.getGlobalPtr()
         selection.loadAuxModules()
 

+ 1 - 3
dtool/src/interrogate/interrogateBuilder.cxx

@@ -1870,9 +1870,7 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
   iproperty._scoped_name = descope(make_property->get_local_name(&parser));
 
   if (return_type != NULL) {
-    iproperty._type = get_type(return_type, false);
-    // if (iproperty._type == 0) { parser.warning("cannot determine property
-    // type", make_property->_ident->_loc); }
+    iproperty._type = get_type(TypeManager::unwrap_reference(return_type), false);
   } else {
     iproperty._type = 0;
   }

+ 1 - 1
dtool/src/interrogatedb/py_panda.cxx

@@ -183,7 +183,7 @@ void *DTOOL_Call_GetPointerThis(PyObject *self) {
  * In the NDEBUG case, this is simply a #define to _PyErr_OCCURRED() (which is
  * an undocumented inline version of PyErr_Occurred()).
  */
-bool Dtool_CheckErrorOccurred() {
+bool _Dtool_CheckErrorOccurred() {
   if (_PyErr_OCCURRED()) {
     return true;
   }

+ 2 - 1
dtool/src/interrogatedb/py_panda.h

@@ -313,12 +313,13 @@ template<class T> INLINE bool DTOOL_Call_ExtractThisPointer(PyObject *self, T *&
 }
 
 // Functions related to error reporting.
+EXPCL_INTERROGATEDB bool _Dtool_CheckErrorOccurred();
 
 #ifdef NDEBUG
 // _PyErr_OCCURRED is an undocumented inline version of PyErr_Occurred.
 #define Dtool_CheckErrorOccurred() (_PyErr_OCCURRED() != NULL)
 #else
-EXPCL_INTERROGATEDB bool Dtool_CheckErrorOccurred();
+#define Dtool_CheckErrorOccurred() _Dtool_CheckErrorOccurred()
 #endif
 
 EXPCL_INTERROGATEDB PyObject *Dtool_Raise_AssertionError();

+ 7 - 0
panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx

@@ -3822,6 +3822,13 @@ do_issue_blending() {
     set_render_state(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
     return;
 
+  case TransparencyAttrib::M_premultiplied_alpha:
+    set_render_state(D3DRS_ALPHABLENDENABLE, TRUE);
+    set_render_state(D3DRS_BLENDOP, D3DBLENDOP_ADD);
+    set_render_state(D3DRS_SRCBLEND, D3DBLEND_ONE);
+    set_render_state(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
+    return;
+
   default:
     dxgsg9_cat.error()
       << "invalid transparency mode " << (int)transparency_mode << endl;

+ 4 - 0
panda/src/egg/eggRenderMode.cxx

@@ -183,6 +183,8 @@ string_alpha_mode(const string &string) {
     return AM_binary;
   } else if (cmp_nocase_uh(string, "dual") == 0) {
     return AM_dual;
+  } else if (cmp_nocase_uh(string, "premultiplied") == 0) {
+    return AM_premultiplied;
   } else {
     return AM_unspecified;
   }
@@ -260,6 +262,8 @@ ostream &operator << (ostream &out, EggRenderMode::AlphaMode mode) {
     return out << "binary";
   case EggRenderMode::AM_dual:
     return out << "dual";
+  case EggRenderMode::AM_premultiplied:
+    return out << "premultiplied";
   }
 
   nassertr(false, out);

+ 2 - 1
panda/src/egg/eggRenderMode.h

@@ -45,7 +45,8 @@ PUBLISHED:
     AM_ms,      // TransparencyAttrib::M_multisample
     AM_ms_mask, // TransparencyAttrib::M_multisample_mask
     AM_binary,  // TransparencyAttrib::M_binary
-    AM_dual     // TransparencyAttrib::M_dual
+    AM_dual,    // TransparencyAttrib::M_dual
+    AM_premultiplied // TransparencyAttrib::M_premultiplied_alpha
   };
 
   enum DepthWriteMode {

+ 5 - 0
panda/src/egg2pg/eggLoader.cxx

@@ -950,6 +950,11 @@ load_texture(TextureDef &def, EggTexture *egg_tex) {
     }
   }
 
+  // Allow the texture loader to pre-compress the texture.
+  if (egg_tex->get_compression_mode() == EggTexture::CM_on) {
+    options.set_texture_flags(options.get_texture_flags() | LoaderOptions::TF_allow_compression);
+  }
+
   PT(Texture) tex;
   switch (egg_tex->get_texture_type()) {
   case EggTexture::TT_unspecified:

+ 4 - 0
panda/src/egg2pg/eggRenderState.cxx

@@ -333,6 +333,10 @@ fill_state(EggPrimitive *egg_prim) {
     add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_dual));
     break;
 
+  case EggRenderMode::AM_premultiplied:
+    add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_premultiplied_alpha));
+    break;
+
   default:
     break;
   }

+ 5 - 3
panda/src/egg2pg/eggSaver.cxx

@@ -686,12 +686,15 @@ convert_primitive(const GeomVertexData *vertex_data,
         break;
       case TransparencyAttrib::M_alpha:
         if (has_depthwrite && (depthwrite == DepthWriteAttrib::M_off)) {
-            tex_trans = EggRenderMode::AM_blend_no_occlude;
-                has_depthwrite = false;
+          tex_trans = EggRenderMode::AM_blend_no_occlude;
+          has_depthwrite = false;
         } else {
           tex_trans = EggRenderMode::AM_blend;
         }
         break;
+      case TransparencyAttrib::M_premultiplied_alpha:
+        tex_trans = EggRenderMode::AM_premultiplied;
+        break;
       case TransparencyAttrib::M_multisample:
         tex_trans = EggRenderMode::AM_ms;
         break;
@@ -705,7 +708,6 @@ convert_primitive(const GeomVertexData *vertex_data,
         tex_trans = EggRenderMode::AM_dual;
         break;
       default:  // intentional fall-through
-      case TransparencyAttrib::M_notused:
         break;
     }
     if (tex_trans != EggRenderMode::AM_unspecified) {

+ 10 - 2
panda/src/express/virtualFileMountRamdisk.cxx

@@ -235,7 +235,13 @@ open_write_file(const Filename &file, bool truncate) {
   if (truncate) {
     // Reset to an empty string.
     f->_data.str(string());
-    f->_timestamp = time(NULL);
+
+    // Instead of setting the time, we ensure that we always store a newer time.
+    // This is a workarround for the case that a file is written twice per
+    // second, since the timer only has a one second precision. The proper
+    // solution to fix this would be to switch to a higher precision
+    // timer everywhere.
+    f->_timestamp = max(f->_timestamp + 1, time(NULL));
   }
 
   return new OSubStream(&f->_wrapper, 0, 0);
@@ -275,7 +281,9 @@ open_read_write_file(const Filename &file, bool truncate) {
   if (truncate) {
     // Reset to an empty string.
     f->_data.str(string());
-    f->_timestamp = time(NULL);
+
+    // See open_write_file
+    f->_timestamp = max(f->_timestamp + 1, time(NULL));
   }
 
   return new SubStream(&f->_wrapper, 0, 0);

+ 2 - 0
panda/src/framework/config_framework.cxx

@@ -31,6 +31,8 @@ ConfigVariableBool show_frame_rate_meter
 ("show-frame-rate-meter", false);
 ConfigVariableBool show_scene_graph_analyzer_meter
 ("show-scene-graph-analyzer-meter", false);
+ConfigVariableBool print_pipe_types
+("print-pipe-types", true);
 ConfigVariableString window_type
 ("window-type", "onscreen");
 

+ 1 - 0
panda/src/framework/config_framework.h

@@ -27,6 +27,7 @@ NotifyCategoryDecl(framework, EXPCL_FRAMEWORK, EXPTP_FRAMEWORK);
 extern ConfigVariableDouble aspect_ratio;
 extern ConfigVariableBool show_frame_rate_meter;
 extern ConfigVariableBool show_scene_graph_analyzer_meter;
+extern ConfigVariableBool print_pipe_types;
 extern ConfigVariableString window_type;
 
 extern ConfigVariableString record_session;

+ 5 - 1
panda/src/framework/pandaFramework.cxx

@@ -769,7 +769,11 @@ make_default_pipe() {
   // folks) that have been loaded in at runtime from the load-display andor
   // aux-display Configrc variables.
   GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr();
-  selection->print_pipe_types();
+
+  if (print_pipe_types) {
+    selection->print_pipe_types();
+  }
+
   _default_pipe = selection->make_default_pipe();
 
   if (_default_pipe == (GraphicsPipe*)NULL) {

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

@@ -169,7 +169,7 @@ static const string default_fshader =
   "#version 130\n"
   "in vec2 texcoord;\n"
   "in vec4 color;\n"
-  "out vec4 p3d_FragColor;"
+  "out vec4 p3d_FragColor;\n"
   "uniform sampler2D p3d_Texture0;\n"
   "uniform vec4 p3d_TexAlphaOnly;\n"
 #else
@@ -414,7 +414,12 @@ debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei l
     break;
 
   case GL_DEBUG_SEVERITY_MEDIUM:
-    level = NS_warning;
+    if (type == GL_DEBUG_TYPE_PERFORMANCE) {
+      // Performance warnings should really be "info".
+      level = NS_info;
+    } else {
+      level = NS_warning;
+    }
     break;
 
   case GL_DEBUG_SEVERITY_LOW:
@@ -2513,7 +2518,7 @@ reset() {
 
   if (core_profile) {
     // TODO: better detection mechanism?
-    _supports_stencil = true;
+    _supports_stencil = support_stencil;
   }
 #ifdef SUPPORT_FIXED_FUNCTION
   else if (support_stencil) {
@@ -2984,7 +2989,7 @@ clear(DrawableRegion *clearable) {
     mask |= GL_DEPTH_BUFFER_BIT;
   }
 
-  if (clearable->get_clear_stencil_active()) {
+  if (_supports_stencil && clearable->get_clear_stencil_active()) {
     glStencilMask(~0);
     glClearStencil(clearable->get_clear_stencil());
     mask |= GL_STENCIL_BUFFER_BIT;
@@ -4812,6 +4817,7 @@ update_texture(TextureContext *tc, bool force) {
     if (gtc->was_properties_modified()) {
       specify_texture(gtc, tex->get_default_sampler());
     }
+
     bool okflag = upload_texture(gtc, force, tex->uses_mipmaps());
     if (!okflag) {
       GLCAT.error()
@@ -6233,7 +6239,9 @@ do_issue_render_mode() {
   }
   report_my_gl_errors();
 
+#ifdef SUPPORT_FIXED_FUNCTION
   do_point_size();
+#endif
 }
 
 /**
@@ -6714,6 +6722,19 @@ do_issue_blending() {
     }
     return;
 
+  case TransparencyAttrib::M_premultiplied_alpha:
+    enable_multisample_alpha_one(false);
+    enable_multisample_alpha_mask(false);
+    enable_blend(true);
+    _glBlendEquation(GL_FUNC_ADD);
+    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+    if (GLCAT.is_spam()) {
+      GLCAT.spam() << "glBlendEquation(GL_FUNC_ADD)\n";
+      GLCAT.spam() << "glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)\n";
+    }
+    return;
+
   case TransparencyAttrib::M_multisample:
     // We need to enable *both* of these in M_multisample case.
     enable_multisample_alpha_one(true);
@@ -12698,9 +12719,9 @@ extract_texture_image(PTA_uchar &image, size_t &page_size,
  * Internally sets the point size parameters after any of the properties have
  * changed that might affect this.
  */
+#ifdef SUPPORT_FIXED_FUNCTION
 void CLP(GraphicsStateGuardian)::
 do_point_size() {
-#ifndef OPENGLES_2
   if (!_point_perspective) {
     // Normal, constant-sized points.  Here _point_size is a width in pixels.
     static LVecBase3f constant(1.0f, 0.0f, 0.0f);
@@ -12730,8 +12751,8 @@ do_point_size() {
   }
 
   report_my_gl_errors();
-#endif
 }
+#endif
 
 /**
  * Returns true if this particular GSG supports the specified Cg Shader

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

@@ -567,7 +567,9 @@ protected:
            Texture::ComponentType type,
            Texture::CompressionMode compression, int n);
 
+#ifdef SUPPORT_FIXED_FUNCTION
   void do_point_size();
+#endif
 
   enum AutoAntialiasMode {
     AA_poly,

+ 21 - 18
panda/src/glstuff/glShaderContext_src.cxx

@@ -1462,24 +1462,27 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) {
         return;
       }
 #ifndef OPENGLES
-      case GL_IMAGE_1D_EXT:
-      case GL_IMAGE_2D_EXT:
-      case GL_IMAGE_3D_EXT:
-      case GL_IMAGE_CUBE_EXT:
-      case GL_IMAGE_2D_ARRAY_EXT:
-      case GL_IMAGE_BUFFER_EXT:
-      case GL_INT_IMAGE_1D_EXT:
-      case GL_INT_IMAGE_2D_EXT:
-      case GL_INT_IMAGE_3D_EXT:
-      case GL_INT_IMAGE_CUBE_EXT:
-      case GL_INT_IMAGE_2D_ARRAY_EXT:
-      case GL_INT_IMAGE_BUFFER_EXT:
-      case GL_UNSIGNED_INT_IMAGE_1D_EXT:
-      case GL_UNSIGNED_INT_IMAGE_2D_EXT:
-      case GL_UNSIGNED_INT_IMAGE_3D_EXT:
-      case GL_UNSIGNED_INT_IMAGE_CUBE_EXT:
-      case GL_UNSIGNED_INT_IMAGE_2D_ARRAY_EXT:
-      case GL_UNSIGNED_INT_IMAGE_BUFFER_EXT:
+      case GL_IMAGE_1D:
+      case GL_IMAGE_2D:
+      case GL_IMAGE_3D:
+      case GL_IMAGE_CUBE:
+      case GL_IMAGE_2D_ARRAY:
+      case GL_IMAGE_CUBE_MAP_ARRAY:
+      case GL_IMAGE_BUFFER:
+      case GL_INT_IMAGE_1D:
+      case GL_INT_IMAGE_2D:
+      case GL_INT_IMAGE_3D:
+      case GL_INT_IMAGE_CUBE:
+      case GL_INT_IMAGE_2D_ARRAY:
+      case GL_INT_IMAGE_CUBE_MAP_ARRAY:
+      case GL_INT_IMAGE_BUFFER:
+      case GL_UNSIGNED_INT_IMAGE_1D:
+      case GL_UNSIGNED_INT_IMAGE_2D:
+      case GL_UNSIGNED_INT_IMAGE_3D:
+      case GL_UNSIGNED_INT_IMAGE_CUBE:
+      case GL_UNSIGNED_INT_IMAGE_2D_ARRAY:
+      case GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY:
+      case GL_UNSIGNED_INT_IMAGE_BUFFER:
         // This won't really change at runtime, so we might as well bind once
         // and then forget about it.
         _glgsg->_glUniform1i(p, _glsl_img_inputs.size());

+ 0 - 7
panda/src/gobj/config_gobj.cxx

@@ -112,13 +112,6 @@ ConfigVariableBool keep_texture_ram
           "texture image from disk; but it will consume memory somewhat "
           "wastefully."));
 
-ConfigVariableBool compressed_textures
-("compressed-textures", false,
- PRC_DESC("Set this to true to compress textures as they are loaded into "
-          "texture memory, if the driver supports this.  Specifically, this "
-          "changes the meaning of set_compression(Texture::CM_default) to "
-          "Texture::CM_on."));
-
 ConfigVariableBool driver_compress_textures
 ("driver-compress-textures", false,
  PRC_DESC("Set this true to ask the graphics driver to compress textures, "

+ 0 - 1
panda/src/gobj/config_gobj.h

@@ -36,7 +36,6 @@ extern EXPCL_PANDA_GOBJ ConfigVariableList exclude_texture_scale;
 
 
 extern EXPCL_PANDA_GOBJ ConfigVariableBool keep_texture_ram;
-extern EXPCL_PANDA_GOBJ ConfigVariableBool compressed_textures;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool driver_compress_textures;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool driver_generate_mipmaps;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool vertex_buffers;

+ 58 - 2
panda/src/gobj/lens.cxx

@@ -139,7 +139,7 @@ get_min_fov() const {
 
 /**
  * Returns the default near plane distance that will be assigned to each
- * newly-created lens.  This is read from the Configrc file.
+ * newly-created lens.  This is read from the Config.prc file.
  */
 PN_stdfloat Lens::
 get_default_near() {
@@ -148,7 +148,7 @@ get_default_near() {
 
 /**
  * Returns the default far plane distance that will be assigned to each newly-
- * created lens.  This is read from the Configrc file.
+ * created lens.  This is read from the Config.prc file.
  */
 PN_stdfloat Lens::
 get_default_far() {
@@ -1930,6 +1930,35 @@ write_datagram(BamWriter *manager, Datagram &dg) const {
   dg.add_stdfloat(_near_distance);
   dg.add_stdfloat(_far_distance);
   dg.add_uint16(_user_flags);
+
+  if (manager->get_file_minor_ver() < 41) {
+    return;
+  }
+
+  dg.add_stdfloat(_min_fov);
+  dg.add_stdfloat(_interocular_distance);
+  dg.add_stdfloat(_convergence_distance);
+
+  if (_user_flags & UF_view_hpr) {
+    _view_hpr.write_datagram(dg);
+  }
+
+  if (_user_flags & UF_view_vector) {
+    _view_vector.write_datagram(dg);
+    _up_vector.write_datagram(dg);
+  }
+
+  if (_user_flags & UF_view_mat) {
+    _lens_mat.write_datagram(dg);
+  }
+
+  if (_user_flags & UF_keystone) {
+    _keystone.write_datagram(dg);
+  }
+
+  if (_user_flags & UF_custom_film_mat) {
+    _custom_film_mat.write_datagram(dg);
+  }
 }
 
 /**
@@ -1949,6 +1978,33 @@ fillin(DatagramIterator &scan, BamReader *manager) {
   _far_distance = scan.get_stdfloat();
   _user_flags = scan.get_uint16();
 
+  if (manager->get_file_minor_ver() >= 41) {
+    _min_fov = scan.get_stdfloat();
+    _interocular_distance = scan.get_stdfloat();
+    _convergence_distance = scan.get_stdfloat();
+
+    if (_user_flags & UF_view_hpr) {
+      _view_hpr.read_datagram(scan);
+    }
+
+    if (_user_flags & UF_view_vector) {
+      _view_vector.read_datagram(scan);
+      _up_vector.read_datagram(scan);
+    }
+
+    if (_user_flags & UF_view_mat) {
+      _lens_mat.read_datagram(scan);
+    }
+
+    if (_user_flags & UF_keystone) {
+      _keystone.read_datagram(scan);
+    }
+
+    if (_user_flags & UF_custom_film_mat) {
+      _custom_film_mat.read_datagram(scan);
+    }
+  }
+
   _comp_flags = 0;
 }
 

+ 38 - 0
panda/src/gobj/matrixLens.cxx

@@ -77,6 +77,23 @@ register_with_read_factory() {
   BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
 }
 
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void MatrixLens::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  dg.add_uint8(_ml_flags);
+  _user_mat.write_datagram(dg);
+
+  if (_ml_flags & MF_has_left_eye) {
+    _left_eye_mat.write_datagram(dg);
+  }
+  if (_ml_flags & MF_has_right_eye) {
+    _left_eye_mat.write_datagram(dg);
+  }
+}
+
 /**
  * This function is called by the BamReader's factory when a new object of
  * type Lens is encountered in the Bam file.  It should create the Lens and
@@ -93,3 +110,24 @@ make_from_bam(const FactoryParams &params) {
 
   return lens;
 }
+
+/**
+ * This internal function is called by make_from_bam to read in all of the
+ * relevant data from the BamFile for the new MatrixLens.
+ */
+void MatrixLens::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  Lens::fillin(scan, manager);
+
+  if (manager->get_file_minor_ver() >= 41) {
+    _ml_flags = scan.get_uint8();
+
+    _user_mat.read_datagram(scan);
+    if (_ml_flags & MF_has_left_eye) {
+      _left_eye_mat.read_datagram(scan);
+    }
+    if (_ml_flags & MF_has_right_eye) {
+      _right_eye_mat.read_datagram(scan);
+    }
+  }
+}

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

@@ -70,9 +70,11 @@ private:
 
 public:
   static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
 
 protected:
   static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
 
 public:
   virtual TypeHandle get_type() const {

+ 5 - 2
panda/src/gobj/texture.I

@@ -1678,9 +1678,12 @@ clear_ram_mipmap_images() {
  */
 INLINE void Texture::
 generate_ram_mipmap_images() {
-  CDWriter cdata(_cycler, unlocked_ensure_ram_image(false));
+  // Don't use unlocked_ensure_ram_image here, because
+  // do_generate_ram_mipmap_images will want to decompress and recompress the
+  // image itself.
+  CDWriter cdata(_cycler, false);
   cdata->inc_image_modified();
-  do_generate_ram_mipmap_images(cdata);
+  do_generate_ram_mipmap_images(cdata, true);
 }
 
 /**

+ 25 - 9
panda/src/gobj/texture.cxx

@@ -2708,7 +2708,8 @@ do_read(CData *cdata, const Filename &fullpath, const Filename &alpha_fullpath,
       // If we intend to keep the ram image around, consider compressing it
       // etc.
       bool generate_mipmaps = ((options.get_texture_flags() & LoaderOptions::TF_generate_mipmaps) != 0);
-      do_consider_auto_process_ram_image(cdata, generate_mipmaps || uses_mipmaps(), true);
+      bool allow_compression = ((options.get_texture_flags() & LoaderOptions::TF_allow_compression) != 0);
+      do_consider_auto_process_ram_image(cdata, generate_mipmaps || uses_mipmaps(), allow_compression);
     }
   }
 
@@ -4277,7 +4278,12 @@ do_reload_ram_image(CData *cdata, bool allow_compression) {
   int orig_num_components = cdata->_num_components;
 
   LoaderOptions options;
-  options.set_texture_flags(LoaderOptions::TF_preload);
+  if (allow_compression) {
+    options.set_texture_flags(LoaderOptions::TF_preload |
+                              LoaderOptions::TF_allow_compression);
+  } else {
+    options.set_texture_flags(LoaderOptions::TF_preload);
+  }
   do_read(cdata, cdata->_fullpath, cdata->_alpha_fullpath,
           cdata->_primary_file_num_channels, cdata->_alpha_file_channel,
           z, n, cdata->_has_read_pages, cdata->_has_read_mipmaps, options, NULL);
@@ -4618,7 +4624,7 @@ do_consider_auto_process_ram_image(CData *cdata, bool generate_mipmaps,
 
   if (generate_mipmaps && !driver_generate_mipmaps &&
       cdata->_ram_images.size() == 1) {
-    do_generate_ram_mipmap_images(cdata);
+    do_generate_ram_mipmap_images(cdata, false);
     modified = true;
   }
 
@@ -4725,7 +4731,7 @@ do_compress_ram_image(CData *cdata, Texture::CompressionMode compression,
     if (!do_has_all_ram_mipmap_images(cdata)) {
       // If we're about to compress the RAM image, we should ensure that we
       // have all of the mipmap levels first.
-      do_generate_ram_mipmap_images(cdata);
+      do_generate_ram_mipmap_images(cdata, false);
     }
 
     RamImages compressed_ram_images;
@@ -6232,7 +6238,10 @@ do_set_simple_ram_image(CData *cdata, CPTA_uchar image, int x_size, int y_size)
  */
 int Texture::
 do_get_expected_num_mipmap_levels(const CData *cdata) const {
-  int size = max(cdata->_x_size, max(cdata->_y_size, cdata->_z_size));
+  int size = max(cdata->_x_size, cdata->_y_size);
+  if (cdata->_texture_type == Texture::TT_3d_texture) {
+    size = max(size, cdata->_z_size);
+  }
   int count = 1;
   while (size > 1) {
     size >>= 1;
@@ -6331,10 +6340,12 @@ do_clear_ram_mipmap_images(CData *cdata) {
 }
 
 /**
- *
+ * Generates the RAM mipmap images for this texture, first uncompressing it as
+ * required.  Will recompress the image if it was originally compressed,
+ * unless allow_recompress is true.
  */
 void Texture::
-do_generate_ram_mipmap_images(CData *cdata) {
+do_generate_ram_mipmap_images(CData *cdata, bool allow_recompress) {
   nassertv(do_has_ram_image(cdata));
 
   if (do_get_expected_num_mipmap_levels(cdata) == 1) {
@@ -6396,7 +6407,7 @@ do_generate_ram_mipmap_images(CData *cdata) {
     }
   }
 
-  if (orig_compression_mode != CM_off) {
+  if (orig_compression_mode != CM_off && allow_recompress) {
     // Now attempt to recompress the mipmap images according to the original
     // compression mode.  We don't need to bother compressing the first image
     // (it was already compressed, after all), so temporarily remove it from
@@ -6415,6 +6426,11 @@ do_generate_ram_mipmap_images(CData *cdata) {
     bool success = do_compress_ram_image(cdata, orig_compression_mode, QL_default, NULL);
     // Now restore the toplevel image.
     if (success) {
+      if (gobj_cat.is_debug()) {
+        gobj_cat.debug()
+          << "Compressed " << get_name() << " generated mipmaps with "
+          << cdata->_ram_image_compression << "\n";
+      }
       cdata->_ram_images.insert(cdata->_ram_images.begin(), orig_compressed_image);
     } else {
       cdata->_ram_images.insert(cdata->_ram_images.begin(), uncompressed_image);
@@ -8252,7 +8268,7 @@ do_squish(CData *cdata, Texture::CompressionMode compression, int squish_flags)
   if (!do_has_all_ram_mipmap_images(cdata)) {
     // If we're about to compress the RAM image, we should ensure that we have
     // all of the mipmap levels first.
-    do_generate_ram_mipmap_images(cdata);
+    do_generate_ram_mipmap_images(cdata, false);
   }
 
   RamImages compressed_ram_images;

+ 1 - 1
panda/src/gobj/texture.h

@@ -697,7 +697,7 @@ protected:
   INLINE void do_clear_ram_image(CData *cdata);
   void do_clear_simple_ram_image(CData *cdata);
   void do_clear_ram_mipmap_images(CData *cdata);
-  void do_generate_ram_mipmap_images(CData *cdata);
+  void do_generate_ram_mipmap_images(CData *cdata, bool allow_recompress);
   void do_set_pad_size(CData *cdata, int x, int y, int z);
   virtual bool do_can_reload(const CData *cdata) const;
   bool do_reload(CData *cdata);

+ 8 - 0
panda/src/gobj/texturePeeker.I

@@ -48,3 +48,11 @@ INLINE int TexturePeeker::
 get_z_size() const {
   return _z_size;
 }
+
+/**
+ * Returns whether a given coordinate is inside of the texture dimensions.
+ */
+INLINE bool TexturePeeker::
+has_pixel(size_t x, size_t y) const {
+  return x < _x_size && y < _y_size;
+}

+ 72 - 2
panda/src/gobj/texturePeeker.cxx

@@ -91,8 +91,11 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
   switch (_format) {
   case Texture::F_depth_stencil:
   case Texture::F_depth_component:
-
+  case Texture::F_depth_component16:
+  case Texture::F_depth_component24:
+  case Texture::F_depth_component32:
   case Texture::F_red:
+  case Texture::F_r16:
     _get_texel = get_texel_r;
     break;
 
@@ -109,23 +112,30 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
     break;
 
   case Texture::F_luminance:
+  case Texture::F_sluminance:
     _get_texel = get_texel_l;
     break;
 
   case Texture::F_luminance_alpha:
+  case Texture::F_sluminance_alpha:
   case Texture::F_luminance_alphamask:
     _get_texel = get_texel_la;
     break;
 
   case Texture::F_rgb:
+  case Texture::F_srgb:
   case Texture::F_rgb5:
   case Texture::F_rgb8:
   case Texture::F_rgb12:
+  case Texture::F_rgb16:
   case Texture::F_rgb332:
+  case Texture::F_r11_g11_b10:
+  case Texture::F_rgb9_e5:
     _get_texel = get_texel_rgb;
     break;
 
   case Texture::F_rgba:
+  case Texture::F_srgb_alpha:
   case Texture::F_rgbm:
   case Texture::F_rgba4:
   case Texture::F_rgba5:
@@ -133,10 +143,13 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
   case Texture::F_rgba12:
   case Texture::F_rgba16:
   case Texture::F_rgba32:
+  case Texture::F_rgb10_a2:
     _get_texel = get_texel_rgba;
     break;
   default:
     // Not supported.
+    gobj_cat.error() << "Unsupported texture peeker format: "
+      << Texture::format_format(_format) << endl;
     _image.clear();
     return;
   }
@@ -155,13 +168,70 @@ void TexturePeeker::
 lookup(LColor &color, PN_stdfloat u, PN_stdfloat v) const {
   int x = int((u - cfloor(u)) * (PN_stdfloat)_x_size) % _x_size;
   int y = int((v - cfloor(v)) * (PN_stdfloat)_y_size) % _y_size;
+  fetch_pixel(color, x, y);
+}
 
+/**
+ *  Works like TexturePeeker::lookup(), but instead uv-coordinates integer
+ *  coordinates are used.
+ */
+void TexturePeeker::
+fetch_pixel(LColor& color, size_t x, size_t y) const {
   nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size);
   const unsigned char *p = _image.p() + (y * _x_size + x) * _pixel_width;
-
   (*_get_texel)(color, p, _get_component);
 }
 
+
+/**
+ * Performs a bilinear lookup to retrieve the color value stored at the uv
+ * coordinate (u, v).
+ *
+ * In case the point is outside of the uv range, color is set to zero,
+ * and false is returned.  Otherwise true is returned.
+ */
+bool TexturePeeker::
+lookup_bilinear(LColor &color, PN_stdfloat u, PN_stdfloat v) const {
+  color = LColor::zero();
+
+  u = u * _x_size - 0.5;
+  v = v * _y_size - 0.5;
+
+  int min_u = int(floor(u));
+  int min_v = int(floor(v));
+
+  PN_stdfloat frac_u = u - min_u;
+  PN_stdfloat frac_v = v - min_v;
+
+  LColor p00(LColor::zero()), p01(LColor::zero()), p10(LColor::zero()), p11(LColor::zero());
+  PN_stdfloat w00 = 0.0, w01 = 0.0, w10 = 0.0, w11 = 0.0;
+
+  if (has_pixel(min_u, min_v)) {
+    w00 = (1.0 - frac_v) * (1.0 - frac_u);
+    fetch_pixel(p00, min_u, min_v);
+  }
+  if (has_pixel(min_u + 1, min_v)) {
+    w10 = (1.0 - frac_v) * frac_u;
+    fetch_pixel(p10, min_u + 1, min_v);
+  }
+  if (has_pixel(min_u, min_v + 1)) {
+    w01 = frac_v * (1.0 - frac_u);
+    fetch_pixel(p01, min_u, min_v + 1);
+  }
+  if (has_pixel(min_u + 1, min_v + 1)) {
+    w11 = frac_v * frac_u;
+    fetch_pixel(p11, min_u + 1, min_v + 1);
+  }
+
+  PN_stdfloat net_w = w00 + w01 + w10 + w11;
+  if (net_w == 0.0) {
+    return false;
+  }
+
+  color = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11) / net_w;
+  return true;
+}
+
 /**
  * Fills "color" with the RGBA color of the texel at point (u, v, w).
  *

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

@@ -36,8 +36,11 @@ PUBLISHED:
   INLINE int get_y_size() const;
   INLINE int get_z_size() const;
 
+  INLINE bool has_pixel(size_t x, size_t y) const;
   void lookup(LColor &color, PN_stdfloat u, PN_stdfloat v) const;
   void lookup(LColor &color, PN_stdfloat u, PN_stdfloat v, PN_stdfloat w) const;
+  void fetch_pixel(LColor &color, size_t x, size_t y) const;
+  bool lookup_bilinear(LColor &color, PN_stdfloat u, PN_stdfloat v) const;
   void filter_rect(LColor &color,
                    PN_stdfloat min_u, PN_stdfloat min_v,
                    PN_stdfloat max_u, PN_stdfloat max_v) const;

+ 24 - 2
panda/src/pgraph/camera.cxx

@@ -26,9 +26,9 @@ Camera(const string &name, Lens *lens) :
   LensNode(name, lens),
   _active(true),
   _camera_mask(~PandaNode::get_overall_bit()),
-  _initial_state(RenderState::make_empty())
+  _initial_state(RenderState::make_empty()),
+  _lod_scale(1)
 {
-  set_lod_scale(1.0);
 }
 
 /**
@@ -271,6 +271,23 @@ write_datagram(BamWriter *manager, Datagram &dg) {
 
   dg.add_bool(_active);
   dg.add_uint32(_camera_mask.get_word());
+
+  manager->write_pointer(dg, _initial_state);
+  dg.add_stdfloat(_lod_scale);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Camera::complete_pointers
+//       Access: Public, Virtual
+//  Description: Receives an array of pointers, one for each time
+//               manager->read_pointer() was called in fillin().
+//               Returns the number of pointers processed.
+////////////////////////////////////////////////////////////////////
+int Camera::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = LensNode::complete_pointers(p_list, manager);
+  _initial_state = DCAST(RenderState, p_list[pi++]);
+  return pi;
 }
 
 /**
@@ -300,4 +317,9 @@ fillin(DatagramIterator &scan, BamReader *manager) {
 
   _active = scan.get_bool();
   _camera_mask.set_word(scan.get_uint32());
+
+  if (manager->get_file_minor_ver() >= 41) {
+    manager->read_pointer(scan);
+    _lod_scale = scan.get_stdfloat();
+  }
 }

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

@@ -124,6 +124,8 @@ private:
 public:
   static void register_with_read_factory();
   virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  virtual int complete_pointers(TypedWritable **plist,
+                                BamReader *manager);
 
 protected:
   static TypedWritable *make_from_bam(const FactoryParams &params);

+ 1 - 0
panda/src/pgraph/cullResult.cxx

@@ -131,6 +131,7 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
   if (object->_state->get_attrib(trans)) {
     switch (trans->get_mode()) {
     case TransparencyAttrib::M_alpha:
+    case TransparencyAttrib::M_premultiplied_alpha:
       // M_alpha implies an alpha-write test, so we don't waste time writing
       // 0-valued pixels.
       object->_state = object->_state->compose(get_alpha_state());

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

@@ -47,7 +47,7 @@ apply_transform_and_state(CullTraverser *trav) {
   _node_reader.compose_draw_mask(_draw_mask);
 
   apply_transform_and_state(trav, _node_reader.get_transform(),
-                            node_state, _node_reader.get_effects(),
+                            MOVE(node_state), _node_reader.get_effects(),
                             _node_reader.get_off_clip_planes());
 }
 

+ 35 - 5
panda/src/pgraph/lensNode.cxx

@@ -218,9 +218,18 @@ void LensNode::
 write_datagram(BamWriter *manager, Datagram &dg) {
   PandaNode::write_datagram(manager, dg);
 
-  // For now, we only write out lens 0, simply because that's what we always
-  // have done.  Should probably write out all lenses for the future.
-  manager->write_pointer(dg, get_lens(0));
+  if (manager->get_file_minor_ver() < 41) {
+    // Prior to bam 6.41, we stored only one lens.
+    manager->write_pointer(dg, get_lens(0));
+  } else {
+    dg.add_uint16(_lenses.size());
+
+    Lenses::const_iterator li;
+    for (li = _lenses.begin(); li != _lenses.end(); ++li) {
+      manager->write_pointer(dg, (*li)._lens);
+      dg.add_bool((*li)._is_active);
+    }
+  }
 }
 
 /**
@@ -230,7 +239,16 @@ write_datagram(BamWriter *manager, Datagram &dg) {
 int LensNode::
 complete_pointers(TypedWritable **p_list, BamReader *manager) {
   int pi = PandaNode::complete_pointers(p_list, manager);
-  set_lens(0, DCAST(Lens, p_list[pi++]));
+
+  Lenses::iterator li;
+  for (li = _lenses.begin(); li != _lenses.end(); ++li) {
+    (*li)._lens = DCAST(Lens, p_list[pi++]);
+  }
+
+  if (_shown_frustum != (PandaNode *)NULL) {
+    show_frustum();
+  }
+
   return pi;
 }
 
@@ -259,5 +277,17 @@ void LensNode::
 fillin(DatagramIterator &scan, BamReader *manager) {
   PandaNode::fillin(scan, manager);
 
-  manager->read_pointer(scan);
+  if (manager->get_file_minor_ver() < 41) {
+    // Prior to bam 6.41, we stored only one lens.
+    _lenses.resize(1);
+    manager->read_pointer(scan);
+
+  } else {
+    _lenses.resize(scan.get_uint16());
+    Lenses::iterator li;
+    for (li = _lenses.begin(); li != _lenses.end(); ++li) {
+      manager->read_pointer(scan);
+      (*li)._is_active = scan.get_bool();
+    }
+  }
 }

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

@@ -3750,6 +3750,8 @@ void PandaNode::
 fillin(DatagramIterator &scan, BamReader *manager) {
   TypedWritable::fillin(scan, manager);
 
+  remove_all_children();
+
   string name = scan.get_string();
   set_name(name);
 

+ 5 - 4
panda/src/pgraph/renderState.cxx

@@ -1853,8 +1853,8 @@ determine_bin_index() {
   string bin_name;
   _draw_order = 0;
 
-  const CullBinAttrib *bin = DCAST(CullBinAttrib, get_attrib(CullBinAttrib::get_class_slot()));
-  if (bin != (const CullBinAttrib *)NULL) {
+  const CullBinAttrib *bin;
+  if (get_attrib(bin)) {
     bin_name = bin->get_bin_name();
     _draw_order = bin->get_draw_order();
   }
@@ -1864,10 +1864,11 @@ determine_bin_index() {
     // opaque or transparent, based on the transparency setting.
     bin_name = "opaque";
 
-    const TransparencyAttrib *transparency = DCAST(TransparencyAttrib, get_attrib(TransparencyAttrib::get_class_slot()));
-    if (transparency != (const TransparencyAttrib *)NULL) {
+    const TransparencyAttrib *transparency;
+    if (get_attrib(transparency)) {
       switch (transparency->get_mode()) {
       case TransparencyAttrib::M_alpha:
+      case TransparencyAttrib::M_premultiplied_alpha:
       case TransparencyAttrib::M_dual:
         // These transparency modes require special back-to-front sorting.
         bin_name = "transparent";

+ 4 - 3
panda/src/pgraph/transparencyAttrib.cxx

@@ -55,6 +55,10 @@ output(ostream &out) const {
     out << "alpha";
     break;
 
+  case M_premultiplied_alpha:
+    out << "premultiplied alpha";
+    break;
+
   case M_multisample:
     out << "multisample";
     break;
@@ -70,9 +74,6 @@ output(ostream &out) const {
   case M_dual:
     out << "dual";
     break;
-
-  case M_notused:
-    break;
   }
 }
 

+ 1 - 1
panda/src/pgraph/transparencyAttrib.h

@@ -36,7 +36,7 @@ PUBLISHED:
     // corresponded to M_none or M_alpha).
     M_none = 0,         // No transparency.
     M_alpha = 1,        // Normal transparency, panda will sort back-to-front.
-    M_notused,          // Unused placeholder.  Do not use this.
+    M_premultiplied_alpha, // Assume textures use premultiplied alpha.
     M_multisample,      // Uses ms buffer, alpha values modified to 1.0.
     M_multisample_mask, // Uses ms buffer, alpha values not modified.
     M_binary,           // Only writes pixels with alpha >= 0.5.

+ 24 - 2
panda/src/pgraphnodes/pointLight.I

@@ -18,6 +18,7 @@ INLINE PointLight::CData::
 CData() :
   _specular_color(1.0f, 1.0f, 1.0f, 1.0f),
   _attenuation(1.0f, 0.0f, 0.0f),
+  _max_distance(make_inf((PN_stdfloat)0)),
   _point(0.0f, 0.0f, 0.0f)
 {
 }
@@ -29,6 +30,7 @@ INLINE PointLight::CData::
 CData(const PointLight::CData &copy) :
   _specular_color(copy._specular_color),
   _attenuation(copy._attenuation),
+  _max_distance(copy._max_distance),
   _point(copy._point)
 {
 }
@@ -88,9 +90,29 @@ set_attenuation(const LVecBase3 &attenuation) {
   cdata->_attenuation = attenuation;
 }
 
+/**
+ * Returns the maximum distance at which the light has any effect, as previously
+ * specified by set_max_distance.
+ */
+INLINE PN_stdfloat PointLight::
+get_max_distance() const {
+  CDReader cdata(_cycler);
+  return cdata->_max_distance;
+}
+
+/**
+ * Sets the radius of the light's sphere of influence.  Beyond this distance, the
+ * light may be attenuated to zero, if this is supported by the shader.
+ */
+INLINE void PointLight::
+set_max_distance(PN_stdfloat max_distance) {
+  CDWriter cdata(_cycler);
+  cdata->_max_distance = max_distance;
+}
+
 /**
  * Returns the point in space at which the light is located.  This is local to
- * the coordinate space in which the light is assigned.
+ * the coordinate space in which the light is assigned, and is usually 0.
  */
 INLINE const LPoint3 &PointLight::
 get_point() const {
@@ -99,7 +121,7 @@ get_point() const {
 }
 
 /**
- * Sets the point in space at which the light is located.
+ * Sets the point in space at which the light is located.  Usually 0.
  */
 INLINE void PointLight::
 set_point(const LPoint3 &point) {

+ 13 - 2
panda/src/pgraphnodes/pointLight.cxx

@@ -33,9 +33,12 @@ make_copy() const {
  * Bam file.
  */
 void PointLight::CData::
-write_datagram(BamWriter *, Datagram &dg) const {
+write_datagram(BamWriter *manager, Datagram &dg) const {
   _specular_color.write_datagram(dg);
   _attenuation.write_datagram(dg);
+  if (manager->get_file_minor_ver() >= 41) {
+    dg.add_stdfloat(_max_distance);
+  }
   _point.write_datagram(dg);
 }
 
@@ -44,9 +47,12 @@ write_datagram(BamWriter *, Datagram &dg) const {
  * relevant data from the BamFile for the new Light.
  */
 void PointLight::CData::
-fillin(DatagramIterator &scan, BamReader *) {
+fillin(DatagramIterator &scan, BamReader *manager) {
   _specular_color.read_datagram(scan);
   _attenuation.read_datagram(scan);
+  if (manager->get_file_minor_ver() >= 41) {
+    _max_distance = scan.get_stdfloat();
+  }
   _point.read_datagram(scan);
 }
 
@@ -127,6 +133,11 @@ write(ostream &out, int indent_level) const {
   }
   indent(out, indent_level + 2)
     << "attenuation " << get_attenuation() << "\n";
+
+  if (!cinf(get_max_distance())) {
+    indent(out, indent_level + 2)
+      << "max distance " << get_max_distance() << "\n";
+  }
 }
 
 /**

+ 5 - 0
panda/src/pgraphnodes/pointLight.h

@@ -48,6 +48,10 @@ PUBLISHED:
   INLINE void set_attenuation(const LVecBase3 &attenuation);
   MAKE_PROPERTY(attenuation, get_attenuation, set_attenuation);
 
+  INLINE PN_stdfloat get_max_distance() const;
+  INLINE void set_max_distance(PN_stdfloat max_distance);
+  MAKE_PROPERTY(max_distance, get_max_distance, set_max_distance);
+
   INLINE const LPoint3 &get_point() const;
   INLINE void set_point(const LPoint3 &point);
   MAKE_PROPERTY(point, get_point, set_point);
@@ -75,6 +79,7 @@ private:
 
     LColor _specular_color;
     LVecBase3 _attenuation;
+    PN_stdfloat _max_distance;
     LPoint3 _point;
   };
 

+ 1 - 0
panda/src/pgraphnodes/shaderGenerator.cxx

@@ -209,6 +209,7 @@ analyze_renderstate(const RenderState *rs) {
   const TransparencyAttrib *transparency;
   rs->get_attrib_def(transparency);
   if ((transparency->get_mode() == TransparencyAttrib::M_alpha)||
+      (transparency->get_mode() == TransparencyAttrib::M_premultiplied_alpha)||
       (transparency->get_mode() == TransparencyAttrib::M_dual)) {
     _have_alpha_blend = true;
   }

+ 24 - 2
panda/src/pgraphnodes/spotlight.I

@@ -18,7 +18,8 @@ INLINE Spotlight::CData::
 CData() :
   _exponent(50.0f),
   _specular_color(1.0f, 1.0f, 1.0f, 1.0f),
-  _attenuation(1.0f, 0.0f, 0.0f)
+  _attenuation(1.0f, 0.0f, 0.0f),
+  _max_distance(make_inf((PN_stdfloat)0))
 {
 }
 
@@ -29,7 +30,8 @@ INLINE Spotlight::CData::
 CData(const Spotlight::CData &copy) :
   _exponent(copy._exponent),
   _specular_color(copy._specular_color),
-  _attenuation(copy._attenuation)
+  _attenuation(copy._attenuation),
+  _max_distance(copy._max_distance)
 {
 }
 
@@ -111,3 +113,23 @@ set_attenuation(const LVecBase3 &attenuation) {
   CDWriter cdata(_cycler);
   cdata->_attenuation = attenuation;
 }
+
+/**
+ * Returns the maximum distance at which the light has any effect, as previously
+ * specified by set_max_distance.
+ */
+INLINE PN_stdfloat Spotlight::
+get_max_distance() const {
+  CDReader cdata(_cycler);
+  return cdata->_max_distance;
+}
+
+/**
+ * Sets the radius of the light's sphere of influence.  Beyond this distance, the
+ * light may be attenuated to zero, if this is supported by the shader.
+ */
+INLINE void Spotlight::
+set_max_distance(PN_stdfloat max_distance) {
+  CDWriter cdata(_cycler);
+  cdata->_max_distance = max_distance;
+}

+ 13 - 2
panda/src/pgraphnodes/spotlight.cxx

@@ -37,10 +37,13 @@ make_copy() const {
  * Bam file.
  */
 void Spotlight::CData::
-write_datagram(BamWriter *, Datagram &dg) const {
+write_datagram(BamWriter *manager, Datagram &dg) const {
   dg.add_stdfloat(_exponent);
   _specular_color.write_datagram(dg);
   _attenuation.write_datagram(dg);
+  if (manager->get_file_minor_ver() >= 41) {
+    dg.add_stdfloat(_max_distance);
+  }
 }
 
 /**
@@ -48,10 +51,13 @@ write_datagram(BamWriter *, Datagram &dg) const {
  * relevant data from the BamFile for the new Light.
  */
 void Spotlight::CData::
-fillin(DatagramIterator &scan, BamReader *) {
+fillin(DatagramIterator &scan, BamReader *manager) {
   _exponent = scan.get_stdfloat();
   _specular_color.read_datagram(scan);
   _attenuation.read_datagram(scan);
+  if (manager->get_file_minor_ver() >= 41) {
+    _max_distance = scan.get_stdfloat();
+  }
 }
 
 /**
@@ -113,6 +119,11 @@ write(ostream &out, int indent_level) const {
   indent(out, indent_level + 2)
     << "exponent " << get_exponent() << "\n";
 
+  if (!cinf(get_max_distance())) {
+    indent(out, indent_level + 2)
+      << "max distance " << get_max_distance() << "\n";
+  }
+
   Lens *lens = get_lens();
   if (lens != (Lens *)NULL) {
     lens->write(out, indent_level + 2);

+ 5 - 0
panda/src/pgraphnodes/spotlight.h

@@ -59,6 +59,10 @@ PUBLISHED:
   INLINE void set_attenuation(const LVecBase3 &attenuation);
   MAKE_PROPERTY(attenuation, get_attenuation, set_attenuation);
 
+  INLINE PN_stdfloat get_max_distance() const;
+  INLINE void set_max_distance(PN_stdfloat max_distance);
+  MAKE_PROPERTY(max_distance, get_max_distance, set_max_distance);
+
   virtual int get_class_priority() const;
 
   static PT(Texture) make_spot(int pixel_width, PN_stdfloat full_radius,
@@ -92,6 +96,7 @@ private:
     PN_stdfloat _exponent;
     LColor _specular_color;
     LVecBase3 _attenuation;
+    PN_stdfloat _max_distance;
   };
 
   PipelineCycler<CData> _cycler;

+ 115 - 6
panda/src/pnmimage/pfmFile.cxx

@@ -1211,14 +1211,65 @@ void PfmFile::
 xform(const LMatrix4f &transform) {
   nassertv(is_valid());
 
-  for (int yi = 0; yi < _y_size; ++yi) {
-    for (int xi = 0; xi < _x_size; ++xi) {
-      if (!has_point(xi, yi)) {
-        continue;
+  int num_channels = get_num_channels();
+  switch (num_channels) {
+  case 1:
+    {
+      for (int yi = 0; yi < _y_size; ++yi) {
+        for (int xi = 0; xi < _x_size; ++xi) {
+          if (!has_point(xi, yi)) {
+            continue;
+          }
+          PN_float32 pi = get_point1(xi, yi);
+          LPoint3f po = transform.xform_point(LPoint3f(pi, 0.0, 0.0));
+          set_point1(xi, yi, po[0]);
+        }
+      }
+    }
+    break;
+
+  case 2:
+    {
+      for (int yi = 0; yi < _y_size; ++yi) {
+        for (int xi = 0; xi < _x_size; ++xi) {
+          if (!has_point(xi, yi)) {
+            continue;
+          }
+          LPoint2f pi = get_point2(xi, yi);
+          LPoint3f po = transform.xform_point(LPoint3f(pi[0], pi[1], 0.0));
+          set_point2(xi, yi, LPoint2f(po[0], po[1]));
+        }
+      }
+    }
+    break;
+
+  case 3:
+    {
+      for (int yi = 0; yi < _y_size; ++yi) {
+        for (int xi = 0; xi < _x_size; ++xi) {
+          if (!has_point(xi, yi)) {
+            continue;
+          }
+          LPoint3f &p = modify_point3(xi, yi);
+          transform.xform_point_general_in_place(p);
+        }
       }
-      LPoint3f &p = modify_point(xi, yi);
-      transform.xform_point_general_in_place(p);
     }
+    break;
+
+  case 4:
+    {
+      for (int yi = 0; yi < _y_size; ++yi) {
+        for (int xi = 0; xi < _x_size; ++xi) {
+          if (!has_point(xi, yi)) {
+            continue;
+          }
+          LPoint4f &p = modify_point4(xi, yi);
+          transform.xform_in_place(p);
+        }
+      }
+    }
+    break;
   }
 }
 
@@ -2158,6 +2209,64 @@ operator *= (float multiplier) {
   }
 }
 
+/**
+ * index_image is a WxH 1-channel image, while pixel_values is an Nx1
+ * image with any number of channels.  Typically pixel_values will be
+ * a 256x1 image.
+ *
+ * Fills the PfmFile with a new image the same width and height as
+ * index_image, with the same number of channels as pixel_values.
+ *
+ * Each pixel of the new image is computed with the formula:
+ *
+ * new_image(x, y) = pixel_values(index_image(x, y)[channel], 0)
+ *
+ * At present, no interpolation is performed; the nearest value in
+ * pixel_values is discovered.  This may change in the future.
+ */
+void PfmFile::
+indirect_1d_lookup(const PfmFile &index_image, int channel,
+                   const PfmFile &pixel_values) {
+  clear(index_image.get_x_size(), index_image.get_y_size(),
+        pixel_values.get_num_channels());
+
+  for (int yi = 0; yi < get_y_size(); ++yi) {
+    switch (get_num_channels()) {
+    case 1:
+      for (int xi = 0; xi < get_x_size(); ++xi) {
+        int v = int(index_image.get_channel(xi, yi, channel) * (pixel_values.get_x_size() - 1) + 0.5);
+        nassertv(v >= 0 && v < pixel_values.get_x_size());
+        set_point1(xi, yi, pixel_values.get_point1(v, 0));
+      }
+      break;
+
+    case 2:
+      for (int xi = 0; xi < get_x_size(); ++xi) {
+        int v = int(index_image.get_channel(xi, yi, channel) * (pixel_values.get_x_size() - 1) + 0.5);
+        nassertv(v >= 0 && v < pixel_values.get_x_size());
+        set_point2(xi, yi, pixel_values.get_point2(v, 0));
+      }
+      break;
+
+    case 3:
+      for (int xi = 0; xi < get_x_size(); ++xi) {
+        int v = int(index_image.get_channel(xi, yi, channel) * (pixel_values.get_x_size() - 1) + 0.5);
+        nassertv(v >= 0 && v < pixel_values.get_x_size());
+        set_point3(xi, yi, pixel_values.get_point3(v, 0));
+      }
+      break;
+
+    case 4:
+      for (int xi = 0; xi < get_x_size(); ++xi) {
+        int v = int(index_image.get_channel(xi, yi, channel) * (pixel_values.get_x_size() - 1) + 0.5);
+        nassertv(v >= 0 && v < pixel_values.get_x_size());
+        set_point4(xi, yi, pixel_values.get_point4(v, 0));
+      }
+      break;
+    }
+  }
+}
+
 /**
  * Adjusts each channel of the image by raising the corresponding component
  * value to the indicated exponent, such that L' = L ^ exponent.

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

@@ -158,6 +158,9 @@ PUBLISHED:
 
   void operator *= (float multiplier);
 
+  void indirect_1d_lookup(const PfmFile &index_image, int channel,
+                          const PfmFile &pixel_values);
+
   INLINE void gamma_correct(float from_gamma, float to_gamma);
   INLINE void gamma_correct_alpha(float from_gamma, float to_gamma);
   INLINE void apply_exponent(float gray_exponent);

+ 2 - 2
panda/src/pnmimage/pnmImage.cxx

@@ -1667,8 +1667,8 @@ fill_distance_outside(const PNMImage &mask, float threshold, int radius) {
  *
  * new_image(x, y) = pixel_values(index_image(x, y)[channel], 0)
  *
- * No interpolation is performed; the nearest value in pixel_values is
- * discovered.
+ * At present, no interpolation is performed; the nearest value in
+ * pixel_values is discovered.  This may change in the future.
  */
 void PNMImage::
 indirect_1d_lookup(const PNMImage &index_image, int channel,

+ 34 - 35
panda/src/putil/bam.h

@@ -25,42 +25,41 @@
 static const string _bam_header = string("pbj\0\n\r", 6);
 
 static const unsigned short _bam_major_ver = 6;
-// Bumped to major version 2 on 7600 due to major changes in Character.
-// Bumped to major version 3 on 12800 to change float64's to float32's.
-// Bumped to major version 4 on 41002 to store new scene graph.  Bumped to
-// major version 5 on 5605 for new Geom implementation.  Bumped to major
-// version 6 on 21106 to factor out PandaNode::CData.
+// Bumped to major version 2 on 2000-07-06 due to major changes in Character.
+// Bumped to major version 3 on 2000-12-08 to change float64's to float32's.
+// Bumped to major version 4 on 2002-04-10 to store new scene graph.
+// Bumped to major version 5 on 2005-05-06 for new Geom implementation.
+// Bumped to major version 6 on 2006-02-11 to factor out PandaNode::CData.
 
 static const unsigned short _bam_first_minor_ver = 14;
-static const unsigned short _bam_minor_ver = 40;
-/*
- * Bumped to minor version 14 on 121907 to change default ColorAttrib.  Bumped
- * to minor version 15 on 4908 to add TextureAttrib::_implicit_sort.  Bumped
- * to minor version 16 on 51308 to add Texture::_quality_level.  Bumped to
- * minor version 17 on 8608 to add PartBundle::_anim_preload.  Bumped to minor
- * version 18 on 81408 to add Texture::_simple_ram_image.  Bumped to minor
- * version 19 on 81408 to add PandaNode::_bounds_type.  Bumped to minor
- * version 20 on 42109 to add MovingPartBase::_forced_channel.  Bumped to
- * minor version 21 on 22608 to add BamEnums::BamObjectCode.  Bumped to minor
- * version 22 on 73109 to add UvScrollNode R speed.  Bumped to minor version
- * 23 on 5410 to add internal TextureAttrib overrides.  Bumped to minor
- * version 24 on 5410 to add internal TexMatrixAttrib overrides.  Bumped to
- * minor version 25 on 62211 to add support for caching movie files.  Bumped
- * to minor version 26 on 8511 to add multiview (stereo) Textures.  Bumped to
- * minor version 27 on 10911 to add stdfloat_double.  Bumped to minor version
- * 28 on 112811 to add Texture::_auto_texture_scale.  Bumped to minor version
- * 29 on 121711 to add GeomVertexColumn::_column_alignment.  Bumped to minor
- * version 30 on 12212 to add Texture::_pad_*_size.  Bumped to minor version
- * 31 on 21612 to add DepthOffsetAttrib::_min_value, _max_value.  Bumped to
- * minor version 32 on 61112 to add Texture::_has_read_mipmaps.  Bumped to
- * minor version 33 on 81713 to add UvScrollNode::_w_speed.  Bumped to minor
- * version 34 on 91614 to add ScissorAttrib::_off.  Bumped to minor version 35
- * on 12314 to change StencilAttrib.  Bumped to minor version 36 on 12914 to
- * add samplers and lod settings.  Bumped to minor version 37 on 12215 to add
- * GeomVertexArrayFormat::_divisor.  Bumped to minor version 38 on 41515 to
- * add various Bullet classes.  Bumped to minor version 39 on 1916 to change
- * lights and materials.  Bumped to minor version 40 on 11116 to make
- * NodePaths writable.
- */
+static const unsigned short _bam_minor_ver = 41;
+// Bumped to minor version 14 on 2007-12-19 to change default ColorAttrib.
+// Bumped to minor version 15 on 2008-04-09 to add TextureAttrib::_implicit_sort.
+// Bumped to minor version 16 on 2008-05-13 to add Texture::_quality_level.
+// Bumped to minor version 17 on 2008-08-06 to add PartBundle::_anim_preload.
+// Bumped to minor version 18 on 2008-08-14 to add Texture::_simple_ram_image.
+// Bumped to minor version 19 on 2008-08-14 to add PandaNode::_bounds_type.
+// Bumped to minor version 20 on 2009-04-21 to add MovingPartBase::_forced_channel.
+// Bumped to minor version 21 on 2008-02-26 to add BamEnums::BamObjectCode.
+// Bumped to minor version 22 on 2009-07-31 to add UvScrollNode R speed.
+// Bumped to minor version 23 on 2010-05-04 to add internal TextureAttrib overrides.
+// Bumped to minor version 24 on 2010-05-04 to add internal TexMatrixAttrib overrides.
+// Bumped to minor version 25 on 2011-06-22 to add support for caching movie files.
+// Bumped to minor version 26 on 2011-08-05 to add multiview (stereo) Textures.
+// Bumped to minor version 27 on 2011-10-09 to add stdfloat_double.
+// Bumped to minor version 28 on 2011-11-28 to add Texture::_auto_texture_scale.
+// Bumped to minor version 29 on 2011-12-17 to add GeomVertexColumn::_column_alignment.
+// Bumped to minor version 30 on 2012-01-22 to add Texture::_pad_*_size.
+// Bumped to minor version 31 on 2012-02-16 to add DepthOffsetAttrib::_min_value, _max_value.
+// Bumped to minor version 32 on 2012-06-11 to add Texture::_has_read_mipmaps.
+// Bumped to minor version 33 on 2013-08-17 to add UvScrollNode::_w_speed.
+// Bumped to minor version 34 on 2014-09-16 to add ScissorAttrib::_off.
+// Bumped to minor version 35 on 2014-12-03 to change StencilAttrib.
+// Bumped to minor version 36 on 2014-12-09 to add samplers and lod settings.
+// Bumped to minor version 37 on 2015-01-22 to add GeomVertexArrayFormat::_divisor.
+// Bumped to minor version 38 on 2015-04-15 to add various Bullet classes.
+// Bumped to minor version 39 on 2016-01-09 to change lights and materials.
+// Bumped to minor version 40 on 2016-01-11 to make NodePaths writable.
+// Bumped to minor version 41 on 2016-03-02 to change LensNode, Lens, and Camera.
 
 #endif

+ 2 - 1
panda/src/putil/bamReader.cxx

@@ -236,7 +236,8 @@ read_object() {
  * This flavor of read_object() returns both a TypedWritable and a
  * ReferenceCount pointer to the same object, so the reference count may be
  * tracked reliably, without having to know precisely what type of object we
- * have.  It returns true on success, or false on failure.
+ * have.
+ * @return true on success, or false on failure.
  */
 bool BamReader::
 read_object(TypedWritable *&ptr, ReferenceCount *&ref_ptr) {

+ 16 - 0
panda/src/putil/bamWriter.I

@@ -34,6 +34,22 @@ get_filename() const {
   return empty_filename;
 }
 
+/**
+ * Returns the major version number of the Bam file currently being written.
+ */
+INLINE int BamWriter::
+get_file_major_ver() const {
+  return _file_major;
+}
+
+/**
+ * Returns the minor version number of the Bam file currently being written.
+ */
+INLINE int BamWriter::
+get_file_minor_ver() const {
+  return _file_minor;
+}
+
 /**
  * Returns the endian preference indicated by the Bam file currently being
  * written.  This does not imply that every number is stored using the

+ 4 - 0
panda/src/putil/bamWriter.cxx

@@ -41,6 +41,8 @@ BamWriter(DatagramSink *target) :
   _next_pta_id = 1;
   _long_pta_id = false;
 
+  _file_major = _bam_major_ver;
+  _file_minor = _bam_minor_ver;
   _file_endian = bam_endian;
   _file_stdfloat_double = bam_stdfloat_double;
   _file_texture_mode = bam_texture_mode;
@@ -96,6 +98,8 @@ init() {
   _next_pta_id = 1;
   _long_pta_id = false;
 
+  _file_major = _bam_major_ver;
+  _file_minor = _bam_minor_ver;
   _file_endian = bam_endian;
   _file_texture_mode = bam_texture_mode;
 

+ 4 - 0
panda/src/putil/bamWriter.h

@@ -74,6 +74,9 @@ PUBLISHED:
   bool has_object(const TypedWritable *obj) const;
   void flush();
 
+  INLINE int get_file_major_ver() const;
+  INLINE int get_file_minor_ver() const;
+
   INLINE BamEndian get_file_endian() const;
   INLINE bool get_file_stdfloat_double() const;
 
@@ -115,6 +118,7 @@ private:
   int enqueue_object(const TypedWritable *object);
   bool flush_queue();
 
+  int _file_major, _file_minor;
   BamEndian _file_endian;
   bool _file_stdfloat_double;
   BamTextureMode _file_texture_mode;

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

@@ -143,6 +143,13 @@ ConfigVariableBool preload_simple_textures
           "in a sub-thread.  It's not generally necessary if you are "
           "loading bam files that were generated via egg2bam."));
 
+ConfigVariableBool compressed_textures
+("compressed-textures", false,
+ PRC_DESC("Set this to true to compress textures as they are loaded into "
+          "texture memory, if the driver supports this.  Specifically, this "
+          "changes the meaning of set_compression(Texture::CM_default) to "
+          "Texture::CM_on."));
+
 ConfigVariableBool cache_check_timestamps
 ("cache-check-timestamps", true,
  PRC_DESC("Set this true to check the timestamps on disk (when possible) "

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

@@ -46,6 +46,7 @@ extern ConfigVariableDouble sleep_precision;
 
 extern EXPCL_PANDA_PUTIL ConfigVariableBool preload_textures;
 extern EXPCL_PANDA_PUTIL ConfigVariableBool preload_simple_textures;
+extern EXPCL_PANDA_PUTIL ConfigVariableBool compressed_textures;
 extern EXPCL_PANDA_PUTIL ConfigVariableBool cache_check_timestamps;
 
 extern EXPCL_PANDA_PUTIL void init_libputil();

+ 8 - 0
panda/src/putil/loaderOptions.cxx

@@ -28,12 +28,16 @@ LoaderOptions(int flags) :
   // Shadowing the variables in config_util for static init ordering issues.
   static ConfigVariableBool *preload_textures;
   static ConfigVariableBool *preload_simple_textures;
+  static ConfigVariableBool *compressed_textures;
   if (preload_textures == NULL) {
     preload_textures = new ConfigVariableBool("preload-textures", true);
   }
   if (preload_simple_textures == NULL) {
     preload_simple_textures = new ConfigVariableBool("preload-simple-textures", false);
   }
+  if (compressed_textures == NULL) {
+    compressed_textures = new ConfigVariableBool("compressed-textures", false);
+  }
 
   if (*preload_textures) {
     _texture_flags |= TF_preload;
@@ -41,6 +45,9 @@ LoaderOptions(int flags) :
   if (*preload_simple_textures) {
     _texture_flags |= TF_preload_simple;
   }
+  if (*compressed_textures) {
+    _texture_flags |= TF_allow_compression;
+  }
 }
 
 /**
@@ -77,6 +84,7 @@ output(ostream &out) const {
   write_texture_flag(out, sep, "TF_preload_simple", TF_preload_simple);
   write_texture_flag(out, sep, "TF_allow_1d", TF_allow_1d);
   write_texture_flag(out, sep, "TF_generate_mipmaps", TF_generate_mipmaps);
+  write_texture_flag(out, sep, "TF_allow_compression", TF_allow_compression);
   if (sep.empty()) {
     out << "0";
   }

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

@@ -45,6 +45,7 @@ PUBLISHED:
     TF_multiview         = 0x0040,  // Load a multiview texture in pages
     TF_integer           = 0x0080,  // Load as an integer (RGB) texture
     TF_float             = 0x0100,  // Load as a floating-point (depth) texture
+    TF_allow_compression = 0x0200,  // Consider compressing RAM image
   };
 
   LoaderOptions(int flags = LF_search | LF_report_errors);

+ 15 - 0
panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx

@@ -804,6 +804,21 @@ begin_draw_primitives(const GeomPipelineReader *geom_reader,
     }
     break;
 
+  case TransparencyAttrib::M_premultiplied_alpha:
+    {
+      // Implement a color mask, with pre-multiplied alpha blending.
+      int op_a = get_color_blend_op(ColorBlendAttrib::O_one);
+      int op_b = get_color_blend_op(ColorBlendAttrib::O_one_minus_incoming_alpha);
+
+      if (srgb_blend) {
+        _c->zb->store_pix_func = store_pixel_funcs_sRGB[op_a][op_b][color_channels];
+      } else {
+        _c->zb->store_pix_func = store_pixel_funcs[op_a][op_b][color_channels];
+      }
+      color_write_state = 2;   // cgeneral
+    }
+    break;
+
   default:
     break;
   }