fix texture issues; introduce model-cache-dir

This commit is contained in:
David Rose 2006-06-15 00:22:35 +00:00
parent 2b1941a12b
commit 7719dd539e
17 changed files with 1595 additions and 268 deletions

View File

@ -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; i<width * height; i++) blank_image[i]=color;
GLP(TexImage2D)(target, 0, internal_format,
GLP(TexImage2D)(page_target, 0, internal_format,
width, height, 0,
external_format, GL_UNSIGNED_BYTE, blank_image);
delete blank_image;
}
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(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

View File

@ -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);

View File

@ -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);
}
////////////////////////////////////////////////////////////////////

View File

@ -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 &params) {
// 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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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";

View File

@ -34,6 +34,8 @@
#include "string_utils.h"
#include "plist.h"
#include "pvector.h"
#include "bamCache.h"
#include "bamCacheRecord.h"
#include <algorithm>
@ -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;
}
}

View File

@ -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 \

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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(&timestamp);
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 &params) {
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();
}
}

View File

@ -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<DependentFile> 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 &params);
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

View File

@ -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();
}

View File

@ -1,4 +1,6 @@
#include "animInterface.cxx"
#include "bamCache.cxx"
#include "bamCacheRecord.cxx"
#include "bamEndian.cxx"
#include "bamReader.cxx"
#include "bamReaderParam.cxx"