diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 6b3cf55d13..6a3b49cc53 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/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; - 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); - 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); + size_t view_size = tex->get_ram_mipmap_view_size(n); + 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); + + 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; - 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); - 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); + size_t view_size = tex->get_ram_mipmap_view_size(n); + 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); + + 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); diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.h b/panda/src/glstuff/glGraphicsStateGuardian_src.h index bcfe2fd383..f89a84600b 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.h +++ b/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; diff --git a/panda/src/glstuff/glShaderContext_src.cxx b/panda/src/glstuff/glShaderContext_src.cxx index e16fb3143b..4aca23a373 100755 --- a/panda/src/glstuff/glShaderContext_src.cxx +++ b/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; diff --git a/panda/src/gobj/texture.I b/panda/src/gobj/texture.I index f075107d34..e7db1364ce 100644 --- a/panda/src/gobj/texture.I +++ b/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 diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx index 0ab909f66c..129ef2f23c 100644 --- a/panda/src/gobj/texture.cxx +++ b/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; } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h index ad93f8641a..22287f6330 100644 --- a/panda/src/gobj/texture.h +++ b/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;