From 7719dd539eb43d65964bf4d1af772b7d9c460e6b Mon Sep 17 00:00:00 2001 From: David Rose Date: Thu, 15 Jun 2006 00:22:35 +0000 Subject: [PATCH] fix texture issues; introduce model-cache-dir --- .../glstuff/glGraphicsStateGuardian_src.cxx | 367 ++++++++-------- .../src/glstuff/glGraphicsStateGuardian_src.h | 9 +- panda/src/gobj/texture.I | 17 +- panda/src/gobj/texture.cxx | 118 ++++-- panda/src/gobj/texture.h | 15 +- panda/src/gobj/texturePool.cxx | 194 +++++++-- panda/src/pgraph/bamFile.cxx | 11 + panda/src/pgraph/loader.cxx | 26 +- panda/src/putil/Sources.pp | 12 +- panda/src/putil/bamCache.I | 71 ++++ panda/src/putil/bamCache.cxx | 390 ++++++++++++++++++ panda/src/putil/bamCache.h | 67 +++ panda/src/putil/bamCacheRecord.I | 154 +++++++ panda/src/putil/bamCacheRecord.cxx | 280 +++++++++++++ panda/src/putil/bamCacheRecord.h | 126 ++++++ panda/src/putil/config_util.cxx | 4 + panda/src/putil/putil_composite1.cxx | 2 + 17 files changed, 1595 insertions(+), 268 deletions(-) create mode 100644 panda/src/putil/bamCache.I create mode 100644 panda/src/putil/bamCache.cxx create mode 100644 panda/src/putil/bamCache.h create mode 100644 panda/src/putil/bamCacheRecord.I create mode 100644 panda/src/putil/bamCacheRecord.cxx create mode 100644 panda/src/putil/bamCacheRecord.h diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 7a9405b28e..622e13e9d5 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -164,48 +164,26 @@ ushort_bgra_to_rgba(unsigned short *dest, const unsigned short *source, } } -//////////////////////////////////////////////////////////////////// -// Function: reduce_image -// Description: Reduces an image to an acceptable size by sampling -// pixels. This is just a cheap and dirty trick to load -// an image that the GL says is too large. -// -// The implementation copies byte_chunk bytes every -// byte_chunk * stride bytes. -//////////////////////////////////////////////////////////////////// -static PTA_uchar -reduce_image(CPTA_uchar orig_image, int byte_chunk, int stride) { - size_t orig_image_size = orig_image.size(); - size_t new_image_size = orig_image_size / stride; - PTA_uchar new_image = PTA_uchar::empty_array(new_image_size); - const unsigned char *from = orig_image.p(); - unsigned char *to = new_image.p(); - - const unsigned char *from_end = from + orig_image_size; - const unsigned char *to_end = to + new_image_size; - - while (from + byte_chunk <= from_end) { - nassertr(to + byte_chunk <= to_end, new_image); - memcpy(to, from, byte_chunk); - from += stride * byte_chunk; - to += byte_chunk; - } - - return new_image; -} - //////////////////////////////////////////////////////////////////// // Function: fix_component_ordering // Description: Reverses the order of the components within the // image, to convert (for instance) GL_BGR to GL_RGB. -// Returns the CPTA_uchar representing the converted +// Returns the byte pointer representing the converted // image, or the original image if it is unchanged. +// +// new_image must be supplied; it is the PTA_uchar that +// will be used to hold the converted image if required. +// It will be modified only if the conversion is +// necessary, in which case the data will be stored +// there, and this pointer will be returned. If the +// conversion is not necessary, this pointer will be +// left unchanged. //////////////////////////////////////////////////////////////////// -static PTA_uchar -fix_component_ordering(CPTA_uchar orig_image, GLenum external_format, - Texture *tex) { - size_t orig_image_size = orig_image.size(); - PTA_uchar new_image = (PTA_uchar &)orig_image; +static const unsigned char * +fix_component_ordering(PTA_uchar &new_image, + const unsigned char *orig_image, size_t orig_image_size, + GLenum external_format, Texture *tex) { + const unsigned char *result = orig_image; switch (external_format) { case GL_RGB: @@ -213,13 +191,15 @@ fix_component_ordering(CPTA_uchar orig_image, GLenum external_format, case Texture::T_unsigned_byte: new_image = PTA_uchar::empty_array(orig_image_size); uchar_bgr_to_rgb(new_image, orig_image, orig_image_size / 3); + result = new_image; break; case Texture::T_unsigned_short: new_image = PTA_uchar::empty_array(orig_image_size); ushort_bgr_to_rgb((unsigned short *)new_image.p(), - (const unsigned short *)orig_image.p(), + (const unsigned short *)orig_image, orig_image_size / 6); + result = new_image; break; default: @@ -232,13 +212,15 @@ fix_component_ordering(CPTA_uchar orig_image, GLenum external_format, case Texture::T_unsigned_byte: new_image = PTA_uchar::empty_array(orig_image_size); uchar_bgra_to_rgba(new_image, orig_image, orig_image_size / 4); + result = new_image; break; case Texture::T_unsigned_short: new_image = PTA_uchar::empty_array(orig_image_size); ushort_bgra_to_rgba((unsigned short *)new_image.p(), - (const unsigned short *)orig_image.p(), + (const unsigned short *)orig_image, orig_image_size / 8); + result = new_image; break; default: @@ -250,7 +232,7 @@ fix_component_ordering(CPTA_uchar orig_image, GLenum external_format, break; } - return new_image; + return result; } //////////////////////////////////////////////////////////////////// @@ -561,6 +543,7 @@ reset() { _supports_bgr = has_extension("GL_EXT_bgra") || is_at_least_version(1, 2); + _supports_rescale_normal = CLP(support_rescale_normal) && (has_extension("GL_EXT_rescale_normal") || is_at_least_version(1, 2)); @@ -3189,23 +3172,28 @@ framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr, << ")" << endl; } - unsigned char *image = tex->modify_ram_image(); + size_t image_size = tex->get_ram_image_size(); + unsigned char *image_ptr = tex->modify_ram_image(); if (z >= 0) { nassertr(z < tex->get_z_size(), false); - image += z * tex->get_expected_ram_page_size(); + image_size = tex->get_expected_ram_page_size(); + image_ptr += z * image_size; } GLP(ReadPixels)(xo, yo, w, h, external_format, get_component_type(component_type), - image); + image_ptr); // We may have to reverse the byte ordering of the image if GL - // didn't do it for us. This assumes we render out the six faces of - // a cube map in ascending order, since we can't do this until we - // have rendered the last face. - if (color_mode && !_supports_bgr && (z == -1 || z == 5)) { - tex->set_ram_image(fix_component_ordering(tex->get_ram_image(), - external_format, tex)); + // didn't do it for us. + if (color_mode && !_supports_bgr) { + PTA_uchar new_image; + const unsigned char *result = + fix_component_ordering(new_image, image_ptr, image_size, + external_format, tex); + if (result != image_ptr) { + memcpy(image_ptr, result, image_size); + } } report_my_gl_errors(); @@ -6359,6 +6347,7 @@ specify_texture(Texture *tex) { report_my_gl_errors(); } +/* #ifndef NDEBUG //////////////////////////////////////////////////////////////////// // Function: compute_gl_image_size @@ -6421,6 +6410,7 @@ compute_gl_image_size(int x_size, int y_size, int z_size, return x_size * y_size * z_size * pixel_width; } #endif // NDEBUG +*/ //////////////////////////////////////////////////////////////////// // Function: GLGraphicsStateGuardian::apply_texture @@ -6470,6 +6460,8 @@ upload_texture(CLP(TextureContext) *gtc) { image_compression = tex->get_ram_image_compression(); } + int mipmap_bias = 0; + int width = tex->get_x_size(); int height = tex->get_y_size(); int depth = tex->get_z_size(); @@ -6499,74 +6491,41 @@ upload_texture(CLP(TextureContext) *gtc) { return false; } - if (!image.is_null()) { - - // If it doesn't fit, we have to reduce it on-the-fly. This is kind - // of expensive and it doesn't look great; it would have been better - // if the user had specified max-texture-dimension to reduce the - // texture at load time instead. Of course, the user doesn't always - // know ahead of time what the hardware limits are. - if (max_dimension > 0 && image_compression == Texture::CM_off) { - if (width > max_dimension) { - int texel_size = tex->get_num_components() * tex->get_component_width(); - int byte_chunk = texel_size; - int stride = 1; - int new_width = width; - while (new_width > max_dimension) { - stride <<= 1; - new_width >>= 1; + // If it doesn't fit, we have to reduce it on-the-fly. We do this + // by incrementing the mipmap_bias, so we're effectively loading a + // lower mipmap level. This requires generating the mipmaps on + // the CPU if they haven't already been generated. It would have + // been better if the user had specified max-texture-dimension to + // reduce the texture at load time instead; of course, the user + // doesn't always know ahead of time what the hardware limits are. + + if (max_dimension > 0 && image_compression == Texture::CM_off) { + while (tex->get_expected_mipmap_x_size(mipmap_bias) > max_dimension || + tex->get_expected_mipmap_y_size(mipmap_bias) > max_dimension || + tex->get_expected_mipmap_z_size(mipmap_bias) > max_dimension) { + ++mipmap_bias; + } + + if (mipmap_bias >= tex->get_num_ram_mipmap_images()) { + // We need to generate some more mipmap images. + if (tex->has_ram_image()) { + tex->generate_ram_mipmap_images(); + if (mipmap_bias >= tex->get_num_ram_mipmap_images()) { + // It didn't work. Send the smallest we've got, and hope + // for the best. + mipmap_bias = tex->get_num_ram_mipmap_images() - 1; } - GLCAT.info() - << "Reducing width of " << tex->get_name() - << " from " << width << " to " << new_width << "\n"; - image = reduce_image(image, byte_chunk, stride); - width = new_width; - } - if (height > max_dimension) { - int texel_size = tex->get_num_components() * tex->get_component_width(); - int byte_chunk = width * texel_size; - int stride = 1; - int new_height = height; - while (new_height > max_dimension) { - stride <<= 1; - new_height >>= 1; - } - GLCAT.info() - << "Reducing height of " << tex->get_name() - << " from " << height << " to " << new_height << "\n"; - image = reduce_image(image, byte_chunk, stride); - height = new_height; - } - if (depth > max_dimension) { - int texel_size = tex->get_num_components() * tex->get_component_width(); - int byte_chunk = height * width * texel_size; - int stride = 1; - int new_depth = depth; - while (new_depth > max_dimension) { - stride <<= 1; - new_depth >>= 1; - } - GLCAT.info() - << "Reducing depth of " << tex->get_name() - << " from " << depth << " to " << new_depth << "\n"; - image = reduce_image(image, byte_chunk, stride); - depth = new_depth; } } - - if (!_supports_bgr) { - // If the GL doesn't claim to support BGR, we may have to reverse - // the component ordering of the image. - image = fix_component_ordering(image, external_format, tex); + + if (mipmap_bias != 0) { + GLCAT.info() + << "Reducing image " << tex->get_name() + << " from " << width << " x " << height << " x " << depth << " to " + << tex->get_expected_mipmap_x_size(mipmap_bias) << " x " + << tex->get_expected_mipmap_y_size(mipmap_bias) << " x " + << tex->get_expected_mipmap_z_size(mipmap_bias) << "\n"; } - -#ifndef NDEBUG - if (image_compression == Texture::CM_off) { - int wanted_size = - compute_gl_image_size(width, height, depth, external_format, component_type); - nassertr(wanted_size == (int)image.size(), false); - } -#endif // NDEBUG } GLP(PixelStorei)(GL_UNPACK_ALIGNMENT, 1); @@ -6590,40 +6549,47 @@ upload_texture(CLP(TextureContext) *gtc) { } success = success && upload_texture_image - (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_POSITIVE_X, - internal_format, width, height, depth, external_format, component_type, + (gtc, uses_mipmaps, mipmap_bias, + GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_POSITIVE_X, + internal_format, external_format, component_type, true, 0, image_compression); success = success && upload_texture_image - (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, - internal_format, width, height, depth, external_format, component_type, + (gtc, uses_mipmaps, mipmap_bias, + GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + internal_format, external_format, component_type, true, 1, image_compression); success = success && upload_texture_image - (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, - internal_format, width, height, depth, external_format, component_type, + (gtc, uses_mipmaps, mipmap_bias, + GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, + internal_format, external_format, component_type, true, 2, image_compression); success = success && upload_texture_image - (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, - internal_format, width, height, depth, external_format, component_type, + (gtc, uses_mipmaps, mipmap_bias, + GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + internal_format, external_format, component_type, true, 3, image_compression); success = success && upload_texture_image - (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, - internal_format, width, height, depth, external_format, component_type, + (gtc, uses_mipmaps, mipmap_bias, + GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, + internal_format, external_format, component_type, true, 4, image_compression); success = success && upload_texture_image - (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, - internal_format, width, height, depth, external_format, component_type, + (gtc, uses_mipmaps, mipmap_bias, + GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, + internal_format, external_format, component_type, true, 5, image_compression); } else { // Any other kind of texture can be loaded all at once. + GLenum target = get_texture_target(tex->get_texture_type()); success = upload_texture_image - (gtc, uses_mipmaps, get_texture_target(tex->get_texture_type()), - internal_format, width, height, depth, external_format, component_type, + (gtc, uses_mipmaps, mipmap_bias, target, target, + internal_format, external_format, component_type, false, 0, image_compression); } @@ -6657,19 +6623,29 @@ upload_texture(CLP(TextureContext) *gtc) { // Access: Protected // Description: Loads a texture image, or one page of a cube map // image, from system RAM to texture memory. +// +// texture_target is normally the same thing as +// page_target; both represent the GL target onto which +// the texture image is loaded, e.g. GL_TEXTURE_1D, +// GL_TEXTURE_2D, etc. The only time they may differ is +// in the case of cube mapping, in which case +// texture_target will be target for the overall +// texture, e.g. GL_TEXTURE_CUBE_MAP, and page_target +// will the target for this particular page, +// e.g. GL_TEXTURE_CUBE_MAP_POSITIVE_X. //////////////////////////////////////////////////////////////////// bool CLP(GraphicsStateGuardian):: upload_texture_image(CLP(TextureContext) *gtc, - bool uses_mipmaps, - GLenum target, GLint internal_format, - int width, int height, int depth, + bool uses_mipmaps, int mipmap_bias, + GLenum texture_target, GLenum page_target, + GLint internal_format, GLint external_format, GLenum component_type, bool one_page_only, int z, - Texture::CompressionMode image_compression) { + Texture::CompressionMode image_compression) { // Make sure the error stack is cleared out before we begin. report_my_gl_errors(); - if (target == GL_NONE) { + if (texture_target == GL_NONE) { // Unsupported target (e.g. 3-d texturing on GL 1.1). return false; } @@ -6679,15 +6655,20 @@ upload_texture_image(CLP(TextureContext) *gtc, PStatTimer timer(_load_texture_pcollector); Texture *tex = gtc->get_texture(); - CPTA_uchar image = tex->get_ram_image(); + nassertr(tex != (Texture *)NULL, false); + + CPTA_uchar image = tex->get_ram_mipmap_image(mipmap_bias); + int width = tex->get_expected_mipmap_x_size(mipmap_bias); + int height = tex->get_expected_mipmap_y_size(mipmap_bias); + int depth = tex->get_expected_mipmap_z_size(mipmap_bias); if (GLCAT.is_debug()) { if (image_compression != Texture::CM_off) { GLCAT.debug() - << "loading pre-compressed texture " << tex->get_name() << "\n"; + << "loading pre-compressed texture " << tex->get_name() << "\n"; } else if (is_compressed_format(internal_format)) { GLCAT.debug() - << "compressing texture " << tex->get_name() << "\n"; + << "compressing texture " << tex->get_name() << "\n"; } } @@ -6697,10 +6678,10 @@ upload_texture_image(CLP(TextureContext) *gtc, if (image.is_null()) { if (uses_mipmaps) { if (_supports_generate_mipmap) { - GLP(TexParameteri)(target, GL_GENERATE_MIPMAP, true); + GLP(TexParameteri)(texture_target, GL_GENERATE_MIPMAP, true); } else { // If it can't, do without mipmaps. - GLP(TexParameteri)(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + GLP(TexParameteri)(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); uses_mipmaps = false; } } @@ -6723,7 +6704,7 @@ upload_texture_image(CLP(TextureContext) *gtc, // We will load the mipmap levels from RAM. Don't ask the GL to // generate them. if (_supports_generate_mipmap) { - GLP(TexParameteri)(target, GL_GENERATE_MIPMAP, false); + GLP(TexParameteri)(texture_target, GL_GENERATE_MIPMAP, false); } load_ram_mipmaps = true; @@ -6731,10 +6712,10 @@ upload_texture_image(CLP(TextureContext) *gtc, // We don't have mipmap levels in RAM. Ask the GL to generate // them if it can. if (_supports_generate_mipmap) { - GLP(TexParameteri)(target, GL_GENERATE_MIPMAP, true); + GLP(TexParameteri)(texture_target, GL_GENERATE_MIPMAP, true); } else { // If it can't, do without mipmaps. - GLP(TexParameteri)(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + GLP(TexParameteri)(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); uses_mipmaps = false; } } @@ -6764,52 +6745,65 @@ upload_texture_image(CLP(TextureContext) *gtc, case 5: color = 0xFFFF00FF; break; } for (int i=0; iget_ram_mipmap_image(n); - if (image == (const unsigned char *)NULL) { + for (int n = mipmap_bias; n < num_ram_mipmap_levels; ++n) { + const unsigned char *image_ptr = tex->get_ram_mipmap_image(n); + if (image_ptr == (const unsigned char *)NULL) { GLCAT.warning() << "No mipmap level " << n << " defined for " << tex->get_name() << "\n"; // No mipmap level n; stop here. break; } + size_t image_size = tex->get_ram_mipmap_image_size(n); if (one_page_only) { image_size = tex->get_ram_mipmap_page_size(n); - image += image_size * z; + image_ptr += image_size * z; } + 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, image_size, + external_format, tex); + } + + int width = tex->get_expected_mipmap_x_size(n); + int height = tex->get_expected_mipmap_y_size(n); + int depth = tex->get_expected_mipmap_z_size(n); + #ifdef DO_PSTATS _data_transferred_pcollector.add_level(image_size); #endif - switch (target) { + switch (texture_target) { case GL_TEXTURE_1D: if (image_compression == Texture::CM_off) { - GLP(TexImage1D)(target, n, internal_format, + GLP(TexImage1D)(page_target, n - mipmap_bias, internal_format, width, 0, - external_format, component_type, image); + external_format, component_type, image_ptr); } else { - _glCompressedTexImage1D(target, n, external_format, width, - 0, image_size, image); + _glCompressedTexImage1D(page_target, n - mipmap_bias, external_format, width, + 0, image_size, image_ptr); } break; case GL_TEXTURE_3D: if (_supports_3d_texture) { if (image_compression == Texture::CM_off) { - _glTexImage3D(target, n, internal_format, + _glTexImage3D(page_target, n - mipmap_bias, internal_format, width, height, depth, 0, - external_format, component_type, image); + external_format, component_type, image_ptr); } else { - _glCompressedTexImage3D(target, n, external_format, width, + _glCompressedTexImage3D(page_target, n - mipmap_bias, external_format, width, height, depth, - 0, image_size, image); + 0, image_size, image_ptr); } } else { report_my_gl_errors(); @@ -6819,61 +6813,70 @@ upload_texture_image(CLP(TextureContext) *gtc, default: if (image_compression == Texture::CM_off) { - GLP(TexImage2D)(target, n, internal_format, + GLP(TexImage2D)(page_target, n - mipmap_bias, internal_format, width, height, 0, - external_format, component_type, image); + external_format, component_type, image_ptr); } else { - _glCompressedTexImage2D(target, n, external_format, width, height, - 0, image_size, image); + _glCompressedTexImage2D(page_target, n - mipmap_bias, external_format, width, height, + 0, image_size, image_ptr); } } highest_level = n; - - width = max(width >> 1, 1); - height = max(height >> 1, 1); - depth = max(depth >> 1, 1); } } else { // We can reload the image over the previous image, possibly // saving on texture memory fragmentation. - for (int n = 0; n < num_ram_mipmap_levels; ++n) { - const unsigned char *image = tex->get_ram_mipmap_image(n); - if (image == (const unsigned char *)NULL) { + for (int n = mipmap_bias; n < num_ram_mipmap_levels; ++n) { + const unsigned char *image_ptr = tex->get_ram_mipmap_image(n); + if (image_ptr == (const unsigned char *)NULL) { GLCAT.warning() << "No mipmap level " << n << " defined for " << tex->get_name() << "\n"; // No mipmap level n; stop here. break; } + size_t image_size = tex->get_ram_mipmap_image_size(n); if (one_page_only) { image_size = tex->get_ram_mipmap_page_size(n); - image += image_size * z; + image_ptr += image_size * z; } + 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, image_size, + external_format, tex); + } + + int width = tex->get_expected_mipmap_x_size(n); + int height = tex->get_expected_mipmap_y_size(n); + int depth = tex->get_expected_mipmap_z_size(n); + #ifdef DO_PSTATS _data_transferred_pcollector.add_level(image_size); #endif - switch (target) { + switch (texture_target) { case GL_TEXTURE_1D: if (image_compression == Texture::CM_off) { - GLP(TexSubImage1D)(target, n, 0, width, - external_format, component_type, image); + GLP(TexSubImage1D)(page_target, n - mipmap_bias, 0, width, + external_format, component_type, image_ptr); } else { - _glCompressedTexSubImage1D(target, n, 0, width, - external_format, image_size, image); + _glCompressedTexSubImage1D(page_target, n - mipmap_bias, 0, width, + external_format, image_size, image_ptr); } break; case GL_TEXTURE_3D: if (_supports_3d_texture) { if (image_compression == Texture::CM_off) { - _glTexSubImage3D(target, n, 0, 0, 0, width, height, depth, - external_format, component_type, image); + _glTexSubImage3D(page_target, n - mipmap_bias, 0, 0, 0, width, height, depth, + external_format, component_type, image_ptr); } else { - _glCompressedTexSubImage3D(target, n, 0, 0, 0, width, height, depth, - external_format, image_size, image); + _glCompressedTexSubImage3D(page_target, n - mipmap_bias, 0, 0, 0, width, height, depth, + external_format, image_size, image_ptr); } } else { report_my_gl_errors(); @@ -6883,20 +6886,16 @@ upload_texture_image(CLP(TextureContext) *gtc, default: if (image_compression == Texture::CM_off) { - GLP(TexSubImage2D)(target, n, 0, 0, width, height, - external_format, component_type, image); + GLP(TexSubImage2D)(page_target, n - mipmap_bias, 0, 0, width, height, + external_format, component_type, image_ptr); } else { - _glCompressedTexSubImage2D(target, n, 0, 0, width, height, - external_format, image_size, image); + _glCompressedTexSubImage2D(page_target, n - mipmap_bias, 0, 0, width, height, + external_format, image_size, image_ptr); } break; } highest_level = n; - - width = max(width >> 1, 1); - height = max(height >> 1, 1); - depth = max(depth >> 1, 1); } } @@ -6904,14 +6903,14 @@ upload_texture_image(CLP(TextureContext) *gtc, // By the time we get here, we have successfully loaded a certain // number of mipmap levels. Tell the GL that's all it's going to // get. - GLP(TexParameteri)(target, GL_TEXTURE_MAX_LEVEL, highest_level); + GLP(TexParameteri)(texture_target, GL_TEXTURE_MAX_LEVEL, highest_level - mipmap_bias); } else if (uses_mipmaps) { // Since the mipmap levels were auto-generated and are therefore // complete, make sure the GL doesn't remember some previous value // for GL_TEXTURE_MAX_LEVEL from the above call--set it to the // full count of mipmap levels. - GLP(TexParameteri)(target, GL_TEXTURE_MAX_LEVEL, tex->get_expected_num_mipmap_levels() - 1); + GLP(TexParameteri)(texture_target, GL_TEXTURE_MAX_LEVEL, tex->get_expected_num_mipmap_levels() - mipmap_bias - 1); } // Report the error message explicitly if the GL texture creation diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.h b/panda/src/glstuff/glGraphicsStateGuardian_src.h index 9084e088d8..2d9e4b189f 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.h +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.h @@ -293,10 +293,11 @@ protected: void specify_texture(Texture *tex); void apply_texture(TextureContext *tc); bool upload_texture(CLP(TextureContext) *gtc); - bool upload_texture_image(CLP(TextureContext) *gtc, bool uses_mipmaps, - GLenum target, GLint internal_format, - int width, int height, int depth, - GLint external_format, GLenum component_type, + bool upload_texture_image(CLP(TextureContext) *gtc, + bool uses_mipmaps, int mipmap_bias, + GLenum texture_target, GLenum page_target, + GLint internal_format, GLint external_format, + GLenum component_type, bool one_page_only, int z, Texture::CompressionMode image_compression); diff --git a/panda/src/gobj/texture.I b/panda/src/gobj/texture.I index 0a33f8ae5b..cf2f40a946 100644 --- a/panda/src/gobj/texture.I +++ b/panda/src/gobj/texture.I @@ -134,7 +134,7 @@ setup_cube_map(int size, ComponentType component_type, INLINE bool Texture:: read(const Filename &fullpath) { ++_modified; - return do_read(fullpath, Filename(), 0, 0, 0, 0, false, false); + return do_read(fullpath, Filename(), 0, 0, 0, 0, false, false, NULL); } //////////////////////////////////////////////////////////////////// @@ -153,7 +153,7 @@ read(const Filename &fullpath, const Filename &alpha_fullpath, int primary_file_num_channels, int alpha_file_channel) { ++_modified; return do_read(fullpath, alpha_fullpath, primary_file_num_channels, - alpha_file_channel, 0, 0, false, false); + alpha_file_channel, 0, 0, false, false, NULL); } //////////////////////////////////////////////////////////////////// @@ -170,7 +170,8 @@ INLINE bool Texture:: read(const Filename &fullpath, int z, int n, bool read_pages, bool read_mipmaps) { ++_modified; - return do_read(fullpath, Filename(), 0, 0, z, n, read_pages, read_mipmaps); + return do_read(fullpath, Filename(), 0, 0, z, n, read_pages, read_mipmaps, + NULL); } //////////////////////////////////////////////////////////////////// @@ -245,10 +246,12 @@ read(const Filename &fullpath, int z, int n, INLINE bool Texture:: read(const Filename &fullpath, const Filename &alpha_fullpath, int primary_file_num_channels, int alpha_file_channel, - int z, int n, bool read_pages, bool read_mipmaps) { + int z, int n, bool read_pages, bool read_mipmaps, + BamCacheRecord *record) { ++_modified; return do_read(fullpath, alpha_fullpath, primary_file_num_channels, - alpha_file_channel, z, n, read_pages, read_mipmaps); + alpha_file_channel, z, n, read_pages, read_mipmaps, + record); } //////////////////////////////////////////////////////////////////// @@ -329,7 +332,7 @@ write(const Filename &fullpath, int z, int n, INLINE bool Texture:: load(const PNMImage &pnmimage) { ++_modified; - return do_load_one(pnmimage, 0, 0); + return do_load_one(pnmimage, get_name(), 0, 0); } //////////////////////////////////////////////////////////////////// @@ -341,7 +344,7 @@ load(const PNMImage &pnmimage) { INLINE bool Texture:: load(const PNMImage &pnmimage, int z, int n) { ++_modified; - return do_load_one(pnmimage, z, n); + return do_load_one(pnmimage, get_name(), z, n); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx index 7c52ef1e99..504035ed78 100644 --- a/panda/src/gobj/texture.cxx +++ b/panda/src/gobj/texture.cxx @@ -22,6 +22,8 @@ #include "config_util.h" #include "texturePool.h" #include "textureContext.h" +#include "bamCache.h" +#include "bamCacheRecord.h" #include "datagram.h" #include "datagramIterator.h" #include "bamReader.h" @@ -447,6 +449,16 @@ read_txo(istream &in, const string &filename) { } TypedWritable *object = reader.read_object(); + + if (object != (TypedWritable *)NULL && + object->is_exact_type(BamCacheRecord::get_class_type())) { + // Here's a special case: if the first object in the file is a + // BamCacheRecord, it's really a cache data file and not a true + // txo file; but skip over the cache data record and let the user + // treat it like an ordinary txo file. + object = reader.read_object(); + } + if (object == (TypedWritable *)NULL) { gobj_cat.error() << "Texture object " << filename << " is empty.\n"; @@ -1629,7 +1641,8 @@ make_texture() { bool Texture:: do_read(const Filename &fullpath, const Filename &alpha_fullpath, int primary_file_num_channels, int alpha_file_channel, - int z, int n, bool read_pages, bool read_mipmaps) { + int z, int n, bool read_pages, bool read_mipmaps, + BamCacheRecord *record) { if ((z == 0 || read_pages) && (n == 0 || read_mipmaps)) { // When we re-read the page 0 of the base image, we clear // everything and start over. @@ -1637,6 +1650,9 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath, } if (is_txo_filename(fullpath)) { + if (record != (BamCacheRecord *)NULL) { + record->add_dependent_file(fullpath); + } return read_txo_file(fullpath); } @@ -1708,7 +1724,7 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath, while ((z_size == 0 && (vfs->exists(file) || z == 0)) || (z_size != 0 && z < z_size)) { if (!do_read_one(file, alpha_file, z, n, primary_file_num_channels, - alpha_file_channel)) { + alpha_file_channel, record)) { return false; } ++z; @@ -1745,7 +1761,7 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath, while ((z_size == 0 && (vfs->exists(file) || z == 0)) || (z_size != 0 && z < z_size)) { if (!do_read_one(file, alpha_file, z, 0, primary_file_num_channels, - alpha_file_channel)) { + alpha_file_channel, record)) { return false; } ++z; @@ -1768,20 +1784,22 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath, n = 0; Filename file = fullpath_pattern.get_filename_index(n); Filename alpha_file = alpha_fullpath_pattern.get_filename_index(n); + while ((n_size == 0 && (vfs->exists(file) || n == 0)) || (n_size != 0 && n < n_size)) { if (!do_read_one(file, alpha_file, z, n, - primary_file_num_channels, alpha_file_channel)) { + primary_file_num_channels, alpha_file_channel, + record)) { return false; } - if (n == 0 && n_size == 0) { - // If n_size is not specified, it gets implicitly set after we - // read the base texture image (which determines the size of - // the texture). - n_size = get_expected_num_mipmap_levels(); - } ++n; + if (n_size == 0 && n >= get_expected_num_mipmap_levels()) { + // Don't try to read more than the requisite number of mipmap + // levels (unless the user insisted on it for some reason). + break; + } + file = fullpath_pattern.get_filename_index(n); alpha_file = alpha_fullpath_pattern.get_filename_index(n); } @@ -1789,7 +1807,8 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath, } else { // Just an ordinary read of one file. if (!do_read_one(fullpath, alpha_fullpath, z, n, - primary_file_num_channels, alpha_file_channel)) { + primary_file_num_channels, alpha_file_channel, + record)) { return false; } } @@ -1810,7 +1829,12 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath, //////////////////////////////////////////////////////////////////// bool Texture:: do_read_one(const Filename &fullpath, const Filename &alpha_fullpath, - int z, int n, int primary_file_num_channels, int alpha_file_channel) { + int z, int n, int primary_file_num_channels, int alpha_file_channel, + BamCacheRecord *record) { + if (record != (BamCacheRecord *)NULL) { + record->add_dependent_file(fullpath); + } + PNMImage image; if (textures_header_only) { if (!image.read_header(fullpath)) { @@ -1835,6 +1859,10 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath, PNMImage alpha_image; if (!alpha_fullpath.empty()) { + if (record != (BamCacheRecord *)NULL) { + record->add_dependent_file(alpha_fullpath); + } + if (textures_header_only) { if (!alpha_image.read_header(alpha_fullpath)) { gobj_cat.error() @@ -1875,7 +1903,7 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath, set_fullpath(fullpath); set_alpha_fullpath(alpha_fullpath); - consider_rescale(image); + consider_rescale(image, fullpath.get_basename()); } if (!alpha_fullpath.empty()) { @@ -1939,7 +1967,7 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath, } } - return do_load_one(image, z, n); + return do_load_one(image, fullpath.get_basename(), z, n); } //////////////////////////////////////////////////////////////////// @@ -2063,7 +2091,7 @@ do_write_one(const Filename &fullpath, int z, int n) const { // level. //////////////////////////////////////////////////////////////////// bool Texture:: -do_load_one(const PNMImage &pnmimage, int z, int n) { +do_load_one(const PNMImage &pnmimage, const string &name, int z, int n) { if (_ram_images.size() <= 1 && n == 0) { // A special case for mipmap level 0. When we load mipmap level // 0, unless we already have mipmap levels, it determines the @@ -2073,18 +2101,20 @@ do_load_one(const PNMImage &pnmimage, int z, int n) { } nassertr(z >= 0 && z < _z_size, false); - ComponentType component_type = T_unsigned_byte; - xelval maxval = pnmimage.get_maxval(); - if (maxval > 255) { - component_type = T_unsigned_short; + if (z == 0) { + ComponentType component_type = T_unsigned_byte; + xelval maxval = pnmimage.get_maxval(); + if (maxval > 255) { + component_type = T_unsigned_short; + } + + if (!reconsider_image_properties(pnmimage.get_x_size(), pnmimage.get_y_size(), + pnmimage.get_num_channels(), component_type, + z)) { + return false; + } } - - if (!reconsider_image_properties(pnmimage.get_x_size(), pnmimage.get_y_size(), - pnmimage.get_num_channels(), component_type, - z)) { - return false; - } - + do_modify_ram_image(); _loaded_from_image = true; } @@ -2097,8 +2127,13 @@ do_load_one(const PNMImage &pnmimage, int z, int n) { if (pnmimage.get_x_size() != x_size || pnmimage.get_y_size() != y_size) { gobj_cat.info() - << "Automatically rescaling " << get_name() << " mipmap level " - << n << " from " << pnmimage.get_x_size() << " by " + << "Automatically rescaling " << name; + if (n != 0) { + gobj_cat.info(false) + << " mipmap level " << n; + } + gobj_cat.info(false) + << " from " << pnmimage.get_x_size() << " by " << pnmimage.get_y_size() << " to " << x_size << " by " << y_size << "\n"; @@ -2159,6 +2194,21 @@ reconsider_dirty() { //////////////////////////////////////////////////////////////////// void Texture:: reload_ram_image() { + BamCache *cache = BamCache::get_global_ptr(); + if (cache->get_active() && !textures_header_only) { + // See if the texture can be found in the on-disk cache, if it is + // active. + PT(BamCacheRecord) record = cache->lookup(get_fullpath(), "txo"); + if (record != (BamCacheRecord *)NULL && + record->has_data()) { + gobj_cat.info() + << "Texture " << get_name() << " reloaded from disk cache.\n"; + PT(Texture) tex = DCAST(Texture, record->extract_data()); + (*this) = (*tex); + return; + } + } + gobj_cat.info() << "Reloading texture " << get_name() << "\n"; do_make_ram_image(); @@ -2175,7 +2225,7 @@ reload_ram_image() { do_read(get_fullpath(), get_alpha_fullpath(), _primary_file_num_channels, _alpha_file_channel, - z, n, _has_read_pages, _has_read_mipmaps); + z, n, _has_read_pages, _has_read_mipmaps, NULL); } //////////////////////////////////////////////////////////////////// @@ -2390,7 +2440,7 @@ reconsider_image_properties(int x_size, int y_size, int num_components, _component_type != component_type) { gobj_cat.error() << "Texture properties have changed for texture " << get_name() - << " level " << z << ".\n"; + << " page " << z << ".\n"; return false; } } @@ -2607,7 +2657,7 @@ clear_prepared(PreparedGraphicsObjects *prepared_objects) { // Config.prc file. //////////////////////////////////////////////////////////////////// void Texture:: -consider_rescale(PNMImage &pnmimage) { +consider_rescale(PNMImage &pnmimage, const string &name) { int new_x_size = pnmimage.get_x_size(); int new_y_size = pnmimage.get_y_size(); @@ -2647,7 +2697,7 @@ consider_rescale(PNMImage &pnmimage) { if (pnmimage.get_x_size() != new_x_size || pnmimage.get_y_size() != new_y_size) { gobj_cat.info() - << "Automatically rescaling " << get_name() << " from " + << "Automatically rescaling " << name << " from " << pnmimage.get_x_size() << " by " << pnmimage.get_y_size() << " to " << new_x_size << " by " << new_y_size << "\n"; @@ -3125,7 +3175,11 @@ make_from_bam(const FactoryParams ¶ms) { // and don't load from the file. me = new Texture(name); me->_filename = filename; + me->_fullpath = filename; + me->_fullpath.make_absolute(); me->_alpha_filename = alpha_filename; + me->_alpha_fullpath = alpha_filename; + me->_alpha_fullpath.make_absolute(); me->_primary_file_num_channels = primary_file_num_channels; me->_alpha_file_channel = alpha_file_channel; me->_texture_type = texture_type; diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h index 65aebaa7c9..4cc7c3a0f1 100644 --- a/panda/src/gobj/texture.h +++ b/panda/src/gobj/texture.h @@ -35,6 +35,7 @@ class FactoryParams; class PreparedGraphicsObjects; class CullTraverser; class CullTraverserData; +class BamCacheRecord; //////////////////////////////////////////////////////////////////// // Class : Texture @@ -206,7 +207,8 @@ PUBLISHED: bool read_pages, bool read_mipmaps); INLINE bool read(const Filename &fullpath, const Filename &alpha_fullpath, int primary_file_num_channels, int alpha_file_channel, - int z, int n, bool read_pages, bool read_mipmaps); + int z, int n, bool read_pages, bool read_mipmaps, + BamCacheRecord *record = NULL); INLINE bool write(const Filename &fullpath); INLINE bool write(const Filename &fullpath, int z, int n, @@ -362,14 +364,17 @@ public: protected: virtual bool do_read(const Filename &fullpath, const Filename &alpha_fullpath, int primary_file_num_channels, int alpha_file_channel, - int z, int n, bool read_pages, bool read_mipmaps); + int z, int n, bool read_pages, bool read_mipmaps, + BamCacheRecord *record); virtual bool do_read_one(const Filename &fullpath, const Filename &alpha_fullpath, - int z, int n, int primary_file_num_channels, int alpha_file_channel); + int z, int n, int primary_file_num_channels, int alpha_file_channel, + BamCacheRecord *record); bool do_write(const Filename &fullpath, int z, int n, bool write_pages, bool write_mipmaps) const; bool do_write_one(const Filename &fullpath, int z, int n) const; - virtual bool do_load_one(const PNMImage &pnmimage, int z, int n); + virtual bool do_load_one(const PNMImage &pnmimage, const string &name, + int z, int n); bool do_store_one(PNMImage &pnmimage, int z, int n) const; virtual void reconsider_dirty(); @@ -397,7 +402,7 @@ private: CPTA_uchar image, size_t page_size, int z) const; void clear_prepared(PreparedGraphicsObjects *prepared_objects); - void consider_rescale(PNMImage &pnmimage); + void consider_rescale(PNMImage &pnmimage, const string &name); void consider_downgrade(PNMImage &pnmimage, int num_channels); INLINE static void store_unscaled_byte(unsigned char *&p, int value); diff --git a/panda/src/gobj/texturePool.cxx b/panda/src/gobj/texturePool.cxx index 2afd56331b..65446453aa 100644 --- a/panda/src/gobj/texturePool.cxx +++ b/panda/src/gobj/texturePool.cxx @@ -22,6 +22,8 @@ #include "config_express.h" #include "string_utils.h" #include "virtualFileSystem.h" +#include "bamCache.h" +#include "bamCacheRecord.h" #include "pnmFileTypeRegistry.h" @@ -225,20 +227,51 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels, return (*ti).second; } - gobj_cat.info() - << "Loading texture " << filename << "\n"; - PT(Texture) tex = make_texture(filename.get_extension()); - if (!tex->read(filename, Filename(), primary_file_num_channels, 0, - 0, 0, false, read_mipmaps)) { - // This texture was not found or could not be read. - report_texture_unreadable(filename); - return NULL; + PT(Texture) tex; + PT(BamCacheRecord) record; + bool store_record = false; + + BamCache *cache = BamCache::get_global_ptr(); + if (cache->get_active() && !textures_header_only) { + // See if the texture can be found in the on-disk cache, if it is + // active. + record = cache->lookup(filename, "txo"); + if (record != (BamCacheRecord *)NULL) { + if (record->has_data()) { + gobj_cat.info() + << "Texture " << filename << " found in disk cache.\n"; + tex = DCAST(Texture, record->extract_data()); + } + } + } + + if (tex == (Texture *)NULL) { + // The texture was neither in the pool, nor found in the on-disk + // cache; it needs to be loaded from its source image(s). + gobj_cat.info() + << "Loading texture " << filename << "\n"; + tex = make_texture(filename.get_extension()); + if (!tex->read(filename, Filename(), primary_file_num_channels, 0, + 0, 0, false, read_mipmaps, record)) { + // This texture was not found or could not be read. + report_texture_unreadable(filename); + return NULL; + } + store_record = (record != (BamCacheRecord *)NULL); } // Set the original filename, before we searched along the path. + nassertr(tex != (Texture *)NULL, false); tex->set_filename(orig_filename); tex->_texture_pool_key = filename; _textures[filename] = tex; + + if (store_record) { + // Store the on-disk cache record for next time. + record->set_data(tex, false); + cache->store(record); + } + return tex; } @@ -275,22 +308,53 @@ ns_load_texture(const Filename &orig_filename, return (*ti).second; } - gobj_cat.info() - << "Loading texture " << filename << " and alpha component " - << alpha_filename << endl; - PT(Texture) tex = make_texture(filename.get_extension()); - if (!tex->read(filename, alpha_filename, primary_file_num_channels, - alpha_file_channel, 0, 0, false, read_mipmaps)) { - // This texture was not found or could not be read. - report_texture_unreadable(filename); - return NULL; + PT(Texture) tex; + PT(BamCacheRecord) record; + bool store_record = false; + + BamCache *cache = BamCache::get_global_ptr(); + if (cache->get_active() && !textures_header_only) { + // See if the texture can be found in the on-disk cache, if it is + // active. + record = cache->lookup(filename, "txo"); + if (record != (BamCacheRecord *)NULL) { + if (record->has_data()) { + gobj_cat.info() + << "Texture " << filename << " found in disk cache.\n"; + tex = DCAST(Texture, record->extract_data()); + } + } + } + + if (tex == (Texture *)NULL) { + // The texture was neither in the pool, nor found in the on-disk + // cache; it needs to be loaded from its source image(s). + gobj_cat.info() + << "Loading texture " << filename << " and alpha component " + << alpha_filename << endl; + tex = make_texture(filename.get_extension()); + if (!tex->read(filename, alpha_filename, primary_file_num_channels, + alpha_file_channel, 0, 0, false, read_mipmaps)) { + // This texture was not found or could not be read. + report_texture_unreadable(filename); + return NULL; + } + store_record = (record != (BamCacheRecord *)NULL); } // Set the original filenames, before we searched along the path. + nassertr(tex != (Texture *)NULL, false); tex->set_filename(orig_filename); tex->set_alpha_filename(orig_alpha_filename); tex->_texture_pool_key = filename; _textures[filename] = tex; + + if (store_record) { + // Store the on-disk cache record for next time. + record->set_data(tex, false); + cache->store(record); + } + return tex; } @@ -316,20 +380,52 @@ ns_load_3d_texture(const Filename &filename_pattern, return (*ti).second; } - gobj_cat.info() - << "Loading 3-d texture " << filename << "\n"; - PT(Texture) tex = make_texture(filename.get_extension()); - tex->setup_3d_texture(); - if (!tex->read(filename, 0, 0, true, read_mipmaps)) { - // This texture was not found or could not be read. - report_texture_unreadable(filename); - return NULL; + PT(Texture) tex; + PT(BamCacheRecord) record; + bool store_record = false; + + BamCache *cache = BamCache::get_global_ptr(); + if (cache->get_active() && !textures_header_only) { + // See if the texture can be found in the on-disk cache, if it is + // active. + record = cache->lookup(filename, "txo"); + if (record != (BamCacheRecord *)NULL) { + if (record->has_data()) { + gobj_cat.info() + << "Texture " << filename << " found in disk cache.\n"; + tex = DCAST(Texture, record->extract_data()); + } + } + } + + if (tex == (Texture *)NULL || + tex->get_texture_type() != Texture::TT_3d_texture) { + // The texture was neither in the pool, nor found in the on-disk + // cache; it needs to be loaded from its source image(s). + gobj_cat.info() + << "Loading 3-d texture " << filename << "\n"; + tex = make_texture(filename.get_extension()); + tex->setup_3d_texture(); + if (!tex->read(filename, 0, 0, true, read_mipmaps)) { + // This texture was not found or could not be read. + report_texture_unreadable(filename); + return NULL; + } + store_record = (record != (BamCacheRecord *)NULL); } // Set the original filename, before we searched along the path. + nassertr(tex != (Texture *)NULL, false); tex->set_filename(filename_pattern); tex->_texture_pool_key = filename; _textures[filename] = tex; + + if (store_record) { + // Store the on-disk cache record for next time. + record->set_data(tex, false); + cache->store(record); + } + return tex; } @@ -354,20 +450,52 @@ ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps) { return (*ti).second; } - gobj_cat.info() - << "Loading cube map texture " << filename << "\n"; - PT(Texture) tex = make_texture(filename.get_extension()); - tex->setup_cube_map(); - if (!tex->read(filename, 0, 0, true, read_mipmaps)) { - // This texture was not found or could not be read. - report_texture_unreadable(filename); - return NULL; + PT(Texture) tex; + PT(BamCacheRecord) record; + bool store_record = false; + + BamCache *cache = BamCache::get_global_ptr(); + if (cache->get_active() && !textures_header_only) { + // See if the texture can be found in the on-disk cache, if it is + // active. + record = cache->lookup(filename, "txo"); + if (record != (BamCacheRecord *)NULL) { + if (record->has_data()) { + gobj_cat.info() + << "Texture " << filename << " found in disk cache.\n"; + tex = DCAST(Texture, record->extract_data()); + } + } } + if (tex == (Texture *)NULL || + tex->get_texture_type() != Texture::TT_cube_map) { + // The texture was neither in the pool, nor found in the on-disk + // cache; it needs to be loaded from its source image(s). + gobj_cat.info() + << "Loading cube map texture " << filename << "\n"; + tex = make_texture(filename.get_extension()); + tex->setup_cube_map(); + if (!tex->read(filename, 0, 0, true, read_mipmaps)) { + // This texture was not found or could not be read. + report_texture_unreadable(filename); + return NULL; + } + store_record = (record != (BamCacheRecord *)NULL); + } + // Set the original filename, before we searched along the path. + nassertr(tex != (Texture *)NULL, false); tex->set_filename(filename_pattern); tex->_texture_pool_key = filename; _textures[filename] = tex; + + if (store_record) { + // Store the on-disk cache record for next time. + record->set_data(tex, false); + cache->store(record); + } + return tex; } diff --git a/panda/src/pgraph/bamFile.cxx b/panda/src/pgraph/bamFile.cxx index 31518a518f..e0aee9ebc9 100644 --- a/panda/src/pgraph/bamFile.cxx +++ b/panda/src/pgraph/bamFile.cxx @@ -20,6 +20,7 @@ #include "config_pgraph.h" #include "bam.h" +#include "bamCacheRecord.h" #include "config_util.h" #include "bamReader.h" #include "bamWriter.h" @@ -174,6 +175,16 @@ read_node(bool report_errors) { PT(PandaNode) result; TypedWritable *object = read_object(); + + if (object != (TypedWritable *)NULL && + object->is_exact_type(BamCacheRecord::get_class_type())) { + // Here's a special case: if the first object in the file is a + // BamCacheRecord, it's really a cache data file and not a true + // bam file; but skip over the cache data record and let the user + // treat it like an ordinary bam file. + object = read_object(); + } + if (object == TypedWritable::Null) { if (report_errors) { loader_cat.error() << "Bam file " << _bam_filename << " is empty.\n"; diff --git a/panda/src/pgraph/loader.cxx b/panda/src/pgraph/loader.cxx index c8ae67e17a..359af93481 100644 --- a/panda/src/pgraph/loader.cxx +++ b/panda/src/pgraph/loader.cxx @@ -34,6 +34,8 @@ #include "string_utils.h" #include "plist.h" #include "pvector.h" +#include "bamCache.h" +#include "bamCacheRecord.h" #include @@ -463,11 +465,33 @@ load_file(const Filename &filename, const LoaderOptions &options) const { return NULL; } + BamCache *cache = BamCache::get_global_ptr(); for (int i = 0; i < num_files; ++i) { const Filename &path = results.get_file(i); + + PT(BamCacheRecord) record; + + if (cache->get_active()) { + // See if the texture can be found in the on-disk cache, if it is + // active. + record = cache->lookup(path, "bam"); + if (record != (BamCacheRecord *)NULL) { + if (record->has_data()) { + loader_cat.info() + << "Model " << path << " found in disk cache.\n"; + return DCAST(PandaNode, record->extract_data()); + } + } + } + LoaderFileType *type = results.get_file_type(i); PT(PandaNode) result = type->load_file(path, options); - if (result != (PandaNode *)NULL) { + if (result != (PandaNode *)NULL){ + if (record != (BamCacheRecord *)NULL) { + record->set_data(result, false); + cache->store(record); + } + return result; } } diff --git a/panda/src/putil/Sources.pp b/panda/src/putil/Sources.pp index b42f3202bc..aa4178e5dd 100644 --- a/panda/src/putil/Sources.pp +++ b/panda/src/putil/Sources.pp @@ -10,7 +10,10 @@ #define SOURCES \ animInterface.h animInterface.I \ - bam.h bamEndian.h \ + bam.h \ + bamCache.h bamCache.I \ + bamCacheRecord.h bamCacheRecord.I \ + bamEndian.h \ bamReader.I bamReader.N bamReader.h bamReaderParam.I \ bamReaderParam.h \ bamTextureMode.h \ @@ -61,6 +64,8 @@ #define INCLUDED_SOURCES \ animInterface.cxx \ + bamCache.cxx \ + bamCacheRecord.cxx \ bamEndian.cxx \ bamReader.cxx bamReaderParam.cxx \ bamTextureMode.cxx \ @@ -97,7 +102,10 @@ #define INSTALL_HEADERS \ animInterface.h animInterface.I \ - bam.h bamEndian.h \ + bam.h \ + bamCache.h bamCache.I \ + bamCacheRecord.h bamCacheRecord.I \ + bamEndian.h \ bamReader.I bamReader.h bamReaderParam.I bamReaderParam.h \ bamTextureMode.h \ bamWriter.I bamWriter.h \ diff --git a/panda/src/putil/bamCache.I b/panda/src/putil/bamCache.I new file mode 100644 index 0000000000..1f49e68e3b --- /dev/null +++ b/panda/src/putil/bamCache.I @@ -0,0 +1,71 @@ +// Filename: bamCache.I +// Created by: drose (09Jun06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::get_active +// Access: Published +// Description: Returns true if the BamCache is currently active, +// false if it is not. "active" means that the cache +// should be consulted automatically on loads, "not +// active" means that objects should be loaded directly +// without consulting the cache. +//////////////////////////////////////////////////////////////////// +INLINE bool BamCache:: +get_active() const { + return _active; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::set_active +// Access: Published +// Description: Changes the state of the active flag. "active" means +// that the cache should be consulted automatically on +// loads, "not active" means that objects should be +// loaded directly without consulting the cache. +//////////////////////////////////////////////////////////////////// +INLINE void BamCache:: +set_active(bool active) { + _active = active; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::get_root +// Access: Published +// Description: Returns the current root pathname of the cache. See +// set_root(). +//////////////////////////////////////////////////////////////////// +INLINE const Filename &BamCache:: +get_root() const { + return _root; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::get_global_ptr +// Access: Published, Static +// Description: Returns a pointer to the global BamCache object, +// which is used automatically by the ModelPool and +// TexturePool. +//////////////////////////////////////////////////////////////////// +INLINE BamCache *BamCache:: +get_global_ptr() { + if (_global_ptr == (BamCache *)NULL) { + make_global(); + } + return _global_ptr; +} diff --git a/panda/src/putil/bamCache.cxx b/panda/src/putil/bamCache.cxx new file mode 100644 index 0000000000..5939db9a63 --- /dev/null +++ b/panda/src/putil/bamCache.cxx @@ -0,0 +1,390 @@ +// Filename: bamCache.cxx +// Created by: drose (09Jun06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "bamCache.h" +#include "hashVal.h" +#include "datagramInputFile.h" +#include "datagramOutputFile.h" +#include "config_util.h" +#include "bam.h" +#include "typeRegistry.h" + +BamCache *BamCache::_global_ptr = NULL; + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +BamCache:: +BamCache() : + _active(true) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::Destructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +BamCache:: +~BamCache() { +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::set_root +// Access: Published +// Description: Changes the current root pathname of the cache. This +// specifies where the cache files are stored on disk. +// This should name a directory that is on a disk local +// to the machine (not on a network-mounted disk), for +// instance, /tmp/panda-cache or /c/panda-cache. +// +// If the directory does not already exist, it will be +// created as a result of this call. +//////////////////////////////////////////////////////////////////// +void BamCache:: +set_root(const Filename &root) { + _root = root; + + // For now, the filename must be a directory. Maybe eventually we + // will support writing caches to a Panda multifile (though maybe it + // would be better to implement this kind of thing at a lower level, + // via a writable VFS, in which case the specified root filename + // will still be a "directory"). + if (!root.is_directory()) { + Filename dirname(_root, Filename(".")); + dirname.make_dir(); + } + nassertv(root.is_directory()); +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::lookup +// Access: Published +// Description: Looks up a file in the cache. +// +// If the file is cacheable, then regardless of whether +// the file is found in the cache or not, this returns a +// BamCacheRecord. On the other hand, if the file +// cannot be cached, returns NULL. +// +// If record->has_data() returns true, then the file was +// found in the cache, and you may call +// record->extract_data() to get the object. If +// record->has_data() returns false, then the file was +// not found in the cache or the cache was stale; and +// you should reload the source file (calling +// record->add_dependent_file() for each file loaded, +// including the original source file), and then call +// record->set_data() to record the resulting loaded +// object; and finally, you should call store() to write +// the cached record to disk. +//////////////////////////////////////////////////////////////////// +PT(BamCacheRecord) BamCache:: +lookup(const Filename &source_filename, const string &cache_extension) { + Filename source_pathname(source_filename); + source_pathname.make_absolute(); + + Filename rel_pathname(source_pathname); + rel_pathname.make_relative_to(_root, false); + if (rel_pathname.is_local()) { + // If the source pathname is already within the cache directory, + // don't cache it further. + return NULL; + } + + Filename cache_filename = hash_filename(source_pathname.get_fullpath()); + cache_filename.set_extension(cache_extension); + + return find_and_read_record(source_pathname, cache_filename); +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::store +// Access: Published +// Description: Flushes a cache entry to disk. You must have +// retrieved the cache record via a prior call to +// lookup(), and then stored the data via +// record->set_data(). Returns true on success, false +// on failure. +//////////////////////////////////////////////////////////////////// +bool BamCache:: +store(BamCacheRecord *record) { + nassertr(!record->_cache_pathname.empty(), false); + nassertr(record->has_data(), false); + +#ifndef NDEBUG + // Ensure that the cache_pathname is within the _root directory tree. + Filename rel_pathname(record->_cache_pathname); + rel_pathname.make_relative_to(_root, false); + nassertr(rel_pathname.is_local(), false); +#endif // NDEBUG + + record->_recorded_time = time(NULL); + + ofstream cache_file; + + Filename filename = Filename::binary_filename(record->_cache_pathname); + if (!filename.open_write(cache_file)) { + util_cat.error() + << "Could not open cache file: " << filename << "\n"; + return false; + } + + DatagramOutputFile dout; + if (!dout.open(cache_file)) { + util_cat.error() + << "Could not write cache file: " << filename << "\n"; + return false; + } + + if (!dout.write_header(_bam_header)) { + util_cat.error() + << "Unable to write to " << filename << "\n"; + return false; + } + + BamWriter writer(&dout, filename); + if (!writer.init()) { + return false; + } + + TypeRegistry *type_registry = TypeRegistry::ptr(); + TypeHandle texture_type = type_registry->find_type("Texture"); + if (record->get_data()->is_of_type(texture_type)) { + // Texture objects write the actual texture image. + writer.set_file_texture_mode(BTM_rawdata); + } else { + // Any other kinds of objects write texture references. + writer.set_file_texture_mode(BTM_fullpath); + } + + if (!writer.write_object(record)) { + return false; + } + + if (!writer.write_object(record->get_data())) { + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::find_and_read_record +// Access: Private +// Description: Looks for the existing cache file that corresponds +// to the indicated filename. Normally, this is the +// specified cache filename exactly; but in the case of +// a hash collision, it may be a variant of the cache +// filename. +//////////////////////////////////////////////////////////////////// +PT(BamCacheRecord) BamCache:: +find_and_read_record(const Filename &source_pathname, + const Filename &cache_filename) const { + int pass = 0; + while (true) { + PT(BamCacheRecord) record = + read_record(source_pathname, cache_filename, pass); + if (record != (BamCacheRecord *)NULL) { + return record; + } + ++pass; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::read_record +// Access: Private +// Description: Reads the indicated cache file and returns its +// associated record if it can be read and it matches +// the source filename. +//////////////////////////////////////////////////////////////////// +PT(BamCacheRecord) BamCache:: +read_record(const Filename &source_pathname, + const Filename &cache_filename, + int pass) const { + Filename filename(_root, cache_filename); + if (pass != 0) { + ostringstream strm; + strm << filename.get_basename_wo_extension() << "_" << pass; + filename.set_basename_wo_extension(strm.str()); + } + + if (!filename.exists()) { + // There is no such cache file already. Declare it. + filename.touch(); + PT(BamCacheRecord) record = + new BamCacheRecord(source_pathname, cache_filename); + record->_cache_pathname = filename; + return record; + } + + filename.set_binary(); + ifstream cache_file; + if (!filename.open_read(cache_file)) { + util_cat.debug() + << "Could not open cache file: " << filename << "\n"; + return NULL; + } + + DatagramInputFile din; + + if (!din.open(cache_file)) { + util_cat.debug() + << "Could not read cache file: " << filename << "\n"; + return NULL; + } + + string head; + if (!din.read_header(head, _bam_header.size())) { + util_cat.debug() + << filename << " is not a cache file.\n"; + return NULL; + } + + if (head != _bam_header) { + util_cat.debug() + << filename << " is not a cache file.\n"; + return NULL; + } + + BamReader reader(&din, filename); + if (!reader.init()) { + return NULL; + } + + TypedWritable *object = reader.read_object(); + if (object == (TypedWritable *)NULL) { + util_cat.debug() + << filename << " is empty.\n"; + return NULL; + + } else if (!object->is_of_type(BamCacheRecord::get_class_type())) { + util_cat.debug() + << "Cache file " << filename << "contains a " + << object->get_type() << ", not a BamCacheRecord.\n"; + return NULL; + } + + PT(BamCacheRecord) record = DCAST(BamCacheRecord, object); + if (!reader.resolve()) { + util_cat.debug() + << "Unable to fully resolve cache record in " << filename << "\n"; + return NULL; + } + + if (record->get_source_pathname() != source_pathname) { + util_cat.debug() + << "Cache file " << filename << " references " + << record->get_source_pathname() << ", not " + << source_pathname << "\n"; + return NULL; + } + + // From this point below, we have validated that the selected + // filename is indeed a cache record for the indicated source file, + // and therefore the cache record will be returned. + + // We still need to decide whether the cache record is stale. + if (record->dependents_unchanged()) { + // The cache record doesn't appear to be stale. Load the cached + // object. + object = reader.read_object(); + + if (object != (TypedWritable *)NULL) { + if (!reader.resolve()) { + util_cat.debug() + << "Unable to fully resolve cached object in " << filename << "\n"; + delete object; + } else { + // The object is valid. Store it in the record. + record->set_data(object, true); + } + } + } + + if (!record->has_data()) { + // If we didn't find any data, the caller will have to reload it. + record->clear_dependent_files(); + } + + record->_cache_pathname = filename; + return record; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::hash_filename +// Access: Private, Static +// Description: Returns the appropriate filename to use for a cache +// file, given the fullpath string to the source +// filename. +//////////////////////////////////////////////////////////////////// +string BamCache:: +hash_filename(const string &filename) { +#ifdef HAVE_OPENSSL + // With OpenSSl, use the MD5 hash of the filename. + HashVal hv; + hv.hash_string(filename); + ostringstream strm; + hv.output_hex(strm); + return strm.str(); + +#else // HAVE_OPENSSL + // Without OpenSSL, don't get fancy; just build a simple hash. + unsigned int hash = 0; + for (string::const_iterator si = filename.begin(); + si != filename.end(); + ++si) { + hash = (hash * 9109) + (unsigned int)(*si); + } + + ostringstream strm; + strm << hex << setw(8) << setfill('0') << hash; + return strm.str(); + +#endif // HAVE_OPENSSL +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCache::make_global +// Access: Private, Static +// Description: Constructs the global BamCache object. +//////////////////////////////////////////////////////////////////// +void BamCache:: +make_global() { + _global_ptr = new BamCache; + + ConfigVariableFilename model_cache_dir + ("model-cache-dir", Filename(), + PRC_DESC("The full path to a directory, local to this computer, in which " + "model and texture files will be cached on load. If a directory " + "name is specified here, files may be loaded from the cache " + "instead of from their actual pathnames, which may save load time, " + "especially if you are loading egg files instead of bam files, " + "or if you are loading models from a shared network drive. " + "If this is the empty string, no cache will be used.")); + if (model_cache_dir.empty()) { + _global_ptr->set_active(false); + + } else { + _global_ptr->set_active(true); + _global_ptr->set_root(model_cache_dir); + } +} diff --git a/panda/src/putil/bamCache.h b/panda/src/putil/bamCache.h new file mode 100644 index 0000000000..32a2afd971 --- /dev/null +++ b/panda/src/putil/bamCache.h @@ -0,0 +1,67 @@ +// Filename: bamCache.h +// Created by: drose (09Jun06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#ifndef BAMCACHE_H +#define BAMCACHE_H + +#include "pandabase.h" +#include "bamCacheRecord.h" +#include "pointerTo.h" + +//////////////////////////////////////////////////////////////////// +// Class : BamCache +// Description : This class maintains a cache of Bam and/or Txo +// objects generated from model files and texture images +// (as well as possibly other kinds of loadable objects +// that can be stored in bam file format). +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA BamCache { +PUBLISHED: + BamCache(); + ~BamCache(); + + INLINE bool get_active() const; + INLINE void set_active(bool flag); + + INLINE const Filename &get_root() const; + void set_root(const Filename &root); + + PT(BamCacheRecord) lookup(const Filename &source_filename, + const string &cache_extension); + bool store(BamCacheRecord *record); + + INLINE static BamCache *get_global_ptr(); + +private: + PT(BamCacheRecord) find_and_read_record(const Filename &source_pathname, + const Filename &cache_filename) const; + PT(BamCacheRecord) read_record(const Filename &source_pathname, + const Filename &cache_filename, + int pass) const; + + static string hash_filename(const string &filename); + static void make_global(); + + bool _active; + Filename _root; + static BamCache *_global_ptr; +}; + +#include "bamCache.I" + +#endif diff --git a/panda/src/putil/bamCacheRecord.I b/panda/src/putil/bamCacheRecord.I new file mode 100644 index 0000000000..2a567ce728 --- /dev/null +++ b/panda/src/putil/bamCacheRecord.I @@ -0,0 +1,154 @@ +// Filename: bamCacheRecord.I +// Created by: drose (09Jun06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::get_source_pathname +// Access: Published +// Description: Returns the full pathname to the source file that +// originally generated this cache request. In some +// cases, for instance in the case of a of a multipage +// texture like "cube_#.png", this may not not a true +// filename on disk. +//////////////////////////////////////////////////////////////////// +INLINE const Filename &BamCacheRecord:: +get_source_pathname() const { + return _source_pathname; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::get_cache_filename +// Access: Published +// Description: Returns the name of the cache file as hashed from the +// source_pathname. This will be relative to the root +// of the cache directory, and it will not include any +// suffixes that may be appended to resolve hash +// conflicts. +//////////////////////////////////////////////////////////////////// +INLINE const Filename &BamCacheRecord:: +get_cache_filename() const { + return _cache_filename; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::get_recorded_time +// Access: Published +// Description: Returns the time at which this particular record was +// recorded or updated. +//////////////////////////////////////////////////////////////////// +INLINE time_t BamCacheRecord:: +get_recorded_time() const { + return _recorded_time; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::get_num_dependent_files +// Access: Published +// Description: Returns the number of source files that contribute to +// the cache. +//////////////////////////////////////////////////////////////////// +INLINE int BamCacheRecord:: +get_num_dependent_files() const { + return _files.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::get_dependent_pathname +// Access: Published +// Description: Returns the full pathname of the nth source files +// that contributes to the cache. +//////////////////////////////////////////////////////////////////// +INLINE const Filename &BamCacheRecord:: +get_dependent_pathname(int n) const { + nassertr(n >= 0 && n < (int)_files.size(), _files[0]._pathname); + return _files[n]._pathname; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::has_data +// Access: Published +// Description: Returns true if this cache record has an in-memory +// data object associated--that is, the object stored in +// the cache. +//////////////////////////////////////////////////////////////////// +INLINE bool BamCacheRecord:: +has_data() const { + return (_data != (TypedWritable *)NULL); +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::clear_data +// Access: Published +// Description: Removes the in-memory data object associated with +// this record, if any. This does not affect the +// on-disk representation of the record. +//////////////////////////////////////////////////////////////////// +INLINE void BamCacheRecord:: +clear_data() { + if (_owns_pointer) { + delete _data; + } + _data = NULL; + _owns_pointer = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::get_data +// Access: Published +// Description: Returns a read-only pointer to the data stored in the +// record, or NULL if there is no data. This does not +// change ownership of the data pointer. +//////////////////////////////////////////////////////////////////// +INLINE const TypedWritable *BamCacheRecord:: +get_data() const { + return _data; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::extract_data +// Access: Published +// Description: Removes the in-memory data object associated with +// this record, and returns it. This transfers +// ownership of the data pointer; the caller will be +// responsible for subsequently deleting this object. +// +// It is an error to call this if the record does not +// own the pointer. +//////////////////////////////////////////////////////////////////// +INLINE TypedWritable *BamCacheRecord:: +extract_data() { + nassertr(_owns_pointer, NULL); + TypedWritable *result = _data; + _data = NULL; + _owns_pointer = false; + return result; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::set_data +// Access: Published +// Description: Stores a new data object on the record. If +// owns_pointer is true, the record owns the pointer, +// and will eventually be responsible for deleting it. +//////////////////////////////////////////////////////////////////// +INLINE void BamCacheRecord:: +set_data(TypedWritable *data, bool owns_pointer) { + clear_data(); + _data = data; + _owns_pointer = owns_pointer; +} diff --git a/panda/src/putil/bamCacheRecord.cxx b/panda/src/putil/bamCacheRecord.cxx new file mode 100644 index 0000000000..9091da81cb --- /dev/null +++ b/panda/src/putil/bamCacheRecord.cxx @@ -0,0 +1,280 @@ +// Filename: bamCacheRecord.cxx +// Created by: drose (09Jun06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "bamCacheRecord.h" +#include "virtualFileSystem.h" +#include "virtualFile.h" +#include "indent.h" + +TypeHandle BamCacheRecord::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::Default Constructor +// Access: Private +// Description: Used when reading from a bam file. +//////////////////////////////////////////////////////////////////// +BamCacheRecord:: +BamCacheRecord() : + _data(NULL), + _owns_pointer(false) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::Constructor +// Access: Private +// Description: Use BamCache::lookup() to create one of these. +//////////////////////////////////////////////////////////////////// +BamCacheRecord:: +BamCacheRecord(const Filename &source_pathname, + const Filename &cache_filename) : + _source_pathname(source_pathname), + _cache_filename(cache_filename), + _data(NULL), + _owns_pointer(false) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::Destructor +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +BamCacheRecord:: +~BamCacheRecord() { + clear_data(); +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::dependents_unchanged +// Access: Published +// Description: Returns true if all of the dependent files are still +// the same as when the cache was recorded, false +// otherwise. +//////////////////////////////////////////////////////////////////// +bool BamCacheRecord:: +dependents_unchanged() const { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + DependentFiles::const_iterator fi; + for (fi = _files.begin(); fi != _files.end(); ++fi) { + const DependentFile &dfile = (*fi); + PT(VirtualFile) file = vfs->get_file(dfile._pathname); + if (file == (VirtualFile *)NULL) { + // No such file. + if (dfile._timestamp != 0) { + return false; + } + } else { + if (file->get_timestamp() != dfile._timestamp || + file->get_file_size() != dfile._size) { + // File has changed timestamp or size. + return false; + } + } + + // Presumably, the file is unchanged. + } + + return true; +} + + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::clear_dependent_files +// Access: Published +// Description: Empties the list of files that contribute to the data +// in this record. +//////////////////////////////////////////////////////////////////// +void BamCacheRecord:: +clear_dependent_files() { + _files.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::add_dependent_file +// Access: Published +// Description: Adds the indicated file to the list of files that +// will be loaded to generate the data in this record. +// This should be called once for the primary source +// file, and again for each secondary source file, if +// any. +//////////////////////////////////////////////////////////////////// +void BamCacheRecord:: +add_dependent_file(const Filename &pathname) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + _files.push_back(DependentFile()); + DependentFile &dfile = _files.back(); + dfile._pathname = pathname; + + PT(VirtualFile) file = vfs->get_file(pathname); + if (file == (VirtualFile *)NULL) { + // No such file. + dfile._timestamp = 0; + dfile._size = 0; + + } else { + dfile._timestamp = file->get_timestamp(); + dfile._size = file->get_file_size(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::output +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void BamCacheRecord:: +output(ostream &out) const { + out << "BamCacheRecord " << get_source_pathname(); +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::write +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void BamCacheRecord:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) + << "BamCacheRecord " << get_source_pathname() << "\n"; + indent(out, indent_level) + << "recorded " << format_timestamp(_recorded_time) << "\n"; + + DependentFiles::const_iterator fi; + for (fi = _files.begin(); fi != _files.end(); ++fi) { + const DependentFile &dfile = (*fi); + indent(out, indent_level) + << " " + << setw(10) << dfile._size << " " + << format_timestamp(dfile._timestamp) << " " + << dfile._pathname << "\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::format_timestamp +// Access: Private, Static +// Description: Returns a timestamp value formatted nicely for +// output. +//////////////////////////////////////////////////////////////////// +string BamCacheRecord:: +format_timestamp(time_t timestamp) { + static const size_t buffer_size = 512; + char buffer[buffer_size]; + + if (timestamp == 0) { + // A zero timestamp is a special case. + return " (no date) "; + } + + time_t now = time(NULL); + struct tm *tm_p = localtime(×tamp); + + if (timestamp > now || (now - timestamp > 86400 * 365)) { + // A timestamp in the future, or more than a year in the past, + // gets a year appended. + strftime(buffer, buffer_size, "%b %d %Y", tm_p); + } else { + // Otherwise, within the past year, show the date and time. + strftime(buffer, buffer_size, "%b %d %H:%M", tm_p); + } + + return buffer; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::register_with_read_factory +// Access: Published, Static +// Description: Tells the BamReader how to create objects of type +// BamCacheRecord. +//////////////////////////////////////////////////////////////////// +void BamCacheRecord:: +register_with_read_factory() { + BamReader::get_factory()->register_factory(get_class_type(), make_from_bam); +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::write_datagram +// Access: Published, Virtual +// Description: Writes the contents of this object to the datagram +// for shipping out to a Bam file. +//////////////////////////////////////////////////////////////////// +void BamCacheRecord:: +write_datagram(BamWriter *manager, Datagram &dg) { + TypedWritableReferenceCount::write_datagram(manager, dg); + dg.add_string(_source_pathname); + dg.add_string(_cache_filename); + dg.add_uint32(_recorded_time); + + dg.add_uint16(_files.size()); + DependentFiles::const_iterator fi; + for (fi = _files.begin(); fi != _files.end(); ++fi) { + const DependentFile &file = (*fi); + dg.add_string(file._pathname); + dg.add_uint32(file._timestamp); + dg.add_uint64(file._size); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::make_from_bam +// Access: Protected, Static +// Description: This function is called by the BamReader's factory +// when a new object of type BamCacheRecord is encountered +// in the Bam file. It should create the BamCacheRecord +// and extract its information from the file. +//////////////////////////////////////////////////////////////////// +TypedWritable *BamCacheRecord:: +make_from_bam(const FactoryParams ¶ms) { + BamCacheRecord *object = new BamCacheRecord; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + object->fillin(scan, manager); + + return object; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamCacheRecord::fillin +// Access: Protected +// Description: This internal function is called by make_from_bam to +// read in all of the relevant data from the BamFile for +// the new BamCacheRecord. +//////////////////////////////////////////////////////////////////// +void BamCacheRecord:: +fillin(DatagramIterator &scan, BamReader *manager) { + TypedWritableReferenceCount::fillin(scan, manager); + + _source_pathname = scan.get_string(); + _cache_filename = scan.get_string(); + _recorded_time = scan.get_uint32(); + + unsigned int num_files = scan.get_uint16(); + _files.reserve(num_files); + for (unsigned int i = 0; i < num_files; ++i) { + _files.push_back(DependentFile()); + DependentFile &file = _files.back(); + file._pathname = scan.get_string(); + file._timestamp = scan.get_uint32(); + file._size = scan.get_uint64(); + } +} diff --git a/panda/src/putil/bamCacheRecord.h b/panda/src/putil/bamCacheRecord.h new file mode 100644 index 0000000000..5493d1ccdd --- /dev/null +++ b/panda/src/putil/bamCacheRecord.h @@ -0,0 +1,126 @@ +// Filename: bamCacheRecord.h +// Created by: drose (08Jun06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#ifndef BAMCACHERECORD_H +#define BAMCACHERECORD_H + +#include "pandabase.h" +#include "typedWritableReferenceCount.h" + +class BamWriter; +class BamReader; +class Datagram; +class DatagramIterator; +class FactoryParams; + +//////////////////////////////////////////////////////////////////// +// Class : BamCacheRecord +// Description : An instance of this class is written to the front of +// a Bam or Txo file to make the file a cached instance +// of some other loadable resource. This record +// contains information needed to test the validity of +// the cache. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA BamCacheRecord : public TypedWritableReferenceCount { +private: + BamCacheRecord(); + BamCacheRecord(const Filename &source_pathname, + const Filename &cache_filename); + +PUBLISHED: + virtual ~BamCacheRecord(); + + INLINE const Filename &get_source_pathname() const; + INLINE const Filename &get_cache_filename() const; + INLINE time_t get_recorded_time() const; + + INLINE int get_num_dependent_files() const; + INLINE const Filename &get_dependent_pathname(int n) const; + + bool dependents_unchanged() const; + void clear_dependent_files(); + void add_dependent_file(const Filename &pathname); + + INLINE bool has_data() const; + INLINE void clear_data(); + INLINE const TypedWritable *get_data() const; + INLINE TypedWritable *extract_data(); + INLINE void set_data(TypedWritable *data, bool owns_pointer); + + void output(ostream &out) const; + void write(ostream &out, int indent_level = 0) const; + +private: + static string format_timestamp(time_t timestamp); + + Filename _source_pathname; + Filename _cache_filename; + time_t _recorded_time; + + class DependentFile { + public: + Filename _pathname; + time_t _timestamp; + off_t _size; + }; + + typedef pvector DependentFiles; + DependentFiles _files; + + // The following are not recorded to disk; they are preserved + // in-memory only for the current session. + Filename _cache_pathname; + TypedWritable *_data; + bool _owns_pointer; + +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *manager, Datagram &dg); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + void fillin(DatagramIterator &scan, BamReader *manager); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedWritableReferenceCount::init_type(); + register_type(_type_handle, "BamCacheRecord", + TypedWritableReferenceCount::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; + + friend class BamCache; +}; + +INLINE ostream &operator << (ostream &out, const BamCacheRecord &record) { + record.output(out); + return out; +} + +#include "bamCacheRecord.I" + +#endif diff --git a/panda/src/putil/config_util.cxx b/panda/src/putil/config_util.cxx index fb4239b622..4b4c3f39bc 100644 --- a/panda/src/putil/config_util.cxx +++ b/panda/src/putil/config_util.cxx @@ -18,6 +18,7 @@ #include "config_util.h" #include "animInterface.h" +#include "bamCacheRecord.h" #include "bamReader.h" #include "bamReaderParam.h" #include "bitArray.h" @@ -79,6 +80,7 @@ ConfigVariableSearchPath sound_path ConfigureFn(config_util) { AnimInterface::init_type(); + BamCacheRecord::init_type(); BamReaderParam::init_type(); BitArray::init_type(); BitMask32::init_type(); @@ -104,6 +106,8 @@ ConfigureFn(config_util) { NonDeletor::register_deletor(); register_type(BamReader::_remove_flag, "remove"); + + BamCacheRecord::register_with_read_factory(); } diff --git a/panda/src/putil/putil_composite1.cxx b/panda/src/putil/putil_composite1.cxx index 45f1f9438e..dd84199266 100644 --- a/panda/src/putil/putil_composite1.cxx +++ b/panda/src/putil/putil_composite1.cxx @@ -1,4 +1,6 @@ #include "animInterface.cxx" +#include "bamCache.cxx" +#include "bamCacheRecord.cxx" #include "bamEndian.cxx" #include "bamReader.cxx" #include "bamReaderParam.cxx"