Browse Source

Implement texture clear, fix immutable tex support

rdb 11 years ago
parent
commit
01b669ccd8

+ 143 - 83
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -800,6 +800,21 @@ reset() {
     }
   }
 
+  _supports_clear_texture = false;
+#ifndef OPENGLES
+  if (is_at_least_gl_version(4, 4) || has_extension("GL_ARB_clear_texture")) {
+    _glClearTexImage = (PFNGLCLEARTEXIMAGEPROC)
+      get_extension_func("glClearTexImage");
+
+    if (_glClearTexImage == NULL) {
+      GLCAT.warning()
+        << "GL_ARB_clear_texture advertised as supported by OpenGL runtime, but could not get pointers to extension functions.\n";
+    } else {
+      _supports_clear_texture = true;
+    }
+  }
+#endif
+
   _supports_2d_texture_array = false;
 #ifndef OPENGLES
   _supports_2d_texture_array = has_extension("GL_EXT_texture_array");
@@ -7519,7 +7534,7 @@ get_external_image_format(Texture *tex) const {
 //               suitable internal format for GL textures.
 ////////////////////////////////////////////////////////////////////
 GLint CLP(GraphicsStateGuardian)::
-get_internal_image_format(Texture *tex) const {
+get_internal_image_format(Texture *tex, bool force_sized) const {
   Texture::CompressionMode compression = tex->get_compression();
   if (compression == Texture::CM_default) {
     compression = (compressed_textures) ? Texture::CM_on : Texture::CM_off;
@@ -7752,7 +7767,7 @@ get_internal_image_format(Texture *tex) const {
       } else
 #endif
       {
-        return GL_DEPTH_STENCIL;
+        return force_sized ? GL_DEPTH24_STENCIL8 : GL_DEPTH_STENCIL;
       }
     }
     // Fall through.
@@ -7764,7 +7779,7 @@ get_internal_image_format(Texture *tex) const {
     } else
 #endif
     {
-      return GL_DEPTH_COMPONENT;
+      return force_sized ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT;
     }
   case Texture::F_depth_component16:
 #ifdef OPENGLES
@@ -7811,7 +7826,7 @@ get_internal_image_format(Texture *tex) const {
     } else
 #endif
     {
-      return GL_RGBA;
+      return force_sized ? GL_RGBA8 : GL_RGBA;
     }
 
   case Texture::F_rgba4:
@@ -7821,7 +7836,7 @@ get_internal_image_format(Texture *tex) const {
   case Texture::F_rgba8:
     return GL_RGBA8_OES;
   case Texture::F_rgba12:
-    return GL_RGBA;
+    return force_sized ? GL_RGBA8 : GL_RGBA;
 #else
   case Texture::F_rgba8:
     return GL_RGBA8;
@@ -7843,7 +7858,7 @@ get_internal_image_format(Texture *tex) const {
     if (tex->get_component_type() == Texture::T_float) {
       return GL_RGB16F;
     } else {
-      return GL_RGB;
+      return force_sized ? GL_RGB8 : GL_RGB;
     }
 
   case Texture::F_rgb5:
@@ -7860,7 +7875,7 @@ get_internal_image_format(Texture *tex) const {
   case Texture::F_rgb8:
     return GL_RGB8_OES;
   case Texture::F_rgb12:
-    return GL_RGB;
+    return force_sized ? GL_RGB8 : GL_RGB;
   case Texture::F_rgb16:
     return GL_RGB16F;
 #else
@@ -7912,25 +7927,26 @@ get_internal_image_format(Texture *tex) const {
   case Texture::F_red:
   case Texture::F_green:
   case Texture::F_blue:
-    return GL_RED;
+    return force_sized ? GL_R8 : GL_RED;
 #endif
 
   case Texture::F_alpha:
-    return GL_ALPHA;
+    return force_sized ? GL_ALPHA8 : GL_ALPHA;
+
   case Texture::F_luminance:
     if (tex->get_component_type() == Texture::T_float) {
       return GL_LUMINANCE16F_ARB;
     } else if (tex->get_component_type() == Texture::T_unsigned_short) {
       return GL_LUMINANCE16;
     } else {
-      return GL_LUMINANCE;
+      return force_sized ? GL_LUMINANCE8 : GL_LUMINANCE;
     }
   case Texture::F_luminance_alpha:
   case Texture::F_luminance_alphamask:
     if (tex->get_component_type() == Texture::T_float || tex->get_component_type() == Texture::T_unsigned_short) {
       return GL_LUMINANCE_ALPHA16F_ARB;
     } else {
-      return GL_LUMINANCE_ALPHA;
+      return force_sized ? GL_LUMINANCE8_ALPHA8 : GL_LUMINANCE_ALPHA;
     }
 
 #ifndef OPENGLES_1
@@ -7953,7 +7969,7 @@ get_internal_image_format(Texture *tex) const {
     GLCAT.error()
       << "Invalid image format in get_internal_image_format(): "
       << (int)tex->get_format() << "\n";
-    return GL_RGB;
+    return force_sized ? GL_RGB8 : GL_RGB;
   }
 }
 
@@ -9359,6 +9375,8 @@ update_show_usage_texture_bindings(int show_stage_index) {
       GLuint index = (*ui).second;
       glBindTexture(GL_TEXTURE_2D, index);
     }
+
+    //TODO: glBindSampler(0) ?
   }
 
   report_my_gl_errors();
@@ -10052,7 +10070,11 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
   int height = tex->get_y_size();
   int depth = tex->get_z_size();
 
-  GLint internal_format = get_internal_image_format(tex);
+  // If we'll use immutable texture storage, we have to pick a sized
+  // image format.
+  bool force_sized = (gl_immutable_texture_storage && _supports_tex_storage);
+
+  GLint internal_format = get_internal_image_format(tex, force_sized);
   GLint external_format = get_external_image_format(tex);
   GLenum component_type = get_component_type(tex->get_component_type());
 
@@ -10207,6 +10229,8 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
     CPTA_uchar image = tex->get_ram_mipmap_image(mipmap_bias);
 
     if (image.is_null()) {
+      // We don't even have a RAM image, so we have no choice but to let
+      // mipmaps be generated on the GPU.
       if (uses_mipmaps) {
         if (_supports_generate_mipmap) {
           num_levels = tex->get_expected_num_mipmap_levels() - mipmap_bias;
@@ -10372,7 +10396,9 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
        component_type, false, 0, image_compression);
   }
 
-  if (gtc->_generate_mipmaps && _glGenerateMipmap != NULL) {
+  if (gtc->_generate_mipmaps && _glGenerateMipmap != NULL &&
+      !image.is_null()) {
+    // We uploaded an image; we may need to generate mipmaps.
     if (GLCAT.is_debug()) {
       GLCAT.debug()
         << "generating mipmaps for texture " << tex->get_name() << ", "
@@ -10470,6 +10496,11 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
   int depth = tex->get_expected_mipmap_z_size(mipmap_bias);
 
   // Determine the number of images to upload.
+  int num_levels = 1;
+  if (uses_mipmaps) {
+    num_levels = tex->get_expected_num_mipmap_levels();
+  }
+
   int num_ram_mipmap_levels = 0;
   if (!image.is_null()) {
     if (uses_mipmaps) {
@@ -10494,49 +10525,85 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
 
     if (GLCAT.is_debug()) {
       if (num_ram_mipmap_levels == 0) {
-        GLCAT.debug()
-          << "not loading NULL image for tex " << tex->get_name() << ", " << width << " x " << height
-          << " x " << depth << ", z = " << z << ", uses_mipmaps = " << uses_mipmaps << "\n";
+        if (tex->has_clear_color()) {
+          GLCAT.debug()
+            << "clearing texture " << tex->get_name() << ", "
+            << width << " x " << height << " x " << depth << ", z = " << z
+            << ", uses_mipmaps = " << uses_mipmaps << ", clear_color = "
+            << tex->get_clear_color() << "\n";
+        } else {
+          GLCAT.debug()
+            << "not loading NULL image for texture " << tex->get_name()
+            << ", " << width << " x " << height << " x " << depth
+            << ", z = " << z << ", uses_mipmaps = " << uses_mipmaps << "\n";
+        }
       } else {
         GLCAT.debug()
-          << "updating image data of texture " << tex->get_name() << ", " << width << " x " << height
-          << " x " << depth << ", z = " << z << ", mipmaps " << num_ram_mipmap_levels
+          << "updating image data of texture " << tex->get_name()
+          << ", " << width << " x " << height << " x " << depth
+          << ", z = " << z << ", mipmaps " << num_ram_mipmap_levels
           << ", uses_mipmaps = " << uses_mipmaps << "\n";
       }
     }
 
-    for (int n = mipmap_bias; n < num_ram_mipmap_levels; ++n) {
+    for (int n = mipmap_bias; n < num_levels; ++n) {
       // we grab the mipmap pointer first, if it is NULL we grab the
       // normal mipmap image pointer which is a PTA_uchar
       const unsigned char *image_ptr = (unsigned char*)tex->get_ram_mipmap_pointer(n);
       CPTA_uchar ptimage;
       if (image_ptr == (const unsigned char *)NULL) {
         ptimage = tex->get_ram_mipmap_image(n);
-        if (ptimage == (const unsigned char *)NULL) {
-          GLCAT.warning()
-            << "No mipmap level " << n << " defined for " << tex->get_name()
-            << "\n";
-          // No mipmap level n; stop here.
-          break;
+        if (ptimage.is_null()) {
+          if (n < num_ram_mipmap_levels) {
+            // We were told we'd have this many RAM mipmap images, but
+            // we don't.  Raise a warning.
+            GLCAT.warning()
+              << "No mipmap level " << n << " defined for " << tex->get_name()
+              << "\n";
+            break;
+          }
+
+          if (tex->has_clear_color()) {
+            // The texture has a clear color, so we should fill this mipmap
+            // level to a solid color.
+            if (_supports_clear_texture) {
+              // We can do that with the convenient glClearTexImage function.
+              string clear_data = tex->get_clear_data();
+
+              _glClearTexImage(gtc->_index, n - mipmap_bias, external_format,
+                               component_type, (void *)clear_data.data());
+              continue;
+            } else {
+              // Ask the Texture class to create the mipmap level in RAM.
+              // It'll fill it in with the correct clear color, which we
+              // can then upload.
+              ptimage = tex->make_ram_mipmap_image(n);
+            }
+          } else {
+            // No clear color and no more images.
+            break;
+          }
         }
         image_ptr = ptimage;
       }
 
-      const unsigned char *orig_image_ptr = image_ptr;
+      PTA_uchar bgr_image;
       size_t view_size = tex->get_ram_mipmap_view_size(n);
-      image_ptr += view_size * gtc->get_view();
-      if (one_page_only) {
-        view_size = tex->get_ram_mipmap_page_size(n);
-        image_ptr += view_size * z;
-      }
-      nassertr(image_ptr >= orig_image_ptr && image_ptr + view_size <= orig_image_ptr + tex->get_ram_mipmap_image_size(n), false);
+      if (image_ptr != (const unsigned char *)NULL) {
+        const unsigned char *orig_image_ptr = image_ptr;
+        image_ptr += view_size * gtc->get_view();
+        if (one_page_only) {
+          view_size = tex->get_ram_mipmap_page_size(n);
+          image_ptr += view_size * z;
+        }
+        nassertr(image_ptr >= orig_image_ptr && image_ptr + view_size <= orig_image_ptr + tex->get_ram_mipmap_image_size(n), false);
 
-      PTA_uchar bgr_image;
-      if (!_supports_bgr && image_compression == Texture::CM_off) {
-        // If the GL doesn't claim to support BGR, we may have to reverse
-        // the component ordering of the image.
-        image_ptr = fix_component_ordering(bgr_image, image_ptr, view_size,
-                                           external_format, tex);
+        if (!_supports_bgr && image_compression == Texture::CM_off) {
+          // If the GL doesn't claim to support BGR, we may have to reverse
+          // the component ordering of the image.
+          image_ptr = fix_component_ordering(bgr_image, image_ptr, view_size,
+                                             external_format, tex);
+        }
       }
 
       int width = tex->get_expected_mipmap_x_size(n);
@@ -10651,63 +10718,56 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
         component_type = GL_UNSIGNED_INT_24_8_EXT;
 #endif
       }
-
-      // We don't have any RAM mipmap levels, so we create an uninitialized OpenGL
-      // texture.  Presumably this will be used later for render-to-texture or so.
-      switch (page_target) {
-#ifndef OPENGLES
-        case GL_TEXTURE_1D:
-          glTexImage1D(page_target, 0, internal_format, width, 0, external_format, component_type, NULL);
-          break;
-        case GL_TEXTURE_2D_ARRAY:
-#endif
-#ifndef OPENGLES_1
-        case GL_TEXTURE_3D:
-          _glTexImage3D(page_target, 0, internal_format, width, height, depth, 0, external_format, component_type, NULL);
-          break;
-#endif
-        default:
-          glTexImage2D(page_target, 0, internal_format, width, height, 0, external_format, component_type, NULL);
-          break;
-      }
     }
 
-    for (int n = mipmap_bias; n < num_ram_mipmap_levels; ++n) {
+    for (int n = mipmap_bias; n < num_levels; ++n) {
       const unsigned char *image_ptr = (unsigned char*)tex->get_ram_mipmap_pointer(n);
       CPTA_uchar ptimage;
       if (image_ptr == (const unsigned char *)NULL) {
         ptimage = tex->get_ram_mipmap_image(n);
-        if (ptimage == (const unsigned char *)NULL) {
-          GLCAT.warning()
-            << "No mipmap level " << n << " defined for " << tex->get_name()
-            << "\n";
-          // No mipmap level n; stop here.
-#ifndef OPENGLES
-          if (is_at_least_gl_version(1, 2)) {
-            // Tell the GL we have no more mipmaps for it to use.
-            glTexParameteri(texture_target, GL_TEXTURE_MAX_LEVEL, n - mipmap_bias);
+        if (ptimage.is_null()) {
+          if (n < num_ram_mipmap_levels) {
+            // We were told we'd have this many RAM mipmap images, but
+            // we don't.  Raise a warning.
+            GLCAT.warning()
+              << "No mipmap level " << n << " defined for " << tex->get_name()
+              << "\n";
+  #ifndef OPENGLES
+            if (is_at_least_gl_version(1, 2)) {
+              // Tell the GL we have no more mipmaps for it to use.
+              glTexParameteri(texture_target, GL_TEXTURE_MAX_LEVEL, n - mipmap_bias);
+            }
+  #endif
+            break;
+          }
+
+          if (tex->has_clear_color()) {
+            // Ask the Texture class to create the mipmap level in RAM.
+            // It'll fill it in with the correct clear color, which we
+            // can then upload.
+            ptimage = tex->make_ram_mipmap_image(n);
           }
-#endif
-          break;
         }
         image_ptr = ptimage;
       }
 
-      const unsigned char *orig_image_ptr = image_ptr;
+      PTA_uchar bgr_image;
       size_t view_size = tex->get_ram_mipmap_view_size(n);
-      image_ptr += view_size * gtc->get_view();
-      if (one_page_only) {
-        view_size = tex->get_ram_mipmap_page_size(n);
-        image_ptr += view_size * z;
-      }
-      nassertr(image_ptr >= orig_image_ptr && image_ptr + view_size <= orig_image_ptr + tex->get_ram_mipmap_image_size(n), false);
+      if (image_ptr != (const unsigned char *)NULL) {
+        const unsigned char *orig_image_ptr = image_ptr;
+        image_ptr += view_size * gtc->get_view();
+        if (one_page_only) {
+          view_size = tex->get_ram_mipmap_page_size(n);
+          image_ptr += view_size * z;
+        }
+        nassertr(image_ptr >= orig_image_ptr && image_ptr + view_size <= orig_image_ptr + tex->get_ram_mipmap_image_size(n), false);
 
-      PTA_uchar bgr_image;
-      if (!_supports_bgr && image_compression == Texture::CM_off) {
-        // If the GL doesn't claim to support BGR, we may have to reverse
-        // the component ordering of the image.
-        image_ptr = fix_component_ordering(bgr_image, image_ptr, view_size,
-                                           external_format, tex);
+        if (!_supports_bgr && image_compression == Texture::CM_off) {
+          // If the GL doesn't claim to support BGR, we may have to reverse
+          // the component ordering of the image.
+          image_ptr = fix_component_ordering(bgr_image, image_ptr, view_size,
+                                             external_format, tex);
+        }
       }
 
       int width = tex->get_expected_mipmap_x_size(n);

+ 8 - 1
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -194,6 +194,8 @@ typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pna
 typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIEXTPROC) (GLuint program, GLenum pname, GLint value);
 typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount);
 typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount);
+typedef void (APIENTRYP PFNGLCLEARTEXIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, const void *data);
+typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data);
 typedef void (APIENTRYP PFNGLBINDTEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures);
 typedef void (APIENTRYP PFNGLBINDSAMPLERSPROC) (GLuint first, GLsizei count, const GLuint *samplers);
 typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREPROC) (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format);
@@ -461,7 +463,7 @@ protected:
   static SamplerState::FilterType get_panda_filter_type(GLenum ft);
   GLenum get_component_type(Texture::ComponentType component_type);
   GLint get_external_image_format(Texture *tex) const;
-  GLint get_internal_image_format(Texture *tex) const;
+  GLint get_internal_image_format(Texture *tex, bool force_sized=false) const;
   static bool is_mipmap_filter(GLenum min_filter);
   static bool is_compressed_format(GLenum format);
   static GLint get_texture_apply_mode_type(TextureStage::Mode am);
@@ -644,6 +646,11 @@ public:
   PFNGLTEXSTORAGE2DPROC _glTexStorage2D;
   PFNGLTEXSTORAGE3DPROC _glTexStorage3D;
 
+  bool _supports_clear_texture;
+#ifndef OPENGLES
+  PFNGLCLEARTEXIMAGEPROC _glClearTexImage;
+#endif
+
   PFNGLCOMPRESSEDTEXIMAGE1DPROC _glCompressedTexImage1D;
   PFNGLCOMPRESSEDTEXIMAGE2DPROC _glCompressedTexImage2D;
   PFNGLCOMPRESSEDTEXIMAGE3DPROC _glCompressedTexImage3D;

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

@@ -1228,17 +1228,14 @@ update_shader_texture_bindings(ShaderContext *prev) {
       } else {
         //TODO: automatically convert to sized type instead of plain GL_RGBA
         // If a base type is used, it will crash.
-        if (gtc->_internal_format == GL_RGBA || gtc->_internal_format == GL_RGB) {
+        GLenum internal_format = gtc->_internal_format;
+        if (internal_format == GL_RGBA || internal_format == GL_RGB) {
           GLCAT.error()
             << "Texture " << tex->get_name() << " has an unsized format.  Textures bound "
             << "to a shader as an image need a sized format.\n";
 
           // This may not actually be right, but may still prevent a crash.
-          if (gtc->_internal_format == GL_RGBA) {
-            gtc->_internal_format = GL_RGBA8;
-          } else {
-            gtc->_internal_format = GL_RGB8;
-          }
+          internal_format = _glgsg->get_internal_image_format(tex, true);
         }
 
         GLenum access = GL_READ_ONLY;

+ 104 - 10
panda/src/gobj/texture.I

@@ -55,7 +55,8 @@ clear() {
 //  Description: Sets the texture to the indicated type and
 //               dimensions, presumably in preparation for calling
 //               read() or load(), or set_ram_image() or
-//               modify_ram_image().
+//               modify_ram_image(), or use set_clear_color to let
+//               the texture be cleared to a solid color.
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
@@ -71,7 +72,9 @@ setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
 //       Access: Published
 //  Description: Sets the texture as an empty 1-d texture with no
 //               dimensions.  Follow up with read() or load() to fill
-//               the texture properties and image data.
+//               the texture properties and image data, or use
+//               set_clear_color to let the texture be cleared to a
+//               solid color.
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 setup_1d_texture() {
@@ -84,7 +87,8 @@ setup_1d_texture() {
 //  Description: Sets the texture as an empty 1-d texture with the
 //               specified dimensions and properties.  Follow up with
 //               set_ram_image() or modify_ram_image() to fill the
-//               image data.
+//               image data, or use set_clear_color to let the
+//               texture be cleared to a solid color.
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 setup_1d_texture(int x_size, ComponentType component_type, Format format) {
@@ -96,7 +100,9 @@ setup_1d_texture(int x_size, ComponentType component_type, Format format) {
 //       Access: Published
 //  Description: Sets the texture as an empty 2-d texture with no
 //               dimensions.  Follow up with read() or load() to fill
-//               the texture properties and image data.
+//               the texture properties and image data, or use
+//               set_clear_color to let the texture be cleared to a
+//               solid color.
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 setup_2d_texture() {
@@ -109,7 +115,8 @@ setup_2d_texture() {
 //  Description: Sets the texture as an empty 2-d texture with the
 //               specified dimensions and properties.  Follow up with
 //               set_ram_image() or modify_ram_image() to fill the
-//               image data.
+//               image data, or use set_clear_color to let the
+//               texture be cleared to a solid color.
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 setup_2d_texture(int x_size, int y_size, ComponentType component_type,
@@ -124,7 +131,8 @@ setup_2d_texture(int x_size, int y_size, ComponentType component_type,
 //               dimensions (though if you know the depth ahead
 //               of time, it saves a bit of reallocation later).
 //               Follow up with read() or load() to fill the texture
-//               properties and image data.
+//               properties and image data, or use set_clear_color
+//               to let the texture be cleared to a solid color.
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 setup_3d_texture(int z_size) {
@@ -152,7 +160,8 @@ setup_3d_texture(int x_size, int y_size, int z_size,
 //               no dimensions (though if you know the depth ahead
 //               of time, it saves a bit of reallocation later).
 //               Follow up with read() or load() to fill the texture
-//               properties and image data.
+//               properties and image data, or use set_clear_color
+//               to let the texture be cleared to a solid color.
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 setup_2d_texture_array(int z_size) {
@@ -165,7 +174,8 @@ setup_2d_texture_array(int z_size) {
 //  Description: Sets the texture as an empty 2-d texture array with the
 //               specified dimensions and properties. Follow up with
 //               set_ram_image() or modify_ram_image() to fill the
-//               image data.
+//               image data, or use set_clear_color to let the
+//               texture be cleared to a solid color.
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 setup_2d_texture_array(int x_size, int y_size, int z_size,
@@ -178,7 +188,9 @@ setup_2d_texture_array(int x_size, int y_size, int z_size,
 //       Access: Published
 //  Description: Sets the texture as an empty cube map texture with no
 //               dimensions.  Follow up with read() or load() to fill
-//               the texture properties and image data.
+//               the texture properties and image data, or use
+//               set_clear_color to let the texture be cleared to a
+//               solid color.
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 setup_cube_map() {
@@ -191,7 +203,8 @@ setup_cube_map() {
 //  Description: Sets the texture as an empty cube map texture with
 //               the specified dimensions and properties.  Follow up
 //               with set_ram_image() or modify_ram_image() to fill
-//               the image data.
+//               the image data, or use set_clear_color to let the
+//               texture be cleared to a solid color.
 //
 //               Note that a cube map should always consist of six
 //               square images, so x_size and y_size will be the same,
@@ -202,6 +215,87 @@ setup_cube_map(int size, ComponentType component_type, Format format) {
   setup_texture(TT_cube_map, size, size, 6, component_type, format);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::clear_image
+//       Access: Published
+//  Description: Clears the texture data without changing its format
+//               or resolution.  The texture is cleared on both the
+//               graphics hardware and from RAM, unlike clear_ram_image,
+//               which only removes the data from RAM.
+//
+//               If a clear color has been specified using
+//               set_clear_color, the texture will be cleared using
+//               a solid color.
+//
+//               The texture data will be cleared the first time in
+//               which the texture is used after this method is called.
+////////////////////////////////////////////////////////////////////
+INLINE void Texture::
+clear_image() {
+  CDWriter cdata(_cycler, true);
+  do_clear_ram_image(cdata);
+  do_clear_simple_ram_image(cdata);
+  cdata->inc_image_modified();
+  cdata->inc_simple_image_modified();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::has_clear_color
+//       Access: Published
+//  Description: Returns true if a color was previously set using
+//               set_clear_color.
+////////////////////////////////////////////////////////////////////
+INLINE bool Texture::
+has_clear_color() const {
+  CDReader cdata(_cycler);
+  return cdata->_has_clear_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_clear_color
+//       Access: Published
+//  Description: Returns the color that was previously set using
+//               set_clear_color.
+////////////////////////////////////////////////////////////////////
+INLINE LColor Texture::
+get_clear_color() const {
+  CDReader cdata(_cycler);
+  return cdata->_clear_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::set_clear_color
+//       Access: Published
+//  Description: Sets the color that will be used to fill the
+//               texture image in absence of any image data.  It is
+//               used when any of the setup_texture functions or
+//               clear_image is called and image data is not
+//               provided using read() or modify_ram_image().
+//
+//               This does not affect a texture that has already
+//               been cleared; call clear_image to clear it again.
+////////////////////////////////////////////////////////////////////
+INLINE void Texture::
+set_clear_color(const LColor &color) {
+  CDWriter cdata(_cycler, true);
+  cdata->_clear_color = color;
+  cdata->_has_clear_color = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_clear_data
+//       Access: Published
+//  Description: Returns the raw image data for a single pixel if
+//               it were set to the clear color.
+////////////////////////////////////////////////////////////////////
+INLINE string Texture::
+get_clear_data() const {
+  CDReader cdata(_cycler);
+  unsigned char data[16];
+  int size = do_get_clear_data(cdata, data);
+  return string((char *)data, size);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::write
 //       Access: Published

+ 142 - 2
panda/src/gobj/texture.cxx

@@ -4258,12 +4258,26 @@ do_modify_ram_image(CData *cdata) {
 ////////////////////////////////////////////////////////////////////
 PTA_uchar Texture::
 do_make_ram_image(CData *cdata) {
+  int image_size = do_get_expected_ram_image_size(cdata);
   cdata->_ram_images.clear();
   cdata->_ram_images.push_back(RamImage());
   cdata->_ram_images[0]._page_size = do_get_expected_ram_page_size(cdata);
-  cdata->_ram_images[0]._image = PTA_uchar::empty_array(do_get_expected_ram_image_size(cdata), get_class_type());
+  cdata->_ram_images[0]._image = PTA_uchar::empty_array(image_size, get_class_type());
   cdata->_ram_images[0]._pointer_image = NULL;
   cdata->_ram_image_compression = CM_off;
+
+  if (cdata->_has_clear_color) {
+    // Fill the image with the clear color.
+    unsigned char pixel[16];
+    const int pixel_size = do_get_clear_data(cdata, pixel);
+    nassertr(pixel_size > 0, cdata->_ram_images[0]._image);
+
+    unsigned char *image_data = cdata->_ram_images[0]._image;
+    for (int i = 0; i < image_size; i += pixel_size) {
+      memcpy(image_data + i, pixel, pixel_size);
+    }
+  }
+
   return cdata->_ram_images[0]._image;
 }
 
@@ -4331,9 +4345,23 @@ do_make_ram_mipmap_image(CData *cdata, int n) {
     cdata->_ram_images.push_back(RamImage());
   }
 
-  cdata->_ram_images[n]._image = PTA_uchar::empty_array(do_get_expected_ram_mipmap_image_size(cdata, n), get_class_type());
+  size_t image_size = do_get_expected_ram_mipmap_image_size(cdata, n);
+  cdata->_ram_images[n]._image = PTA_uchar::empty_array(image_size, get_class_type());
   cdata->_ram_images[n]._pointer_image = NULL;
   cdata->_ram_images[n]._page_size = do_get_expected_ram_mipmap_page_size(cdata, n);
+
+  if (cdata->_has_clear_color) {
+    // Fill the image with the clear color.
+    unsigned char pixel[16];
+    const int pixel_size = do_get_clear_data(cdata, pixel);
+    nassertr(pixel_size > 0, cdata->_ram_images[n]._image);
+
+    unsigned char *image_data = cdata->_ram_images[n]._image;
+    for (int i = 0; i < image_size; i += pixel_size) {
+      memcpy(image_data + i, pixel, pixel_size);
+    }
+  }
+
   return cdata->_ram_images[n]._image;
 }
 
@@ -4362,6 +4390,116 @@ do_set_ram_mipmap_image(CData *cdata, int n, CPTA_uchar image, size_t page_size)
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::do_get_clear_color
+//       Access: Published
+//  Description: Returns a string with a single pixel representing
+//               the clear color of the texture in the format of
+//               this texture.
+//
+//               In other words, to create an uncompressed RAM
+//               texture filled with the clear color, it should
+//               be initialized with this string repeated for
+//               every pixel.
+////////////////////////////////////////////////////////////////////
+int Texture::
+do_get_clear_data(const CData *cdata, unsigned char *into) const {
+  nassertr(cdata->_has_clear_color, 0);
+  nassertr(cdata->_num_components <= 4, 0);
+
+  //TODO: encode the color into the sRGB color space if used
+  switch (cdata->_component_type) {
+  case T_unsigned_byte:
+    {
+      LColorf scaled = cdata->_clear_color.fmin(LColorf(1)).fmax(LColorf::zero());
+      scaled *= 255;
+      switch (cdata->_num_components) {
+      case 2:
+        into[1] = (unsigned char)scaled[1];
+      case 1:
+        into[0] = (unsigned char)scaled[0];
+        break;
+      case 4:
+        into[3] = (unsigned char)scaled[3];
+      case 3: // BGR <-> RGB
+        into[0] = (unsigned char)scaled[2];
+        into[1] = (unsigned char)scaled[1];
+        into[2] = (unsigned char)scaled[0];
+        break;
+      }
+      break;
+    }
+
+  case T_unsigned_short:
+    {
+      LColorf scaled = cdata->_clear_color.fmin(LColorf(1)).fmax(LColorf::zero());
+      scaled *= 65535;
+      switch (cdata->_num_components) {
+      case 2:
+        ((unsigned short *)into)[1] = (unsigned short)scaled[1];
+      case 1:
+        ((unsigned short *)into)[0] = (unsigned short)scaled[0];
+        break;
+      case 4:
+        ((unsigned short *)into)[3] = (unsigned short)scaled[3];
+      case 3: // BGR <-> RGB
+        ((unsigned short *)into)[0] = (unsigned short)scaled[2];
+        ((unsigned short *)into)[1] = (unsigned short)scaled[1];
+        ((unsigned short *)into)[2] = (unsigned short)scaled[0];
+        break;
+      }
+      break;
+    }
+
+  case T_float:
+    switch (cdata->_num_components) {
+    case 2:
+      ((float *)into)[1] = cdata->_clear_color[1];
+    case 1:
+      ((float *)into)[0] = cdata->_clear_color[0];
+      break;
+    case 4:
+      ((float *)into)[3] = cdata->_clear_color[3];
+    case 3: // BGR <-> RGB
+      ((float *)into)[0] = cdata->_clear_color[2];
+      ((float *)into)[1] = cdata->_clear_color[1];
+      ((float *)into)[2] = cdata->_clear_color[0];
+      break;
+    }
+    break;
+
+  case T_unsigned_int_24_8:
+    nassertr(cdata->_num_components == 1, 0);
+    *((unsigned int *)into) =
+      ((unsigned int)(cdata->_clear_color[0] * 16777215) << 8) +
+       (unsigned int)max(min(cdata->_clear_color[1], 255), 0);
+    break;
+
+  case T_int:
+    {
+      // Note: there are no 32-bit UNORM textures.  Therefore, we don't
+      // do any normalization here, either.
+      switch (cdata->_num_components) {
+      case 2:
+        ((int *)into)[1] = (int)cdata->_clear_color[1];
+      case 1:
+        ((int *)into)[0] = (int)cdata->_clear_color[0];
+        break;
+      case 4:
+        ((int *)into)[3] = (int)cdata->_clear_color[3];
+      case 3: // BGR <-> RGB
+        ((int *)into)[0] = (int)cdata->_clear_color[2];
+        ((int *)into)[1] = (int)cdata->_clear_color[1];
+        ((int *)into)[2] = (int)cdata->_clear_color[0];
+        break;
+      }
+      break;
+    }
+  }
+
+  return cdata->_num_components * cdata->_component_width;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::consider_auto_process_ram_image
 //       Access: Protected
@@ -8151,6 +8289,8 @@ CData() {
   _simple_x_size = 0;
   _simple_y_size = 0;
   _simple_ram_image._page_size = 0;
+
+  _has_clear_color = false;
 }
 
 ////////////////////////////////////////////////////////////////////

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

@@ -234,6 +234,12 @@ PUBLISHED:
   void generate_normalization_cube_map(int size);
   void generate_alpha_scale_map();
 
+  INLINE void clear_image();
+  INLINE bool has_clear_color() const;
+  INLINE LColor get_clear_color() const;
+  INLINE void set_clear_color(const LColor &color);
+  INLINE string get_clear_data() const;
+
   BLOCKING bool read(const Filename &fullpath, const LoaderOptions &options = LoaderOptions());
   BLOCKING bool read(const Filename &fullpath, const Filename &alpha_fullpath,
                      int primary_file_num_channels, int alpha_file_channel,
@@ -569,6 +575,7 @@ protected:
   PTA_uchar do_modify_ram_mipmap_image(CData *cdata, int n);
   PTA_uchar do_make_ram_mipmap_image(CData *cdata, int n);
   void do_set_ram_mipmap_image(CData *cdata, int n, CPTA_uchar image, size_t page_size);
+  int do_get_clear_data(const CData *cdata, unsigned char *into) const;
 
   bool consider_auto_process_ram_image(bool generate_mipmaps, bool allow_compression);
   bool do_consider_auto_process_ram_image(CData *cdata, bool generate_mipmaps,
@@ -844,6 +851,10 @@ protected:
     int _simple_y_size;
     PN_int32 _simple_image_date_generated;
 
+    // This is the color that should be used when no image was given.
+    bool _has_clear_color;
+    LColor _clear_color;
+
     UpdateSeq _properties_modified;
     UpdateSeq _image_modified;
     UpdateSeq _simple_image_modified;