panda3d/panda/src/gobj/texture.cxx
2014-09-13 19:49:17 +00:00

8619 lines
293 KiB
C++

// Filename: texture.cxx
// Created by: mike (09Jan97)
// Updated by: fperazzi, PandaSE(29Apr10) (added TT_2d_texture_array)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#include "pandabase.h"
#include "texture.h"
#include "config_gobj.h"
#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"
#include "bamWriter.h"
#include "string_utils.h"
#include "preparedGraphicsObjects.h"
#include "pnmImage.h"
#include "pnmReader.h"
#include "pfmFile.h"
#include "virtualFileSystem.h"
#include "datagramInputFile.h"
#include "datagramOutputFile.h"
#include "bam.h"
#include "zStream.h"
#include "indent.h"
#include "cmath.h"
#include "pStatTimer.h"
#include "pbitops.h"
#include "streamReader.h"
#include "texturePeeker.h"
#ifdef HAVE_SQUISH
#include <squish.h>
#endif // HAVE_SQUISH
#include <stddef.h>
ConfigVariableEnum<Texture::QualityLevel> texture_quality_level
("texture-quality-level", Texture::QL_normal,
PRC_DESC("This specifies a global quality level for all textures. You "
"may specify either fastest, normal, or best. This actually "
"affects the meaning of Texture::set_quality_level(QL_default), "
"so it may be overridden on a per-texture basis. This generally "
"only has an effect when using the tinydisplay software renderer; "
"it has little or no effect on normal, hardware-accelerated "
"renderers. See Texture::set_quality_level()."));
ConfigVariableEnum<Texture::FilterType> texture_minfilter
("texture-minfilter", Texture::FT_linear,
PRC_DESC("This specifies the default minfilter that is applied to a texture "
"in the absence of a specific minfilter setting. Normally this "
"is either 'linear' to disable mipmapping by default, or "
"'mipmap', to enable trilinear mipmapping by default. This "
"does not apply to depth textures. Note if this variable is "
"changed at runtime, you may need to reload textures explicitly "
"in order to change their visible properties."));
ConfigVariableEnum<Texture::FilterType> texture_magfilter
("texture-magfilter", Texture::FT_linear,
PRC_DESC("This specifies the default magfilter that is applied to a texture "
"in the absence of a specific magfilter setting. Normally this "
"is 'linear' (since mipmapping does not apply to magfilters). This "
"does not apply to depth textures. Note if this variable is "
"changed at runtime, you may need to reload textures explicitly "
"in order to change their visible properties."));
ConfigVariableInt texture_anisotropic_degree
("texture-anisotropic-degree", 1,
PRC_DESC("This specifies the default anisotropic degree that is applied "
"to a texture in the absence of a particular anisotropic degree "
"setting (that is, a texture for which the anisotropic degree "
"is 0, meaning the default setting). It should be 1 to disable "
"anisotropic filtering, or a higher number to enable it. "
"Note if this variable is "
"changed at runtime, you may need to reload textures explicitly "
"in order to change their visible properties."));
PStatCollector Texture::_texture_read_pcollector("*:Texture:Read");
TypeHandle Texture::_type_handle;
TypeHandle Texture::CData::_type_handle;
AutoTextureScale Texture::_textures_power_2 = ATS_unspecified;
// Stuff to read and write DDS files.
// little-endian, of course
#define DDS_MAGIC 0x20534444
// DDS_header.dwFlags
#define DDSD_CAPS 0x00000001
#define DDSD_HEIGHT 0x00000002
#define DDSD_WIDTH 0x00000004
#define DDSD_PITCH 0x00000008
#define DDSD_PIXELFORMAT 0x00001000
#define DDSD_MIPMAPCOUNT 0x00020000
#define DDSD_LINEARSIZE 0x00080000
#define DDSD_DEPTH 0x00800000
// DDS_header.sPixelFormat.dwFlags
#define DDPF_ALPHAPIXELS 0x00000001
#define DDPF_FOURCC 0x00000004
#define DDPF_INDEXED 0x00000020
#define DDPF_RGB 0x00000040
// DDS_header.sCaps.dwCaps1
#define DDSCAPS_COMPLEX 0x00000008
#define DDSCAPS_TEXTURE 0x00001000
#define DDSCAPS_MIPMAP 0x00400000
// DDS_header.sCaps.dwCaps2
#define DDSCAPS2_CUBEMAP 0x00000200
#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400
#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800
#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000
#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000
#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000
#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000
#define DDSCAPS2_VOLUME 0x00200000
struct DDSPixelFormat {
unsigned int pf_size;
unsigned int pf_flags;
unsigned int four_cc;
unsigned int rgb_bitcount;
unsigned int r_mask;
unsigned int g_mask;
unsigned int b_mask;
unsigned int a_mask;
};
struct DDSCaps2 {
unsigned int caps1;
unsigned int caps2;
unsigned int ddsx;
};
struct DDSHeader {
unsigned int dds_magic;
unsigned int dds_size;
unsigned int dds_flags;
unsigned int height;
unsigned int width;
unsigned int pitch;
unsigned int depth;
unsigned int num_levels;
DDSPixelFormat pf;
DDSCaps2 caps;
};
// This table is used for converting unsigned char texture values in an sRGB
// texture to linear RGB values, for use in mipmap generation.
static float srgb_to_lrgbf[256] = {0.000000f, 0.000304f, 0.000607f, 0.000911f,
0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f,
0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f,
0.006049f, 0.006512f, 0.006995f, 0.007499f, 0.008023f, 0.008568f, 0.009134f,
0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f,
0.014444f, 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f,
0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, 0.025187f, 0.026241f,
0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f,
0.035601f, 0.036889f, 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f,
0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, 0.054480f,
0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f,
0.068478f, 0.070360f, 0.072272f, 0.074214f, 0.076185f, 0.078187f, 0.080220f,
0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f,
0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f,
0.114435f, 0.116971f, 0.119538f, 0.122139f, 0.124772f, 0.127438f, 0.130136f,
0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f,
0.152926f, 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f,
0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, 0.191202f, 0.194618f,
0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f,
0.223228f, 0.226966f, 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f,
0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, 0.274677f,
0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f,
0.309469f, 0.313989f, 0.318547f, 0.323143f, 0.327778f, 0.332452f, 0.337164f,
0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f,
0.376262f, 0.381326f, 0.386429f, 0.391572f, 0.396755f, 0.401978f, 0.407240f,
0.412543f, 0.417885f, 0.423268f, 0.428690f, 0.434154f, 0.439657f, 0.445201f,
0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473531f, 0.479320f, 0.485150f,
0.491021f, 0.496933f, 0.502886f, 0.508881f, 0.514918f, 0.520996f, 0.527115f,
0.533276f, 0.539479f, 0.545724f, 0.552011f, 0.558340f, 0.564712f, 0.571125f,
0.577580f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f,
0.623960f, 0.630757f, 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f,
0.672443f, 0.679542f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, 0.715694f,
0.723055f, 0.730461f, 0.737910f, 0.745404f, 0.752942f, 0.760525f, 0.768151f,
0.775822f, 0.783538f, 0.791298f, 0.799103f, 0.806952f, 0.814847f, 0.822786f,
0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f,
0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f,
0.947307f, 0.955973f, 0.964686f, 0.973445f, 0.982251f, 0.991102f, 1.000000f};
////////////////////////////////////////////////////////////////////
// Function: Texture::Constructor
// Access: Published
// Description: Constructs an empty texture. The default is to set
// up the texture as an empty 2-d texture; follow up
// with one of the variants of setup_texture() if this
// is not what you want.
////////////////////////////////////////////////////////////////////
Texture::
Texture(const string &name) :
Namable(name),
_lock(name),
_cvar(_lock)
{
_reloading = false;
CDWriter cdata(_cycler, true);
do_set_format(cdata, F_rgb);
do_set_component_type(cdata, T_unsigned_byte);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::Copy Constructor
// Access: Protected
// Description: Use Texture::make_copy() to make a duplicate copy of
// an existing Texture.
////////////////////////////////////////////////////////////////////
Texture::
Texture(const Texture &copy) :
Namable(copy),
_cycler(copy._cycler),
_lock(copy.get_name()),
_cvar(_lock)
{
_reloading = false;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::Copy Assignment Operator
// Access: Protected
// Description: Use Texture::make_copy() to make a duplicate copy of
// an existing Texture.
////////////////////////////////////////////////////////////////////
void Texture::
operator = (const Texture &copy) {
Namable::operator = (copy);
_cycler = copy._cycler;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::Destructor
// Access: Published, Virtual
// Description:
////////////////////////////////////////////////////////////////////
Texture::
~Texture() {
release_all();
nassertv(!_reloading);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::generate_normalization_cube_map
// Access: Published
// Description: Generates a special cube map image in the texture
// that can be used to apply bump mapping effects: for
// each texel in the cube map that is indexed by the 3-d
// texture coordinates (x, y, z), the resulting value is
// the normalized vector (x, y, z) (compressed from
// -1..1 into 0..1).
////////////////////////////////////////////////////////////////////
void Texture::
generate_normalization_cube_map(int size) {
CDWriter cdata(_cycler, true);
do_setup_texture(cdata, TT_cube_map, size, size, 6, T_unsigned_byte, F_rgb);
PTA_uchar image = do_make_ram_image(cdata);
cdata->_keep_ram_image = true;
cdata->inc_image_modified();
cdata->inc_properties_modified();
PN_stdfloat half_size = (PN_stdfloat)size * 0.5f;
PN_stdfloat center = half_size - 0.5f;
LMatrix4 scale
(127.5f, 0.0f, 0.0f, 0.0f,
0.0f, 127.5f, 0.0f, 0.0f,
0.0f, 0.0f, 127.5f, 0.0f,
127.5f, 127.5f, 127.5f, 1.0f);
unsigned char *p = image;
int xi, yi;
// Page 0: positive X.
for (yi = 0; yi < size; ++yi) {
for (xi = 0; xi < size; ++xi) {
LVector3 vec(half_size, center - yi, center - xi);
vec.normalize();
vec = scale.xform_point(vec);
*p++ = (unsigned char)vec[2];
*p++ = (unsigned char)vec[1];
*p++ = (unsigned char)vec[0];
}
}
// Page 1: negative X.
for (yi = 0; yi < size; ++yi) {
for (xi = 0; xi < size; ++xi) {
LVector3 vec(-half_size, center - yi, xi - center);
vec.normalize();
vec = scale.xform_point(vec);
*p++ = (unsigned char)vec[2];
*p++ = (unsigned char)vec[1];
*p++ = (unsigned char)vec[0];
}
}
// Page 2: positive Y.
for (yi = 0; yi < size; ++yi) {
for (xi = 0; xi < size; ++xi) {
LVector3 vec(xi - center, half_size, yi - center);
vec.normalize();
vec = scale.xform_point(vec);
*p++ = (unsigned char)vec[2];
*p++ = (unsigned char)vec[1];
*p++ = (unsigned char)vec[0];
}
}
// Page 3: negative Y.
for (yi = 0; yi < size; ++yi) {
for (xi = 0; xi < size; ++xi) {
LVector3 vec(xi - center, -half_size, center - yi);
vec.normalize();
vec = scale.xform_point(vec);
*p++ = (unsigned char)vec[2];
*p++ = (unsigned char)vec[1];
*p++ = (unsigned char)vec[0];
}
}
// Page 4: positive Z.
for (yi = 0; yi < size; ++yi) {
for (xi = 0; xi < size; ++xi) {
LVector3 vec(xi - center, center - yi, half_size);
vec.normalize();
vec = scale.xform_point(vec);
*p++ = (unsigned char)vec[2];
*p++ = (unsigned char)vec[1];
*p++ = (unsigned char)vec[0];
}
}
// Page 5: negative Z.
for (yi = 0; yi < size; ++yi) {
for (xi = 0; xi < size; ++xi) {
LVector3 vec(center - xi, center - yi, -half_size);
vec.normalize();
vec = scale.xform_point(vec);
*p++ = (unsigned char)vec[2];
*p++ = (unsigned char)vec[1];
*p++ = (unsigned char)vec[0];
}
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::generate_alpha_scale_map
// Access: Published
// Description: Generates a special 256x1 1-d texture that can be
// used to apply an arbitrary alpha scale to objects by
// judicious use of texture matrix. The texture is a
// gradient, with an alpha of 0 on the left (U = 0), and
// 255 on the right (U = 1).
////////////////////////////////////////////////////////////////////
void Texture::
generate_alpha_scale_map() {
CDWriter cdata(_cycler, true);
do_setup_texture(cdata, TT_1d_texture, 256, 1, 1, T_unsigned_byte, F_alpha);
cdata->_wrap_u = WM_clamp;
cdata->_minfilter = FT_nearest;
cdata->_magfilter = FT_nearest;
cdata->_compression = CM_off;
cdata->inc_image_modified();
cdata->inc_properties_modified();
PTA_uchar image = do_make_ram_image(cdata);
cdata->_keep_ram_image = true;
unsigned char *p = image;
for (int xi = 0; xi < 256; ++xi) {
*p++ = xi;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read
// Access: Published
// Description: Reads the named filename into the texture.
////////////////////////////////////////////////////////////////////
bool Texture::
read(const Filename &fullpath, const LoaderOptions &options) {
CDWriter cdata(_cycler, true);
do_clear(cdata);
cdata->inc_properties_modified();
cdata->inc_image_modified();
return do_read(cdata, fullpath, Filename(), 0, 0, 0, 0, false, false,
options, NULL);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read
// Access: Published
// Description: Combine a 3-component image with a grayscale image
// to get a 4-component image.
//
// See the description of the full-parameter read()
// method for the meaning of the
// primary_file_num_channels and alpha_file_channel
// parameters.
////////////////////////////////////////////////////////////////////
bool Texture::
read(const Filename &fullpath, const Filename &alpha_fullpath,
int primary_file_num_channels, int alpha_file_channel,
const LoaderOptions &options) {
CDWriter cdata(_cycler, true);
do_clear(cdata);
cdata->inc_properties_modified();
cdata->inc_image_modified();
return do_read(cdata, fullpath, alpha_fullpath, primary_file_num_channels,
alpha_file_channel, 0, 0, false, false,
options, NULL);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read
// Access: Published
// Description: Reads a single file into a single page or mipmap
// level, or automatically reads a series of files into
// a series of pages and/or mipmap levels.
//
// See the description of the full-parameter read()
// method for the meaning of the various parameters.
////////////////////////////////////////////////////////////////////
bool Texture::
read(const Filename &fullpath, int z, int n,
bool read_pages, bool read_mipmaps,
const LoaderOptions &options) {
CDWriter cdata(_cycler, true);
cdata->inc_properties_modified();
cdata->inc_image_modified();
return do_read(cdata, fullpath, Filename(), 0, 0, z, n, read_pages, read_mipmaps,
options, NULL);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read
// Access: Published
// Description: Reads the texture from the indicated filename. If
// primary_file_num_channels is not 0, it specifies the
// number of components to downgrade the image to if it
// is greater than this number.
//
// If the filename has the extension .txo, this
// implicitly reads a texture object instead of a
// filename (which replaces all of the texture
// properties). In this case, all the rest of the
// parameters are ignored, and the filename should not
// contain any hash marks; just the one named file will
// be read, since a single .txo file can contain all
// pages and mipmaps necessary to define a texture.
//
// If alpha_fullpath is not empty, it specifies the name
// of a file from which to retrieve the alpha. In this
// case, alpha_file_channel represents the numeric
// channel of this image file to use as the resulting
// texture's alpha channel; usually, this is 0 to
// indicate the grayscale combination of r, g, b; or it
// may be a one-based channel number, e.g. 1 for the red
// channel, 2 for the green channel, and so on.
//
// If read pages is false, then z indicates the page
// number into which this image will be assigned.
// Normally this is 0 for the first (or only) page of
// the texture. 3-D textures have one page for each
// level of depth, and cube map textures always have six
// pages.
//
// If read_pages is true, multiple images will be read
// at once, one for each page of a cube map or a 3-D
// texture. In this case, the filename should contain a
// sequence of one or more hash marks ("#") which will
// be filled in with the z value of each page,
// zero-based. In this case, the z parameter indicates
// the maximum z value that will be loaded, or 0 to load
// all filenames that exist.
//
// If read_mipmaps is false, then n indicates the mipmap
// level to which this image will be assigned. Normally
// this is 0 for the base texture image, but it is
// possible to load custom mipmap levels into the later
// images. After the base texture image is loaded (thus
// defining the size of the texture), you can call
// get_expected_num_mipmap_levels() to determine the
// maximum sensible value for n.
//
// If read_mipmaps is true, multiple images will be read
// as above, but this time the images represent the
// different mipmap levels of the texture image. In
// this case, the n parameter indicates the maximum n
// value that will be loaded, or 0 to load all filenames
// that exist (up to the expected number of mipmap
// levels).
//
// If both read_pages and read_mipmaps is true, then
// both sequences will be read; the filename should
// contain two sequences of hash marks, separated by
// some character such as a hyphen, underscore, or dot.
// The first hash mark sequence will be filled in with
// the mipmap level, while the second hash mark sequence
// will be the page index.
//
// This method implicitly sets keep_ram_image to false.
////////////////////////////////////////////////////////////////////
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,
BamCacheRecord *record,
const LoaderOptions &options) {
CDWriter cdata(_cycler, true);
cdata->inc_properties_modified();
cdata->inc_image_modified();
return do_read(cdata, fullpath, alpha_fullpath, primary_file_num_channels,
alpha_file_channel, z, n, read_pages, read_mipmaps,
options, record);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::estimate_texture_memory
// Access: Published
// Description: Estimates the amount of texture memory that will be
// consumed by loading this texture. This returns a
// value that is not specific to any particular graphics
// card or driver; it tries to make a reasonable
// assumption about how a driver will load the texture.
// It does not account for texture compression or
// anything fancy. This is mainly useful for debugging
// and reporting purposes.
//
// Returns a value in bytes.
////////////////////////////////////////////////////////////////////
size_t Texture::
estimate_texture_memory() const {
CDReader cdata(_cycler);
size_t pixels = cdata->_x_size * cdata->_y_size;
size_t bpp = 4;
switch (cdata->_format) {
case Texture::F_rgb332:
bpp = 1;
break;
case Texture::F_alpha:
case Texture::F_red:
case Texture::F_green:
case Texture::F_blue:
case Texture::F_luminance:
case Texture::F_luminance_alpha:
case Texture::F_luminance_alphamask:
case Texture::F_sluminance:
case Texture::F_sluminance_alpha:
bpp = 4;
break;
case Texture::F_rgba:
case Texture::F_rgba4:
case Texture::F_rgbm:
case Texture::F_rgb:
case Texture::F_rgb5:
case Texture::F_rgba5:
case Texture::F_srgb:
bpp = 4;
break;
case Texture::F_color_index:
case Texture::F_rgb8:
case Texture::F_rgba8:
case Texture::F_srgb_alpha:
bpp = 4;
break;
case Texture::F_depth_stencil:
case Texture::F_depth_component:
bpp = 32;
break;
case Texture::F_rgba12:
case Texture::F_rgb12:
bpp = 6;
break;
case Texture::F_rgba16:
bpp = 8;
break;
case Texture::F_rgba32:
bpp = 16;
break;
case Texture::F_r16:
bpp = 2;
break;
case Texture::F_rg16:
bpp = 4;
break;
case Texture::F_rgb16:
bpp = 6;
break;
case Texture::F_r32i:
bpp = 4;
break;
case Texture::F_r32:
bpp = 4;
break;
case Texture::F_rg32:
bpp = 8;
break;
case Texture::F_rgb32:
bpp = 12;
break;
default:
break;
}
size_t bytes = pixels * bpp;
if (uses_mipmaps()) {
bytes = (bytes * 4) / 3;
}
return bytes;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::set_aux_data
// Access: Published
// Description: Records an arbitrary object in the Texture,
// associated with a specified key. The object may
// later be retrieved by calling get_aux_data() with the
// same key.
//
// These data objects are not recorded to a bam or txo
// file.
////////////////////////////////////////////////////////////////////
void Texture::
set_aux_data(const string &key, TypedReferenceCount *aux_data) {
MutexHolder holder(_lock);
_aux_data[key] = aux_data;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::clear_aux_data
// Access: Published
// Description: Removes a record previously recorded via
// set_aux_data().
////////////////////////////////////////////////////////////////////
void Texture::
clear_aux_data(const string &key) {
MutexHolder holder(_lock);
_aux_data.erase(key);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::get_aux_data
// Access: Published
// Description: Returns a record previously recorded via
// set_aux_data(). Returns NULL if there was no record
// associated with the indicated key.
////////////////////////////////////////////////////////////////////
TypedReferenceCount *Texture::
get_aux_data(const string &key) const {
MutexHolder holder(_lock);
AuxData::const_iterator di;
di = _aux_data.find(key);
if (di != _aux_data.end()) {
return (*di).second;
}
return NULL;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read_txo
// Access: Published
// Description: Reads the texture from a Panda texture object. This
// defines the complete Texture specification, including
// the image data as well as all texture properties.
// This only works if the txo file contains a static
// Texture image, as opposed to a subclass of Texture
// such as a movie texture.
//
// Pass a real filename if it is available, or empty
// string if it is not.
////////////////////////////////////////////////////////////////////
bool Texture::
read_txo(istream &in, const string &filename) {
CDWriter cdata(_cycler, true);
cdata->inc_properties_modified();
cdata->inc_image_modified();
return do_read_txo(cdata, in, filename);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::make_from_txo
// Access: Published, Static
// Description: Constructs a new Texture object from the txo file.
// This is similar to Texture::read_txo(), but it
// constructs and returns a new object, which allows it
// to return a subclass of Texture (for instance, a
// movie texture).
//
// Pass a real filename if it is available, or empty
// string if it is not.
////////////////////////////////////////////////////////////////////
PT(Texture) Texture::
make_from_txo(istream &in, const string &filename) {
DatagramInputFile din;
if (!din.open(in, filename)) {
gobj_cat.error()
<< "Could not read texture object: " << filename << "\n";
return NULL;
}
string head;
if (!din.read_header(head, _bam_header.size())) {
gobj_cat.error()
<< filename << " is not a texture object file.\n";
return NULL;
}
if (head != _bam_header) {
gobj_cat.error()
<< filename << " is not a texture object file.\n";
return NULL;
}
BamReader reader(&din);
if (!reader.init()) {
return NULL;
}
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";
return NULL;
} else if (!object->is_of_type(Texture::get_class_type())) {
gobj_cat.error()
<< "Texture object " << filename << " contains a "
<< object->get_type() << ", not a Texture.\n";
return NULL;
}
PT(Texture) other = DCAST(Texture, object);
if (!reader.resolve()) {
gobj_cat.error()
<< "Unable to fully resolve texture object file.\n";
return NULL;
}
return other;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::write_txo
// Access: Published
// Description: Writes the texture to a Panda texture object. This
// defines the complete Texture specification, including
// the image data as well as all texture properties.
//
// The filename is just for reference.
////////////////////////////////////////////////////////////////////
bool Texture::
write_txo(ostream &out, const string &filename) const {
CDReader cdata(_cycler);
return do_write_txo(cdata, out, filename);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read_dds
// Access: Published
// Description: Reads the texture from a DDS file object. This is a
// Microsoft-defined file format; it is similar in
// principle to a txo object, in that it is designed to
// contain the texture image in a form as similar as
// possible to its runtime image, and it can contain
// mipmaps, pre-compressed textures, and so on.
//
// As with read_txo, the filename is just for reference.
////////////////////////////////////////////////////////////////////
bool Texture::
read_dds(istream &in, const string &filename, bool header_only) {
CDWriter cdata(_cycler, true);
cdata->inc_properties_modified();
cdata->inc_image_modified();
return do_read_dds(cdata, in, filename, header_only);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::load_related
// Access: Published
// Description: Loads a texture whose filename is derived by
// concatenating a suffix to the filename of this
// texture. May return NULL, for example, if this
// texture doesn't have a filename.
////////////////////////////////////////////////////////////////////
Texture *Texture::
load_related(const InternalName *suffix) const {
MutexHolder holder(_lock);
CDReader cdata(_cycler);
RelatedTextures::const_iterator ti;
ti = _related_textures.find(suffix);
if (ti != _related_textures.end()) {
return (*ti).second;
}
if (cdata->_fullpath.empty()) {
return (Texture*)NULL;
}
Filename main = cdata->_fullpath;
main.set_basename_wo_extension(main.get_basename_wo_extension() +
suffix->get_name());
PT(Texture) res;
if (!cdata->_alpha_fullpath.empty()) {
Filename alph = cdata->_alpha_fullpath;
alph.set_basename_wo_extension(alph.get_basename_wo_extension() +
suffix->get_name());
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
if (vfs->exists(alph)) {
// The alpha variant of the filename, with the suffix, exists.
// Use it to load the texture.
res = TexturePool::load_texture(main, alph,
cdata->_primary_file_num_channels,
cdata->_alpha_file_channel, false);
} else {
// If the alpha variant of the filename doesn't exist, just go
// ahead and load the related texture without alpha.
res = TexturePool::load_texture(main);
}
} else {
// No alpha filename--just load the single file. It doesn't
// necessarily have the same number of channels as this one.
res = TexturePool::load_texture(main);
}
// I'm casting away the const-ness of 'this' because this
// field is only a cache.
((Texture *)this)->_related_textures.insert(RelatedTextures::value_type(suffix, res));
return res;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::get_effective_minfilter
// Access: Published
// Description: Returns the filter mode of the texture for
// minification, with special treatment for FT_default.
// This will normally not return FT_default, unless
// there is an error in the config file.
////////////////////////////////////////////////////////////////////
Texture::FilterType Texture::
get_effective_minfilter() const {
CDReader cdata(_cycler);
if (cdata->_minfilter != FT_default) {
return cdata->_minfilter;
}
if (cdata->_format == Texture::F_depth_stencil ||
cdata->_format == Texture::F_depth_component) {
return FT_nearest;
}
return texture_minfilter;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::get_effective_magfilter
// Access: Published
// Description: Returns the filter mode of the texture for
// magnification, with special treatment for FT_default.
// This will normally not return FT_default, unless
// there is an error in the config file.
////////////////////////////////////////////////////////////////////
Texture::FilterType Texture::
get_effective_magfilter() const {
CDReader cdata(_cycler);
if (cdata->_magfilter != FT_default) {
return cdata->_magfilter;
}
if (cdata->_format == Texture::F_depth_stencil ||
cdata->_format == Texture::F_depth_component) {
return FT_nearest;
}
return texture_magfilter;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::set_ram_image_as
// Access: Published
// Description: Replaces the current system-RAM image with the new
// data, converting it first if necessary from the
// indicated component-order format. See
// get_ram_image_as() for specifications about the
// format. This method cannot support compressed image
// data or sub-pages; use set_ram_image() for that.
////////////////////////////////////////////////////////////////////
void Texture::
set_ram_image_as(CPTA_uchar image, const string &supplied_format) {
CDWriter cdata(_cycler, true);
string format = upcase(supplied_format);
// Make sure we can grab something that's uncompressed.
int imgsize = cdata->_x_size * cdata->_y_size;
nassertv(image.size() == (size_t)(cdata->_component_width * format.size() * imgsize));
// Check if the format is already what we have internally.
if ((cdata->_num_components == 1 && format.size() == 1) ||
(cdata->_num_components == 2 && format.size() == 2 && format.at(1) == 'A' && format.at(0) != 'A') ||
(cdata->_num_components == 3 && format == "BGR") ||
(cdata->_num_components == 4 && format == "BGRA")) {
// The format string is already our format, so we just need to copy it.
do_set_ram_image(cdata, image);
return;
}
// Create a new empty array that can hold our image.
PTA_uchar newdata = PTA_uchar::empty_array(imgsize * cdata->_num_components * cdata->_component_width, get_class_type());
// These ifs are for optimization of commonly used image types.
if (cdata->_component_width == 1) {
if (format == "RGBA" && cdata->_num_components == 4) {
imgsize *= 4;
for (int p = 0; p < imgsize; p += 4) {
newdata[p + 2] = image[p ];
newdata[p + 1] = image[p + 1];
newdata[p ] = image[p + 2];
newdata[p + 3] = image[p + 3];
}
do_set_ram_image(cdata, newdata);
return;
}
if (format == "RGB" && cdata->_num_components == 3) {
imgsize *= 3;
for (int p = 0; p < imgsize; p += 3) {
newdata[p + 2] = image[p ];
newdata[p + 1] = image[p + 1];
newdata[p ] = image[p + 2];
}
do_set_ram_image(cdata, newdata);
return;
}
if (format == "A" && cdata->_num_components != 3) {
// We can generally rely on alpha to be the last component.
int component = cdata->_num_components - 1;
for (int p = 0; p < imgsize; ++p) {
newdata[component] = image[p];
}
do_set_ram_image(cdata, newdata);
return;
}
for (int p = 0; p < imgsize; ++p) {
for (uchar s = 0; s < format.size(); ++s) {
signed char component = -1;
if (format.at(s) == 'B' || (cdata->_num_components <= 2 && format.at(s) != 'A')) {
component = 0;
} else if (format.at(s) == 'G') {
component = 1;
} else if (format.at(s) == 'R') {
component = 2;
} else if (format.at(s) == 'A') {
nassertv(cdata->_num_components != 3);
component = cdata->_num_components - 1;
} else if (format.at(s) == '0') {
// Ignore.
} else if (format.at(s) == '1') {
// Ignore.
} else {
gobj_cat.error() << "Unexpected component character '"
<< format.at(s) << "', expected one of RGBA!\n";
return;
}
if (component >= 0) {
newdata[p * cdata->_num_components + component] = image[p * format.size() + s];
}
}
}
do_set_ram_image(cdata, newdata);
return;
}
for (int p = 0; p < imgsize; ++p) {
for (uchar s = 0; s < format.size(); ++s) {
signed char component = -1;
if (format.at(s) == 'B' || (cdata->_num_components <= 2 && format.at(s) != 'A')) {
component = 0;
} else if (format.at(s) == 'G') {
component = 1;
} else if (format.at(s) == 'R') {
component = 2;
} else if (format.at(s) == 'A') {
nassertv(cdata->_num_components != 3);
component = cdata->_num_components - 1;
} else if (format.at(s) == '0') {
// Ignore.
} else if (format.at(s) == '1') {
// Ignore.
} else {
gobj_cat.error() << "Unexpected component character '"
<< format.at(s) << "', expected one of RGBA!\n";
return;
}
if (component >= 0) {
memcpy((void*)(newdata + (p * cdata->_num_components + component) * cdata->_component_width),
(void*)(image + (p * format.size() + s) * cdata->_component_width),
cdata->_component_width);
}
}
}
do_set_ram_image(cdata, newdata);
return;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::get_keep_ram_image
// Access: Published, Virtual
// Description: Returns the flag that indicates whether this Texture
// is eligible to have its main RAM copy of the texture
// memory dumped when the texture is prepared for
// rendering. See set_keep_ram_image().
////////////////////////////////////////////////////////////////////
bool Texture::
get_keep_ram_image() const {
CDReader cdata(_cycler);
return cdata->_keep_ram_image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::is_cacheable
// Access: Published, Virtual
// Description: Returns true if there is enough information in this
// Texture object to write it to the bam cache
// successfully, false otherwise. For most textures,
// this is the same as has_ram_image().
////////////////////////////////////////////////////////////////////
bool Texture::
is_cacheable() const {
CDReader cdata(_cycler);
return do_has_bam_rawdata(cdata);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::get_num_loadable_ram_mipmap_images
// Access: Published
// Description: Returns the number of contiguous mipmap levels that
// exist in RAM, up until the first gap in the sequence.
// It is guaranteed that at least mipmap levels [0,
// get_num_ram_mipmap_images()) exist.
//
// The number returned will never exceed the number of
// required mipmap images based on the size of the
// texture and its filter mode.
//
// This method is different from
// get_num_ram_mipmap_images() in that it returns only
// the number of mipmap levels that can actually be
// usefully loaded, regardless of the actual number that
// may be stored.
////////////////////////////////////////////////////////////////////
int Texture::
get_num_loadable_ram_mipmap_images() const {
CDReader cdata(_cycler);
if (cdata->_ram_images.empty() || cdata->_ram_images[0]._image.empty()) {
// If we don't even have a base image, the answer is none.
return 0;
}
if (!uses_mipmaps()) {
// If we have a base image and don't require mipmapping, the
// answer is 1.
return 1;
}
// Check that we have enough mipmap levels to meet the size
// requirements.
int size = max(cdata->_x_size, max(cdata->_y_size, cdata->_z_size));
int n = 0;
int x = 1;
while (x < size) {
x = (x << 1);
++n;
if (n >= (int)cdata->_ram_images.size() || cdata->_ram_images[n]._image.empty()) {
return n;
}
}
++n;
return n;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::get_ram_mipmap_image
// Access: Published
// Description: Returns the system-RAM image data associated with the
// nth mipmap level, if present. Returns NULL if the
// nth mipmap level is not present.
////////////////////////////////////////////////////////////////////
CPTA_uchar Texture::
get_ram_mipmap_image(int n) const {
CDReader cdata(_cycler);
if (n < (int)cdata->_ram_images.size() && !cdata->_ram_images[n]._image.empty()) {
return cdata->_ram_images[n]._image;
}
return CPTA_uchar(get_class_type());
}
////////////////////////////////////////////////////////////////////
// Function: Texture::get_ram_mipmap_pointer
// Access: Published
// Description: Similiar to get_ram_mipmap_image(), however, in this
// case the void pointer for the given ram image is
// returned. This will be NULL unless it has been
// explicitly set.
////////////////////////////////////////////////////////////////////
void *Texture::
get_ram_mipmap_pointer(int n) const {
CDReader cdata(_cycler);
if (n < (int)cdata->_ram_images.size()) {
return cdata->_ram_images[n]._pointer_image;
}
return NULL;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::set_ram_mipmap_pointer
// Access: Published
// Description: Sets an explicit void pointer as the texture's mipmap
// image for the indicated level. This is a special
// call to direct a texture to reference some external
// image location, for instance from a webcam input.
//
// The texture will henceforth reference this pointer
// directly, instead of its own internal storage; the
// user is responsible for ensuring the data at this
// address remains allocated and valid, and in the
// correct format, during the lifetime of the texture.
////////////////////////////////////////////////////////////////////
void Texture::
set_ram_mipmap_pointer(int n, void *image, size_t page_size) {
CDWriter cdata(_cycler, true);
nassertv(cdata->_ram_image_compression != CM_off || do_get_expected_ram_mipmap_image_size(cdata, n));
while (n >= (int)cdata->_ram_images.size()) {
cdata->_ram_images.push_back(RamImage());
}
cdata->_ram_images[n]._page_size = page_size;
//_ram_images[n]._image.clear(); wtf is going on?!
cdata->_ram_images[n]._pointer_image = image;
cdata->inc_image_modified();
}
////////////////////////////////////////////////////////////////////
// Function: Texture::set_ram_mipmap_pointer_from_int
// Access: Published
// Description: Accepts a raw pointer cast as an int, which is then
// passed to set_ram_mipmap_pointer(); see the
// documentation for that method.
//
// This variant is particularly useful to set an
// external pointer from a language like Python, which
// doesn't support void pointers directly.
////////////////////////////////////////////////////////////////////
void Texture::
set_ram_mipmap_pointer_from_int(long long pointer, int n, int page_size) {
set_ram_mipmap_pointer(n, (void*)pointer, (size_t)page_size);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::clear_ram_mipmap_image
// Access: Published
// Description: Discards the current system-RAM image for the nth
// mipmap level.
////////////////////////////////////////////////////////////////////
void Texture::
clear_ram_mipmap_image(int n) {
CDWriter cdata(_cycler, true);
if (n >= (int)cdata->_ram_images.size()) {
return;
}
cdata->_ram_images[n]._page_size = 0;
cdata->_ram_images[n]._image.clear();
cdata->_ram_images[n]._pointer_image = NULL;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::modify_simple_ram_image
// Access: Published
// Description: Returns a modifiable pointer to the internal "simple"
// texture image. See set_simple_ram_image().
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
modify_simple_ram_image() {
CDWriter cdata(_cycler, true);
cdata->_simple_image_date_generated = (PN_int32)time(NULL);
return cdata->_simple_ram_image._image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::new_simple_ram_image
// Access: Published
// Description: Creates an empty array for the simple ram image of
// the indicated size, and returns a modifiable pointer
// to the new array. See set_simple_ram_image().
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
new_simple_ram_image(int x_size, int y_size) {
CDWriter cdata(_cycler, true);
nassertr(cdata->_texture_type == TT_2d_texture, PTA_uchar());
size_t expected_page_size = (size_t)(x_size * y_size * 4);
cdata->_simple_x_size = x_size;
cdata->_simple_y_size = y_size;
cdata->_simple_ram_image._image = PTA_uchar::empty_array(expected_page_size);
cdata->_simple_ram_image._page_size = expected_page_size;
cdata->_simple_image_date_generated = (PN_int32)time(NULL);
cdata->inc_simple_image_modified();
return cdata->_simple_ram_image._image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::generate_simple_ram_image
// Access: Published
// Description: Computes the "simple" ram image by loading the main
// RAM image, if it is not already available, and
// reducing it to 16x16 or smaller. This may be an
// expensive operation.
////////////////////////////////////////////////////////////////////
void Texture::
generate_simple_ram_image() {
CDWriter cdata(_cycler, true);
if (cdata->_texture_type != TT_2d_texture ||
cdata->_ram_image_compression != CM_off) {
return;
}
PNMImage pnmimage;
if (!do_store_one(cdata, pnmimage, 0, 0)) {
return;
}
// Start at the suggested size from the config file.
int x_size = simple_image_size.get_word(0);
int y_size = simple_image_size.get_word(1);
// Limit it to no larger than the source image, and also make it a
// power of two.
x_size = down_to_power_2(min(x_size, cdata->_x_size));
y_size = down_to_power_2(min(y_size, cdata->_y_size));
// Generate a reduced image of that size.
PNMImage scaled(x_size, y_size, pnmimage.get_num_channels());
scaled.quick_filter_from(pnmimage);
// Make sure the reduced image has 4 components, by convention.
if (!scaled.has_alpha()) {
scaled.add_alpha();
scaled.alpha_fill(1.0);
}
scaled.set_num_channels(4);
// Now see if we can go even smaller.
bool did_anything;
do {
did_anything = false;
// Try to reduce X.
if (x_size > 1) {
int new_x_size = (x_size >> 1);
PNMImage smaller(new_x_size, y_size, 4);
smaller.quick_filter_from(scaled);
PNMImage bigger(x_size, y_size, 4);
bigger.quick_filter_from(smaller);
if (compare_images(scaled, bigger)) {
scaled.take_from(smaller);
x_size = new_x_size;
did_anything = true;
}
}
// Try to reduce Y.
if (y_size > 1) {
int new_y_size = (y_size >> 1);
PNMImage smaller(x_size, new_y_size, 4);
smaller.quick_filter_from(scaled);
PNMImage bigger(x_size, y_size, 4);
bigger.quick_filter_from(smaller);
if (compare_images(scaled, bigger)) {
scaled.take_from(smaller);
y_size = new_y_size;
did_anything = true;
}
}
} while (did_anything);
size_t expected_page_size = (size_t)(x_size * y_size * 4);
PTA_uchar image = PTA_uchar::empty_array(expected_page_size, get_class_type());
convert_from_pnmimage(image, expected_page_size, x_size, 0, 0, 0, scaled, 4, 1);
do_set_simple_ram_image(cdata, image, x_size, y_size);
cdata->_simple_image_date_generated = (PN_int32)time(NULL);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::peek
// Access: Published
// Description: Returns a TexturePeeker object that can be used to
// examine the individual texels stored within this
// Texture by (u, v) coordinate.
//
// If the texture has a ram image resident, that image
// is used. If it does not have a full ram image but
// does have a simple_ram_image resident, that image is
// used instead. If neither image is resident the full
// image is reloaded.
//
// Returns NULL if the texture cannot find an image to
// load, or the texture format is incompatible.
////////////////////////////////////////////////////////////////////
PT(TexturePeeker) Texture::
peek() {
CDWriter cdata(_cycler, unlocked_ensure_ram_image(true));
PT(TexturePeeker) peeker = new TexturePeeker(this, cdata);
if (peeker->is_valid()) {
return peeker;
}
return NULL;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::prepare
// Access: Published
// Description: Indicates that the texture should be enqueued to be
// prepared in the indicated prepared_objects at the
// beginning of the next frame. This will ensure the
// texture is already loaded into texture memory if it
// is expected to be rendered soon.
//
// Use this function instead of prepare_now() to preload
// textures from a user interface standpoint.
////////////////////////////////////////////////////////////////////
void Texture::
prepare(PreparedGraphicsObjects *prepared_objects) {
prepared_objects->enqueue_texture(this);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::is_prepared
// Access: Published
// Description: Returns true if the texture has already been prepared
// or enqueued for preparation on the indicated GSG,
// false otherwise.
////////////////////////////////////////////////////////////////////
bool Texture::
is_prepared(PreparedGraphicsObjects *prepared_objects) const {
MutexHolder holder(_lock);
PreparedViews::const_iterator pvi;
pvi = _prepared_views.find(prepared_objects);
if (pvi != _prepared_views.end()) {
return true;
}
return prepared_objects->is_texture_queued(this);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::was_image_modified
// Access: Published
// Description: Returns true if the texture needs to be re-loaded
// onto the indicated GSG, either because its image data
// is out-of-date, or because it's not fully prepared
// now.
////////////////////////////////////////////////////////////////////
bool Texture::
was_image_modified(PreparedGraphicsObjects *prepared_objects) const {
MutexHolder holder(_lock);
CDReader cdata(_cycler);
PreparedViews::const_iterator pvi;
pvi = _prepared_views.find(prepared_objects);
if (pvi != _prepared_views.end()) {
const Contexts &contexts = (*pvi).second;
for (int view = 0; view < cdata->_num_views; ++view) {
Contexts::const_iterator ci;
ci = contexts.find(view);
if (ci == contexts.end()) {
return true;
}
TextureContext *tc = (*ci).second;
if (tc->was_image_modified()) {
return true;
}
}
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::get_data_size_bytes
// Access: Public
// Description: Returns the number of bytes which the texture is
// reported to consume within graphics memory, for the
// indicated GSG. This may return a nonzero value even
// if the texture is not currently resident; you should
// also check get_resident() if you want to know how
// much space the texture is actually consuming right
// now.
////////////////////////////////////////////////////////////////////
size_t Texture::
get_data_size_bytes(PreparedGraphicsObjects *prepared_objects) const {
MutexHolder holder(_lock);
CDReader cdata(_cycler);
PreparedViews::const_iterator pvi;
size_t total_size = 0;
pvi = _prepared_views.find(prepared_objects);
if (pvi != _prepared_views.end()) {
const Contexts &contexts = (*pvi).second;
for (int view = 0; view < cdata->_num_views; ++view) {
Contexts::const_iterator ci;
ci = contexts.find(view);
if (ci != contexts.end()) {
TextureContext *tc = (*ci).second;
total_size += tc->get_data_size_bytes();
}
}
}
return total_size;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::get_active
// Access: Public
// Description: Returns true if this Texture was rendered in the most
// recent frame within the indicated GSG.
////////////////////////////////////////////////////////////////////
bool Texture::
get_active(PreparedGraphicsObjects *prepared_objects) const {
MutexHolder holder(_lock);
CDReader cdata(_cycler);
PreparedViews::const_iterator pvi;
pvi = _prepared_views.find(prepared_objects);
if (pvi != _prepared_views.end()) {
const Contexts &contexts = (*pvi).second;
for (int view = 0; view < cdata->_num_views; ++view) {
Contexts::const_iterator ci;
ci = contexts.find(view);
if (ci != contexts.end()) {
TextureContext *tc = (*ci).second;
if (tc->get_active()) {
return true;
}
}
}
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::get_resident
// Access: Public
// Description: Returns true if this Texture is reported to be
// resident within graphics memory for the indicated
// GSG.
////////////////////////////////////////////////////////////////////
bool Texture::
get_resident(PreparedGraphicsObjects *prepared_objects) const {
MutexHolder holder(_lock);
CDReader cdata(_cycler);
PreparedViews::const_iterator pvi;
pvi = _prepared_views.find(prepared_objects);
if (pvi != _prepared_views.end()) {
const Contexts &contexts = (*pvi).second;
for (int view = 0; view < cdata->_num_views; ++view) {
Contexts::const_iterator ci;
ci = contexts.find(view);
if (ci != contexts.end()) {
TextureContext *tc = (*ci).second;
if (tc->get_resident()) {
return true;
}
}
}
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::release
// Access: Published
// Description: Frees the texture context only on the indicated object,
// if it exists there. Returns true if it was released,
// false if it had not been prepared.
////////////////////////////////////////////////////////////////////
bool Texture::
release(PreparedGraphicsObjects *prepared_objects) {
MutexHolder holder(_lock);
PreparedViews::iterator pvi;
pvi = _prepared_views.find(prepared_objects);
if (pvi != _prepared_views.end()) {
Contexts temp;
temp.swap((*pvi).second);
Contexts::iterator ci;
for (ci = temp.begin(); ci != temp.end(); ++ci) {
TextureContext *tc = (*ci).second;
if (tc != (TextureContext *)NULL) {
prepared_objects->release_texture(tc);
}
}
_prepared_views.erase(pvi);
}
// Maybe it wasn't prepared yet, but it's about to be.
return prepared_objects->dequeue_texture(this);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::release_all
// Access: Published
// Description: Frees the context allocated on all objects for which
// the texture has been declared. Returns the number of
// contexts which have been freed.
////////////////////////////////////////////////////////////////////
int Texture::
release_all() {
MutexHolder holder(_lock);
// We have to traverse a copy of the _prepared_views list, because the
// PreparedGraphicsObjects object will call clear_prepared() in response
// to each release_texture(), and we don't want to be modifying the
// _prepared_views list while we're traversing it.
PreparedViews temp;
temp.swap(_prepared_views);
int num_freed = (int)temp.size();
PreparedViews::iterator pvi;
for (pvi = temp.begin(); pvi != temp.end(); ++pvi) {
PreparedGraphicsObjects *prepared_objects = (*pvi).first;
Contexts temp;
temp.swap((*pvi).second);
Contexts::iterator ci;
for (ci = temp.begin(); ci != temp.end(); ++ci) {
TextureContext *tc = (*ci).second;
if (tc != (TextureContext *)NULL) {
prepared_objects->release_texture(tc);
}
}
}
return num_freed;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::write
// Access: Published
// Description: Not to be confused with write(Filename), this method
// simply describes the texture properties.
////////////////////////////////////////////////////////////////////
void Texture::
write(ostream &out, int indent_level) const {
CDReader cdata(_cycler);
indent(out, indent_level)
<< cdata->_texture_type << " " << get_name();
if (!cdata->_filename.empty()) {
out << " (from " << cdata->_filename << ")";
}
out << "\n";
indent(out, indent_level + 2);
switch (cdata->_texture_type) {
case TT_1d_texture:
out << "1-d, " << cdata->_x_size;
break;
case TT_2d_texture:
out << "2-d, " << cdata->_x_size << " x " << cdata->_y_size;
break;
case TT_3d_texture:
out << "3-d, " << cdata->_x_size << " x " << cdata->_y_size << " x " << cdata->_z_size;
break;
case TT_2d_texture_array:
out << "2-d array, " << cdata->_x_size << " x " << cdata->_y_size << " x " << cdata->_z_size;
break;
case TT_cube_map:
out << "cube map, " << cdata->_x_size << " x " << cdata->_y_size;
break;
}
if (cdata->_num_views > 1) {
out << " (x " << cdata->_num_views << " views)";
}
out << " pixels, each " << cdata->_num_components;
switch (cdata->_component_type) {
case T_unsigned_byte:
out << " bytes";
break;
case T_unsigned_short:
out << " shorts";
break;
case T_float:
out << " floats";
break;
case T_unsigned_int_24_8:
case T_int:
out << " ints";
break;
default:
break;
}
out << ", ";
switch (cdata->_format) {
case F_color_index:
out << "color_index";
break;
case F_depth_stencil:
out << "depth_stencil";
break;
case F_depth_component:
out << "depth_component";
break;
case F_depth_component16:
out << "depth_component16";
break;
case F_depth_component24:
out << "depth_component24";
break;
case F_depth_component32:
out << "depth_component32";
break;
case F_rgba:
out << "rgba";
break;
case F_rgbm:
out << "rgbm";
break;
case F_rgba32:
out << "rgba32";
break;
case F_rgba16:
out << "rgba16";
break;
case F_rgba12:
out << "rgba12";
break;
case F_rgba8:
out << "rgba8";
break;
case F_rgba4:
out << "rgba4";
break;
case F_rgb:
out << "rgb";
break;
case F_rgb12:
out << "rgb12";
break;
case F_rgb8:
out << "rgb8";
break;
case F_rgb5:
out << "rgb5";
break;
case F_rgba5:
out << "rgba5";
break;
case F_rgb332:
out << "rgb332";
break;
case F_red:
out << "red";
break;
case F_green:
out << "green";
break;
case F_blue:
out << "blue";
break;
case F_alpha:
out << "alpha";
break;
case F_luminance:
out << "luminance";
break;
case F_luminance_alpha:
out << "luminance_alpha";
break;
case F_luminance_alphamask:
out << "luminance_alphamask";
break;
case F_r16:
out << "r16";
break;
case F_rg16:
out << "rg16";
break;
case F_rgb16:
out << "rgb16";
break;
case F_srgb:
out << "srgb";
break;
case F_srgb_alpha:
out << "srgb_alpha";
break;
case F_sluminance:
out << "sluminance";
break;
case F_sluminance_alpha:
out << "sluminance_alpha";
break;
case F_r32i:
out << "r32i";
break;
case F_r32:
out << "r32";
break;
case F_rg32:
out << "rg32";
break;
case F_rgb32:
out << "rgb32";
break;
}
if (cdata->_compression != CM_default) {
out << ", compression " << cdata->_compression;
}
out << "\n";
indent(out, indent_level + 2);
switch (cdata->_texture_type) {
case TT_1d_texture:
out << cdata->_wrap_u << ", ";
break;
case TT_2d_texture:
out << cdata->_wrap_u << " x " << cdata->_wrap_v << ", ";
break;
case TT_3d_texture:
out << cdata->_wrap_u << " x " << cdata->_wrap_v << " x " << cdata->_wrap_w << ", ";
break;
case TT_2d_texture_array:
out << cdata->_wrap_u << " x " << cdata->_wrap_v << " x " << cdata->_wrap_w << ", ";
break;
case TT_cube_map:
break;
}
out << "min " << cdata->_minfilter
<< ", mag " << cdata->_magfilter
<< ", aniso " << cdata->_anisotropic_degree
<< ", border " << cdata->_border_color
<< "\n";
if (do_has_ram_image(cdata)) {
indent(out, indent_level + 2)
<< do_get_ram_image_size(cdata) << " bytes in ram, compression "
<< cdata->_ram_image_compression << "\n";
if (cdata->_ram_images.size() > 1) {
int count = 0;
size_t total_size = 0;
for (size_t n = 1; n < cdata->_ram_images.size(); ++n) {
if (!cdata->_ram_images[n]._image.empty()) {
++count;
total_size += cdata->_ram_images[n]._image.size();
} else {
// Stop at the first gap.
break;
}
}
indent(out, indent_level + 2)
<< count
<< " mipmap levels also present in ram (" << total_size
<< " bytes).\n";
}
} else {
indent(out, indent_level + 2)
<< "no ram image\n";
}
if (!cdata->_simple_ram_image._image.empty()) {
indent(out, indent_level + 2)
<< "simple image: " << cdata->_simple_x_size << " x "
<< cdata->_simple_y_size << ", "
<< cdata->_simple_ram_image._image.size() << " bytes\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::set_size_padded
// Access: Published
// Description: Changes the size of the texture, padding
// if necessary, and setting the pad region
// as well.
////////////////////////////////////////////////////////////////////
void Texture::
set_size_padded(int x, int y, int z) {
CDWriter cdata(_cycler, true);
if (do_get_auto_texture_scale(cdata) != ATS_none) {
do_set_x_size(cdata, up_to_power_2(x));
do_set_y_size(cdata, up_to_power_2(y));
if (cdata->_texture_type == TT_3d_texture) {
// Only pad 3D textures. It does not make sense
// to do so for cube maps or 2D texture arrays.
do_set_z_size(cdata, up_to_power_2(z));
} else {
do_set_z_size(cdata, z);
}
} else {
do_set_x_size(cdata, x);
do_set_y_size(cdata, y);
do_set_z_size(cdata, z);
}
do_set_pad_size(cdata,
cdata->_x_size - x,
cdata->_y_size - y,
cdata->_z_size - z);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::set_orig_file_size
// Access: Published
// Description: Specifies the size of the texture as it exists in its
// original disk file, before any Panda scaling.
////////////////////////////////////////////////////////////////////
void Texture::
set_orig_file_size(int x, int y, int z) {
CDWriter cdata(_cycler, true);
cdata->_orig_file_x_size = x;
cdata->_orig_file_y_size = y;
nassertv(z == cdata->_z_size);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::is_mipmap
// Access: Published, Static
// Description: Returns true if the indicated filter type requires
// the use of mipmaps, or false if it does not.
////////////////////////////////////////////////////////////////////
bool Texture::
is_mipmap(FilterType filter_type) {
switch (filter_type) {
case FT_nearest_mipmap_nearest:
case FT_linear_mipmap_nearest:
case FT_nearest_mipmap_linear:
case FT_linear_mipmap_linear:
return true;
default:
return false;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::prepare_now
// Access: Published
// Description: Creates a context for the texture on the particular
// GSG, if it does not already exist. Returns the new
// (or old) TextureContext. This assumes that the
// GraphicsStateGuardian is the currently active
// rendering context and that it is ready to accept new
// textures. If this is not necessarily the case, you
// should use prepare() instead.
//
// Normally, this is not called directly except by the
// GraphicsStateGuardian; a texture does not need to be
// explicitly prepared by the user before it may be
// rendered.
////////////////////////////////////////////////////////////////////
TextureContext *Texture::
prepare_now(int view,
PreparedGraphicsObjects *prepared_objects,
GraphicsStateGuardianBase *gsg) {
MutexHolder holder(_lock);
CDReader cdata(_cycler);
// Don't exceed the actual number of views.
view = max(min(view, cdata->_num_views - 1), 0);
// Get the list of PreparedGraphicsObjects for this view.
Contexts &contexts = _prepared_views[prepared_objects];
Contexts::const_iterator pvi;
pvi = contexts.find(view);
if (pvi != contexts.end()) {
return (*pvi).second;
}
TextureContext *tc = prepared_objects->prepare_texture_now(this, view, gsg);
contexts[view] = tc;
return tc;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::up_to_power_2
// Access: Published, Static
// Description: Returns the smallest power of 2 greater than or equal
// to value.
////////////////////////////////////////////////////////////////////
int Texture::
up_to_power_2(int value) {
if (value <= 1) {
return 1;
}
int bit = get_next_higher_bit(((unsigned int)value) - 1);
return (1 << bit);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::down_to_power_2
// Access: Published, Static
// Description: Returns the largest power of 2 less than or equal
// to value.
////////////////////////////////////////////////////////////////////
int Texture::
down_to_power_2(int value) {
if (value <= 1) {
return 1;
}
int bit = get_next_higher_bit(((unsigned int)value) >> 1);
return (1 << bit);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::consider_rescale
// Access: Published
// Description: Asks the PNMImage to change its scale when it reads
// the image, according to the whims of the Config.prc
// file.
//
// For most efficient results, this method should be
// called after pnmimage.read_header() has been called,
// but before pnmimage.read(). This method may also be
// called after pnmimage.read(), i.e. when the pnmimage
// is already loaded; in this case it will rescale the
// image on the spot. Also see rescale_texture().
////////////////////////////////////////////////////////////////////
void Texture::
consider_rescale(PNMImage &pnmimage) {
consider_rescale(pnmimage, get_name(), get_auto_texture_scale());
}
////////////////////////////////////////////////////////////////////
// Function: Texture::consider_rescale
// Access: Published, Static
// Description: Asks the PNMImage to change its scale when it reads
// the image, according to the whims of the Config.prc
// file.
//
// For most efficient results, this method should be
// called after pnmimage.read_header() has been called,
// but before pnmimage.read(). This method may also be
// called after pnmimage.read(), i.e. when the pnmimage
// is already loaded; in this case it will rescale the
// image on the spot. Also see rescale_texture().
////////////////////////////////////////////////////////////////////
void Texture::
consider_rescale(PNMImage &pnmimage, const string &name, AutoTextureScale auto_texture_scale) {
int new_x_size = pnmimage.get_x_size();
int new_y_size = pnmimage.get_y_size();
if (adjust_size(new_x_size, new_y_size, name, false, auto_texture_scale)) {
if (pnmimage.is_valid()) {
// The image is already loaded. Rescale on the spot.
PNMImage new_image(new_x_size, new_y_size, pnmimage.get_num_channels(),
pnmimage.get_maxval());
new_image.quick_filter_from(pnmimage);
pnmimage.take_from(new_image);
} else {
// Rescale while reading. Some image types (e.g. jpeg) can take
// advantage of this.
pnmimage.set_read_size(new_x_size, new_y_size);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::format_texture_type
// Access: Published, Static
// Description: Returns the indicated TextureType converted to a
// string word.
////////////////////////////////////////////////////////////////////
string Texture::
format_texture_type(TextureType tt) {
switch (tt) {
case TT_1d_texture:
return "1d_texture";
case TT_2d_texture:
return "2d_texture";
case TT_3d_texture:
return "3d_texture";
case TT_2d_texture_array:
return "2d_texture_array";
case TT_cube_map:
return "cube_map";
}
return "**invalid**";
}
////////////////////////////////////////////////////////////////////
// Function: Texture::string_texture_type
// Access: Published, Static
// Description: Returns the TextureType corresponding to the
// indicated string word.
////////////////////////////////////////////////////////////////////
Texture::TextureType Texture::
string_texture_type(const string &str) {
if (cmp_nocase(str, "1d_texture") == 0) {
return TT_1d_texture;
} else if (cmp_nocase(str, "2d_texture") == 0) {
return TT_2d_texture;
} else if (cmp_nocase(str, "3d_texture") == 0) {
return TT_3d_texture;
} else if (cmp_nocase(str, "2d_texture_array") == 0) {
return TT_2d_texture_array;
} else if (cmp_nocase(str, "cube_map") == 0) {
return TT_cube_map;
}
gobj_cat->error()
<< "Invalid Texture::TextureType value: " << str << "\n";
return TT_2d_texture;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::format_component_type
// Access: Published, Static
// Description: Returns the indicated ComponentType converted to a
// string word.
////////////////////////////////////////////////////////////////////
string Texture::
format_component_type(ComponentType ct) {
switch (ct) {
case T_unsigned_byte:
return "unsigned_byte";
case T_unsigned_short:
return "unsigned_short";
case T_float:
return "float";
case T_unsigned_int_24_8:
return "unsigned_int_24_8";
case T_int:
return "int";
}
return "**invalid**";
}
////////////////////////////////////////////////////////////////////
// Function: Texture::string_component_type
// Access: Published, Static
// Description: Returns the ComponentType corresponding to the
// indicated string word.
////////////////////////////////////////////////////////////////////
Texture::ComponentType Texture::
string_component_type(const string &str) {
if (cmp_nocase(str, "unsigned_byte") == 0) {
return T_unsigned_byte;
} else if (cmp_nocase(str, "unsigned_short") == 0) {
return T_unsigned_short;
} else if (cmp_nocase(str, "float") == 0) {
return T_float;
} else if (cmp_nocase(str, "unsigned_int_24_8") == 0) {
return T_unsigned_int_24_8;
} else if (cmp_nocase(str, "int") == 0) {
return T_int;
}
gobj_cat->error()
<< "Invalid Texture::ComponentType value: " << str << "\n";
return T_unsigned_byte;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::format_format
// Access: Published, Static
// Description: Returns the indicated Format converted to a
// string word.
////////////////////////////////////////////////////////////////////
string Texture::
format_format(Format format) {
switch (format) {
case F_depth_stencil:
return "depth_stencil";
case F_depth_component:
return "depth_component";
case F_depth_component16:
return "depth_component16";
case F_depth_component24:
return "depth_component24";
case F_depth_component32:
return "depth_component32";
case F_color_index:
return "color_index";
case F_red:
return "red";
case F_green:
return "green";
case F_blue:
return "blue";
case F_alpha:
return "alpha";
case F_rgb:
return "rgb";
case F_rgb5:
return "rgb5";
case F_rgb8:
return "rgb8";
case F_rgb12:
return "rgb12";
case F_rgb332:
return "rgb332";
case F_rgba:
return "rgba";
case F_rgbm:
return "rgbm";
case F_rgba4:
return "rgba4";
case F_rgba5:
return "rgba5";
case F_rgba8:
return "rgba8";
case F_rgba12:
return "rgba12";
case F_luminance:
return "luminance";
case F_luminance_alpha:
return "luminance_alpha";
case F_luminance_alphamask:
return "luminance_alphamask";
case F_rgba16:
return "rgba16";
case F_rgba32:
return "rgba32";
case F_r16:
return "r16";
case F_rg16:
return "rg16";
case F_rgb16:
return "rgb16";
case F_srgb:
return "srgb";
case F_srgb_alpha:
return "srgb_alpha";
case F_sluminance:
return "sluminance";
case F_sluminance_alpha:
return "sluminance_alpha";
case F_r32i:
return "r32i";
case F_r32:
return "r32";
case F_rg32:
return "rg32";
case F_rgb32:
return "rgb32";
}
return "**invalid**";
}
////////////////////////////////////////////////////////////////////
// Function: Texture::string_format
// Access: Published, Static
// Description: Returns the Format corresponding to the
// indicated string word.
////////////////////////////////////////////////////////////////////
Texture::Format Texture::
string_format(const string &str) {
if (cmp_nocase(str, "depth_stencil") == 0) {
return F_depth_stencil;
} else if (cmp_nocase(str, "depth_component") == 0) {
return F_depth_component;
} else if (cmp_nocase(str, "depth_component16") == 0 || cmp_nocase(str, "d16") == 0) {
return F_depth_component16;
} else if (cmp_nocase(str, "depth_component24") == 0 || cmp_nocase(str, "d24") == 0) {
return F_depth_component24;
} else if (cmp_nocase(str, "depth_component32") == 0 || cmp_nocase(str, "d32") == 0) {
return F_depth_component32;
} else if (cmp_nocase(str, "color_index") == 0) {
return F_color_index;
} else if (cmp_nocase(str, "red") == 0) {
return F_red;
} else if (cmp_nocase(str, "green") == 0) {
return F_green;
} else if (cmp_nocase(str, "blue") == 0) {
return F_blue;
} else if (cmp_nocase(str, "alpha") == 0) {
return F_alpha;
} else if (cmp_nocase(str, "rgb") == 0) {
return F_rgb;
} else if (cmp_nocase(str, "rgb5") == 0) {
return F_rgb5;
} else if (cmp_nocase(str, "rgb8") == 0 || cmp_nocase(str, "r8g8b8") == 0) {
return F_rgb8;
} else if (cmp_nocase(str, "rgb12") == 0) {
return F_rgb12;
} else if (cmp_nocase(str, "rgb332") == 0 || cmp_nocase(str, "r3g3b2") == 0) {
return F_rgb332;
} else if (cmp_nocase(str, "rgba") == 0) {
return F_rgba;
} else if (cmp_nocase(str, "rgbm") == 0) {
return F_rgbm;
} else if (cmp_nocase(str, "rgba4") == 0) {
return F_rgba4;
} else if (cmp_nocase(str, "rgba5") == 0) {
return F_rgba5;
} else if (cmp_nocase(str, "rgba8") == 0 || cmp_nocase(str, "r8g8b8a8") == 0) {
return F_rgba8;
} else if (cmp_nocase(str, "rgba12") == 0) {
return F_rgba12;
} else if (cmp_nocase(str, "luminance") == 0) {
return F_luminance;
} else if (cmp_nocase(str, "luminance_alpha") == 0) {
return F_luminance_alpha;
} else if (cmp_nocase(str, "luminance_alphamask") == 0) {
return F_luminance_alphamask;
} else if (cmp_nocase(str, "rgba16") == 0 || cmp_nocase(str, "r16g16b16a16") == 0) {
return F_rgba16;
} else if (cmp_nocase(str, "rgba32") == 0 || cmp_nocase(str, "r32g32b32a32") == 0) {
return F_rgba32;
} else if (cmp_nocase(str, "r16") == 0 || cmp_nocase(str, "red16") == 0) {
return F_r16;
} else if (cmp_nocase(str, "rg16") == 0 || cmp_nocase(str, "r16g16") == 0) {
return F_rg16;
} else if (cmp_nocase(str, "rgb16") == 0 || cmp_nocase(str, "r16g16b16") == 0) {
return F_rgb16;
} else if (cmp_nocase(str, "srgb") == 0) {
return F_srgb;
} else if (cmp_nocase(str, "srgb_alpha") == 0) {
return F_srgb_alpha;
} else if (cmp_nocase(str, "sluminance") == 0) {
return F_sluminance;
} else if (cmp_nocase(str, "sluminance_alpha") == 0) {
return F_sluminance_alpha;
} else if (cmp_nocase(str, "r32i") == 0) {
return F_r32i;
} else if (cmp_nocase(str, "r32") == 0 || cmp_nocase(str, "red32") == 0) {
return F_r32;
} else if (cmp_nocase(str, "rg32") == 0 || cmp_nocase(str, "r32g32") == 0) {
return F_rg32;
} else if (cmp_nocase(str, "rgb32") == 0 || cmp_nocase(str, "r32g32b32") == 0) {
return F_rgb32;
}
gobj_cat->error()
<< "Invalid Texture::Format value: " << str << "\n";
return F_rgba;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::format_filter_type
// Access: Published, Static
// Description: Returns the indicated FilterType converted to a
// string word.
////////////////////////////////////////////////////////////////////
string Texture::
format_filter_type(FilterType ft) {
switch (ft) {
case FT_nearest:
return "nearest";
case FT_linear:
return "linear";
case FT_nearest_mipmap_nearest:
return "nearest_mipmap_nearest";
case FT_linear_mipmap_nearest:
return "linear_mipmap_nearest";
case FT_nearest_mipmap_linear:
return "nearest_mipmap_linear";
case FT_linear_mipmap_linear:
return "linear_mipmap_linear";
case FT_shadow:
return "shadow";
case FT_default:
return "default";
case FT_invalid:
return "invalid";
}
return "**invalid**";
}
////////////////////////////////////////////////////////////////////
// Function: Texture::string_filter_type
// Access: Public
// Description: Returns the FilterType value associated with the given
// string representation, or FT_invalid if the string
// does not match any known FilterType value.
////////////////////////////////////////////////////////////////////
Texture::FilterType Texture::
string_filter_type(const string &string) {
if (cmp_nocase_uh(string, "nearest") == 0) {
return FT_nearest;
} else if (cmp_nocase_uh(string, "linear") == 0) {
return FT_linear;
} else if (cmp_nocase_uh(string, "nearest_mipmap_nearest") == 0) {
return FT_nearest_mipmap_nearest;
} else if (cmp_nocase_uh(string, "linear_mipmap_nearest") == 0) {
return FT_linear_mipmap_nearest;
} else if (cmp_nocase_uh(string, "nearest_mipmap_linear") == 0) {
return FT_nearest_mipmap_linear;
} else if (cmp_nocase_uh(string, "linear_mipmap_linear") == 0) {
return FT_linear_mipmap_linear;
} else if (cmp_nocase_uh(string, "mipmap") == 0) {
return FT_linear_mipmap_linear;
} else if (cmp_nocase_uh(string, "shadow") == 0) {
return FT_shadow;
} else if (cmp_nocase_uh(string, "default") == 0) {
return FT_default;
} else {
return FT_invalid;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::format_wrap_mode
// Access: Published, Static
// Description: Returns the indicated WrapMode converted to a
// string word.
////////////////////////////////////////////////////////////////////
string Texture::
format_wrap_mode(WrapMode wm) {
switch (wm) {
case WM_clamp:
return "clamp";
case WM_repeat:
return "repeat";
case WM_mirror:
return "mirror";
case WM_mirror_once:
return "mirror_once";
case WM_border_color:
return "border_color";
case WM_invalid:
return "invalid";
}
return "**invalid**";
}
////////////////////////////////////////////////////////////////////
// Function: Texture::string_wrap_mode
// Access: Public
// Description: Returns the WrapMode value associated with the given
// string representation, or WM_invalid if the string
// does not match any known WrapMode value.
////////////////////////////////////////////////////////////////////
Texture::WrapMode Texture::
string_wrap_mode(const string &string) {
if (cmp_nocase_uh(string, "repeat") == 0 ||
cmp_nocase_uh(string, "wrap") == 0) {
return WM_repeat;
} else if (cmp_nocase_uh(string, "clamp") == 0) {
return WM_clamp;
} else if (cmp_nocase_uh(string, "mirror") == 0 ||
cmp_nocase_uh(string, "mirrored_repeat") == 0) {
return WM_mirror;
} else if (cmp_nocase_uh(string, "mirror_once") == 0) {
return WM_mirror_once;
} else if (cmp_nocase_uh(string, "border_color") == 0 ||
cmp_nocase_uh(string, "border") == 0) {
return WM_border_color;
} else {
return WM_invalid;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::format_compression_mode
// Access: Published, Static
// Description: Returns the indicated CompressionMode converted to a
// string word.
////////////////////////////////////////////////////////////////////
string Texture::
format_compression_mode(CompressionMode cm) {
switch (cm) {
case CM_default:
return "default";
case CM_off:
return "off";
case CM_on:
return "on";
case CM_fxt1:
return "fxt1";
case CM_dxt1:
return "dxt1";
case CM_dxt2:
return "dxt2";
case CM_dxt3:
return "dxt3";
case CM_dxt4:
return "dxt4";
case CM_dxt5:
return "dxt5";
case CM_pvr1_2bpp:
return "pvr1_2bpp";
case CM_pvr1_4bpp:
return "pvr1_4bpp";
}
return "**invalid**";
}
////////////////////////////////////////////////////////////////////
// Function: Texture::string_compression_mode
// Access: Public
// Description: Returns the CompressionMode value associated with the
// given string representation.
////////////////////////////////////////////////////////////////////
Texture::CompressionMode Texture::
string_compression_mode(const string &str) {
if (cmp_nocase_uh(str, "default") == 0) {
return CM_default;
} else if (cmp_nocase_uh(str, "off") == 0) {
return CM_off;
} else if (cmp_nocase_uh(str, "on") == 0) {
return CM_on;
} else if (cmp_nocase_uh(str, "fxt1") == 0) {
return CM_fxt1;
} else if (cmp_nocase_uh(str, "dxt1") == 0) {
return CM_dxt1;
} else if (cmp_nocase_uh(str, "dxt2") == 0) {
return CM_dxt2;
} else if (cmp_nocase_uh(str, "dxt3") == 0) {
return CM_dxt3;
} else if (cmp_nocase_uh(str, "dxt4") == 0) {
return CM_dxt4;
} else if (cmp_nocase_uh(str, "dxt5") == 0) {
return CM_dxt5;
} else if (cmp_nocase_uh(str, "pvr1_2bpp") == 0) {
return CM_pvr1_2bpp;
} else if (cmp_nocase_uh(str, "pvr1_4bpp") == 0) {
return CM_pvr1_4bpp;
}
gobj_cat->error()
<< "Invalid Texture::CompressionMode value: " << str << "\n";
return CM_default;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::format_quality_level
// Access: Published, Static
// Description: Returns the indicated QualityLevel converted to a
// string word.
////////////////////////////////////////////////////////////////////
string Texture::
format_quality_level(QualityLevel ql) {
switch (ql) {
case QL_default:
return "default";
case QL_fastest:
return "fastest";
case QL_normal:
return "normal";
case QL_best:
return "best";
}
return "**invalid**";
}
////////////////////////////////////////////////////////////////////
// Function: Texture::string_quality_level
// Access: Public
// Description: Returns the QualityLevel value associated with the
// given string representation.
////////////////////////////////////////////////////////////////////
Texture::QualityLevel Texture::
string_quality_level(const string &str) {
if (cmp_nocase(str, "default") == 0) {
return QL_default;
} else if (cmp_nocase(str, "fastest") == 0) {
return QL_fastest;
} else if (cmp_nocase(str, "normal") == 0) {
return QL_normal;
} else if (cmp_nocase(str, "best") == 0) {
return QL_best;
}
gobj_cat->error()
<< "Invalid Texture::QualityLevel value: " << str << "\n";
return QL_default;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::texture_uploaded
// Access: Public
// Description: This method is called by the GraphicsEngine at the
// beginning of the frame *after* a texture has been
// successfully uploaded to graphics memory. It is
// intended as a callback so the texture can release its
// RAM image, if _keep_ram_image is false.
//
// This is called indirectly when the GSG calls
// GraphicsEngine::texture_uploaded().
////////////////////////////////////////////////////////////////////
void Texture::
texture_uploaded() {
CDLockedReader cdata(_cycler);
if (!keep_texture_ram && !cdata->_keep_ram_image) {
// Once we have prepared the texture, we can generally safely
// remove the pixels from main RAM. The GSG is now responsible
// for remembering what it looks like.
CDWriter cdataw(_cycler, cdata, false);
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Dumping RAM for texture " << get_name() << "\n";
}
do_clear_ram_image(cdataw);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::has_cull_callback
// Access: Public, Virtual
// Description: Should be overridden by derived classes to return
// true if cull_callback() has been defined. Otherwise,
// returns false to indicate cull_callback() does not
// need to be called for this node during the cull
// traversal.
////////////////////////////////////////////////////////////////////
bool Texture::
has_cull_callback() const {
return false;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::cull_callback
// Access: Public, Virtual
// Description: If has_cull_callback() returns true, this function
// will be called during the cull traversal to perform
// any additional operations that should be performed at
// cull time.
//
// This is called each time the Texture is discovered
// applied to a Geom in the traversal. It should return
// true if the Geom is visible, false if it should be
// omitted.
////////////////////////////////////////////////////////////////////
bool Texture::
cull_callback(CullTraverser *, const CullTraverserData &) const {
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::make_texture
// Access: Public, Static
// Description: A factory function to make a new Texture, used to
// pass to the TexturePool.
////////////////////////////////////////////////////////////////////
PT(Texture) Texture::
make_texture() {
return new Texture;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::is_specific
// Access: Public, Static
// Description: Returns true if the indicated compression mode is one
// of the specific compression types, false otherwise.
////////////////////////////////////////////////////////////////////
bool Texture::
is_specific(Texture::CompressionMode compression) {
switch (compression) {
case CM_default:
case CM_off:
case CM_on:
return false;
default:
return true;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::has_alpha
// Access: Public, Static
// Description: Returns true if the indicated format includes alpha,
// false otherwise.
////////////////////////////////////////////////////////////////////
bool Texture::
has_alpha(Format format) {
switch (format) {
case F_alpha:
case F_rgba:
case F_rgbm:
case F_rgba4:
case F_rgba5:
case F_rgba8:
case F_rgba12:
case F_rgba16:
case F_rgba32:
case F_luminance_alpha:
case F_luminance_alphamask:
case F_srgb_alpha:
case F_sluminance_alpha:
return true;
default:
return false;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::has_binary_alpha
// Access: Public, Static
// Description: Returns true if the indicated format includes a
// binary alpha only, false otherwise.
////////////////////////////////////////////////////////////////////
bool Texture::
has_binary_alpha(Format format) {
switch (format) {
case F_rgbm:
return true;
default:
return false;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::is_srgb
// Access: Public, Static
// Description: Returns true if the indicated format is in the
// sRGB color space, false otherwise.
////////////////////////////////////////////////////////////////////
bool Texture::
is_srgb(Format format) {
switch (format) {
case F_srgb:
case F_srgb_alpha:
case F_sluminance:
case F_sluminance_alpha:
return true;
default:
return false;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::adjust_size
// Access: Public, Static
// Description: Computes the proper size of the texture, based on the
// original size, the filename, and the resizing whims
// of the config file.
//
// x_size and y_size should be loaded with the texture
// image's original size on disk. On return, they will
// be loaded with the texture's in-memory target size.
// The return value is true if the size has been
// adjusted, or false if it is the same.
////////////////////////////////////////////////////////////////////
bool Texture::
adjust_size(int &x_size, int &y_size, const string &name,
bool for_padding, AutoTextureScale auto_texture_scale) {
bool exclude = false;
int num_excludes = exclude_texture_scale.get_num_unique_values();
for (int i = 0; i < num_excludes && !exclude; ++i) {
GlobPattern pat(exclude_texture_scale.get_unique_value(i));
if (pat.matches(name)) {
exclude = true;
}
}
int new_x_size = x_size;
int new_y_size = y_size;
if (!exclude) {
new_x_size = (int)cfloor(new_x_size * texture_scale + 0.5);
new_y_size = (int)cfloor(new_y_size * texture_scale + 0.5);
// Don't auto-scale below 4 in either dimension. This causes
// problems for DirectX and texture compression.
new_x_size = min(max(new_x_size, (int)texture_scale_limit), x_size);
new_y_size = min(max(new_y_size, (int)texture_scale_limit), y_size);
}
AutoTextureScale ats = auto_texture_scale;
if (ats == ATS_unspecified) {
ats = get_textures_power_2();
}
if (!for_padding && ats == ATS_pad) {
// If we're not calculating the padding size--that is, we're
// calculating the initial scaling size instead--then ignore
// ATS_pad, and treat it the same as ATS_none.
ats = ATS_none;
}
switch (ats) {
case ATS_down:
new_x_size = down_to_power_2(new_x_size);
new_y_size = down_to_power_2(new_y_size);
break;
case ATS_up:
case ATS_pad:
new_x_size = up_to_power_2(new_x_size);
new_y_size = up_to_power_2(new_y_size);
break;
case ATS_none:
case ATS_unspecified:
break;
}
ats = textures_square.get_value();
if (!for_padding && ats == ATS_pad) {
ats = ATS_none;
}
switch (ats) {
case ATS_down:
new_x_size = new_y_size = min(new_x_size, new_y_size);
break;
case ATS_up:
case ATS_pad:
new_x_size = new_y_size = max(new_x_size, new_y_size);
break;
case ATS_none:
case ATS_unspecified:
break;
}
if (!exclude) {
int max_dimension = max_texture_dimension;
if (max_dimension < 0) {
GraphicsStateGuardianBase *gsg = GraphicsStateGuardianBase::get_default_gsg();
if (gsg != (GraphicsStateGuardianBase *)NULL) {
max_dimension = gsg->get_max_texture_dimension();
}
}
if (max_dimension > 0) {
new_x_size = min(new_x_size, (int)max_dimension);
new_y_size = min(new_y_size, (int)max_dimension);
}
}
if (x_size != new_x_size || y_size != new_y_size) {
x_size = new_x_size;
y_size = new_y_size;
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::ensure_loader_type
// Access: Public, Virtual
// Description: May be called prior to calling read_txo() or any
// bam-related Texture-creating callback, to ensure that
// the proper dynamic libraries for a Texture of the
// current class type, and the indicated filename, have
// been already loaded.
//
// This is a low-level function that should not normally
// need to be called directly by the user.
//
// Note that for best results you must first create a
// Texture object of the appropriate class type for your
// filename, for instance with
// TexturePool::make_texture().
////////////////////////////////////////////////////////////////////
void Texture::
ensure_loader_type(const Filename &filename) {
// For a plain Texture type, this doesn't need to do anything.
}
////////////////////////////////////////////////////////////////////
// Function: Texture::reconsider_dirty
// Access: Protected, Virtual
// Description: Called by TextureContext to give the Texture a chance
// to mark itself dirty before rendering, if necessary.
////////////////////////////////////////////////////////////////////
void Texture::
reconsider_dirty() {
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_adjust_this_size
// Access: Protected, Virtual
// Description: Works like adjust_size, but also considers the
// texture class. Movie textures, for instance, always
// pad outwards, regardless of textures-power-2.
////////////////////////////////////////////////////////////////////
bool Texture::
do_adjust_this_size(const CData *cdata, int &x_size, int &y_size, const string &name,
bool for_padding) const {
return adjust_size(x_size, y_size, name, for_padding, cdata->_auto_texture_scale);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_read
// Access: Protected, Virtual
// Description: The internal implementation of the various read()
// methods.
////////////////////////////////////////////////////////////////////
bool Texture::
do_read(CData *cdata, 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,
const LoaderOptions &options, BamCacheRecord *record) {
PStatTimer timer(_texture_read_pcollector);
if (options.get_auto_texture_scale() != ATS_unspecified) {
cdata->_auto_texture_scale = options.get_auto_texture_scale();
}
bool header_only = ((options.get_texture_flags() & (LoaderOptions::TF_preload | LoaderOptions::TF_preload_simple)) == 0);
if (record != (BamCacheRecord *)NULL) {
header_only = false;
}
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.
do_clear_ram_image(cdata);
}
if (is_txo_filename(fullpath)) {
if (record != (BamCacheRecord *)NULL) {
record->add_dependent_file(fullpath);
}
return do_read_txo_file(cdata, fullpath);
}
if (is_dds_filename(fullpath)) {
if (record != (BamCacheRecord *)NULL) {
record->add_dependent_file(fullpath);
}
return do_read_dds_file(cdata, fullpath, header_only);
}
// If read_pages or read_mipmaps is specified, then z and n actually
// indicate z_size and n_size, respectively--the numerical limits on
// which to search for filenames.
int z_size = z;
int n_size = n;
// Certain texture types have an implicit z_size. If z_size is
// omitted, choose an appropriate default based on the texture
// type.
if (z_size == 0) {
switch (cdata->_texture_type) {
case TT_1d_texture:
case TT_2d_texture:
z_size = 1;
break;
case TT_cube_map:
z_size = 6;
break;
default:
break;
}
}
int num_views = 0;
if (options.get_texture_flags() & LoaderOptions::TF_multiview) {
// We'll be loading a multiview texture.
read_pages = true;
if (options.get_texture_num_views() != 0) {
num_views = options.get_texture_num_views();
do_set_num_views(cdata, num_views);
}
}
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
if (read_pages && read_mipmaps) {
// Read a sequence of pages * mipmap levels.
Filename fullpath_pattern = Filename::pattern_filename(fullpath);
Filename alpha_fullpath_pattern = Filename::pattern_filename(alpha_fullpath);
do_set_z_size(cdata, z_size);
n = 0;
while (true) {
// For mipmap level 0, the total number of pages might be
// determined by the number of files we find. After mipmap
// level 0, though, the number of pages is predetermined.
if (n != 0) {
z_size = do_get_expected_mipmap_z_size(cdata, n);
}
z = 0;
Filename n_pattern = Filename::pattern_filename(fullpath_pattern.get_filename_index(z));
Filename alpha_n_pattern = Filename::pattern_filename(alpha_fullpath_pattern.get_filename_index(z));
if (!n_pattern.has_hash()) {
gobj_cat.error()
<< "Filename requires two different hash sequences: " << fullpath
<< "\n";
return false;
}
Filename file = n_pattern.get_filename_index(n);
Filename alpha_file = alpha_n_pattern.get_filename_index(n);
if ((n_size == 0 && (vfs->exists(file) || n == 0)) ||
(n_size != 0 && n < n_size)) {
// Continue through the loop.
} else {
// We've reached the end of the mipmap sequence.
break;
}
int num_pages = z_size * num_views;
while ((num_pages == 0 && (vfs->exists(file) || z == 0)) ||
(num_pages != 0 && z < num_pages)) {
if (!do_read_one(cdata, file, alpha_file, z, n, primary_file_num_channels,
alpha_file_channel, options, header_only, record)) {
return false;
}
++z;
n_pattern = Filename::pattern_filename(fullpath_pattern.get_filename_index(z));
file = n_pattern.get_filename_index(n);
alpha_file = alpha_n_pattern.get_filename_index(n);
}
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 = do_get_expected_num_mipmap_levels(cdata);
}
++n;
}
cdata->_fullpath = fullpath_pattern;
cdata->_alpha_fullpath = alpha_fullpath_pattern;
} else if (read_pages) {
// Read a sequence of cube map or 3-D texture pages.
Filename fullpath_pattern = Filename::pattern_filename(fullpath);
Filename alpha_fullpath_pattern = Filename::pattern_filename(alpha_fullpath);
if (!fullpath_pattern.has_hash()) {
gobj_cat.error()
<< "Filename requires a hash mark: " << fullpath
<< "\n";
return false;
}
do_set_z_size(cdata, z_size);
z = 0;
Filename file = fullpath_pattern.get_filename_index(z);
Filename alpha_file = alpha_fullpath_pattern.get_filename_index(z);
int num_pages = z_size * num_views;
while ((num_pages == 0 && (vfs->exists(file) || z == 0)) ||
(num_pages != 0 && z < num_pages)) {
if (!do_read_one(cdata, file, alpha_file, z, 0, primary_file_num_channels,
alpha_file_channel, options, header_only, record)) {
return false;
}
++z;
file = fullpath_pattern.get_filename_index(z);
alpha_file = alpha_fullpath_pattern.get_filename_index(z);
}
cdata->_fullpath = fullpath_pattern;
cdata->_alpha_fullpath = alpha_fullpath_pattern;
} else if (read_mipmaps) {
// Read a sequence of mipmap levels.
Filename fullpath_pattern = Filename::pattern_filename(fullpath);
Filename alpha_fullpath_pattern = Filename::pattern_filename(alpha_fullpath);
if (!fullpath_pattern.has_hash()) {
gobj_cat.error()
<< "Filename requires a hash mark: " << fullpath
<< "\n";
return false;
}
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(cdata, file, alpha_file, z, n,
primary_file_num_channels, alpha_file_channel,
options, header_only, record)) {
return false;
}
++n;
if (n_size == 0 && n >= do_get_expected_num_mipmap_levels(cdata)) {
// 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);
}
cdata->_fullpath = fullpath_pattern;
cdata->_alpha_fullpath = alpha_fullpath_pattern;
} else {
// Just an ordinary read of one file.
if (!do_read_one(cdata, fullpath, alpha_fullpath, z, n,
primary_file_num_channels, alpha_file_channel,
options, header_only, record)) {
return false;
}
}
cdata->_has_read_pages = read_pages;
cdata->_has_read_mipmaps = read_mipmaps;
cdata->_num_mipmap_levels_read = cdata->_ram_images.size();
if (header_only) {
// If we were only supposed to be checking the image header
// information, don't let the Texture think that it's got the
// image now.
do_clear_ram_image(cdata);
} else {
if ((options.get_texture_flags() & LoaderOptions::TF_preload) != 0) {
// If we intend to keep the ram image around, consider
// compressing it etc.
bool generate_mipmaps = ((options.get_texture_flags() & LoaderOptions::TF_generate_mipmaps) != 0);
do_consider_auto_process_ram_image(cdata, generate_mipmaps || uses_mipmaps(), true);
}
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_read_one
// Access: Protected, Virtual
// Description: Called only from do_read(), this method reads a
// single image file, either one page or one mipmap
// level.
////////////////////////////////////////////////////////////////////
bool Texture::
do_read_one(CData *cdata, const Filename &fullpath, const Filename &alpha_fullpath,
int z, int n, int primary_file_num_channels, int alpha_file_channel,
const LoaderOptions &options, bool header_only, BamCacheRecord *record) {
if (record != (BamCacheRecord *)NULL) {
nassertr(!header_only, false);
record->add_dependent_file(fullpath);
}
PNMImage image;
PfmFile pfm;
PNMReader *image_reader = image.make_reader(fullpath, NULL, false);
if (image_reader == NULL) {
gobj_cat.error()
<< "Texture::read() - couldn't read: " << fullpath << endl;
return false;
}
image.copy_header_from(*image_reader);
// If it's a floating-point image file, read it by default into a
// floating-point texture.
bool read_floating_point;
int texture_load_type = (options.get_texture_flags() & (LoaderOptions::TF_integer | LoaderOptions::TF_float));
switch (texture_load_type) {
case LoaderOptions::TF_integer:
read_floating_point = false;
break;
case LoaderOptions::TF_float:
read_floating_point = true;
break;
default:
// Neither TF_integer nor TF_float was specified; determine which
// way the texture wants to be loaded.
read_floating_point = (image_reader->is_floating_point());
if (!alpha_fullpath.empty()) {
read_floating_point = false;
}
}
if (header_only || textures_header_only) {
int x_size = image.get_x_size();
int y_size = image.get_y_size();
if (z == 0 && n == 0) {
cdata->_orig_file_x_size = x_size;
cdata->_orig_file_y_size = y_size;
}
if (textures_header_only) {
// In this mode, we never intend to load the actual texture
// image anyway, so we don't even need to make the size right.
x_size = 1;
y_size = 1;
} else {
consider_rescale(image, fullpath.get_basename(), do_get_auto_texture_scale(cdata));
x_size = image.get_read_x_size();
y_size = image.get_read_y_size();
}
if (read_floating_point) {
pfm.clear(x_size, y_size, image.get_num_channels());
} else {
image = PNMImage(x_size, y_size, image.get_num_channels(),
image.get_maxval(), image.get_type());
image.fill(0.2, 0.3, 1.0);
if (image.has_alpha()) {
image.alpha_fill(1.0);
}
}
delete image_reader;
} else {
if (z == 0 && n == 0) {
cdata->_orig_file_x_size = image.get_x_size();
cdata->_orig_file_y_size = image.get_y_size();
consider_rescale(image, fullpath.get_basename(), do_get_auto_texture_scale(cdata));
} else {
image.set_read_size(do_get_expected_mipmap_x_size(cdata, n),
do_get_expected_mipmap_y_size(cdata, n));
}
if (image.get_x_size() != image.get_read_x_size() ||
image.get_y_size() != image.get_read_y_size()) {
gobj_cat.info()
<< "Implicitly rescaling " << fullpath.get_basename() << " from "
<< image.get_x_size() << " by " << image.get_y_size() << " to "
<< image.get_read_x_size() << " by " << image.get_read_y_size()
<< "\n";
}
bool success;
if (read_floating_point) {
success = pfm.read(image_reader);
} else {
success = image.read(image_reader);
}
if (!success) {
gobj_cat.error()
<< "Texture::read() - couldn't read: " << fullpath << endl;
return false;
}
Thread::consider_yield();
}
PNMImage alpha_image;
if (!alpha_fullpath.empty()) {
PNMReader *alpha_image_reader = alpha_image.make_reader(alpha_fullpath, NULL, false);
if (alpha_image_reader == NULL) {
gobj_cat.error()
<< "Texture::read() - couldn't read: " << alpha_fullpath << endl;
return false;
}
alpha_image.copy_header_from(*alpha_image_reader);
if (record != (BamCacheRecord *)NULL) {
record->add_dependent_file(alpha_fullpath);
}
if (header_only || textures_header_only) {
int x_size = image.get_x_size();
int y_size = image.get_y_size();
alpha_image = PNMImage(x_size, y_size, alpha_image.get_num_channels(),
alpha_image.get_maxval(), alpha_image.get_type());
alpha_image.fill(1.0);
if (alpha_image.has_alpha()) {
alpha_image.alpha_fill(1.0);
}
delete alpha_image_reader;
} else {
if (image.get_x_size() != alpha_image.get_x_size() ||
image.get_y_size() != alpha_image.get_y_size()) {
gobj_cat.info()
<< "Implicitly rescaling " << alpha_fullpath.get_basename()
<< " from " << alpha_image.get_x_size() << " by "
<< alpha_image.get_y_size() << " to " << image.get_x_size()
<< " by " << image.get_y_size() << "\n";
alpha_image.set_read_size(image.get_x_size(), image.get_y_size());
}
if (!alpha_image.read(alpha_image_reader)) {
gobj_cat.error()
<< "Texture::read() - couldn't read (alpha): " << alpha_fullpath << endl;
return false;
}
Thread::consider_yield();
}
}
if (z == 0 && n == 0) {
if (!has_name()) {
set_name(fullpath.get_basename_wo_extension());
}
if (cdata->_filename.empty()) {
cdata->_filename = fullpath;
cdata->_alpha_filename = alpha_fullpath;
// The first time we set the filename via a read() operation, we
// clear keep_ram_image. The user can always set it again later
// if he needs to.
cdata->_keep_ram_image = false;
}
cdata->_fullpath = fullpath;
cdata->_alpha_fullpath = alpha_fullpath;
}
if (!alpha_fullpath.empty()) {
// The grayscale (alpha channel) image must be the same size as
// the main image. This should really have been already
// guaranteed by the above.
if (image.get_x_size() != alpha_image.get_x_size() ||
image.get_y_size() != alpha_image.get_y_size()) {
gobj_cat.info()
<< "Automatically rescaling " << alpha_fullpath.get_basename()
<< " from " << alpha_image.get_x_size() << " by "
<< alpha_image.get_y_size() << " to " << image.get_x_size()
<< " by " << image.get_y_size() << "\n";
PNMImage scaled(image.get_x_size(), image.get_y_size(),
alpha_image.get_num_channels(),
alpha_image.get_maxval(), alpha_image.get_type());
scaled.quick_filter_from(alpha_image);
Thread::consider_yield();
alpha_image = scaled;
}
}
if (n == 0) {
consider_downgrade(image, primary_file_num_channels, get_name());
cdata->_primary_file_num_channels = image.get_num_channels();
cdata->_alpha_file_channel = 0;
}
if (!alpha_fullpath.empty()) {
// Make the original image a 4-component image by taking the
// grayscale value from the second image.
image.add_alpha();
if (alpha_file_channel == 4 ||
(alpha_file_channel == 2 && alpha_image.get_num_channels() == 2)) {
// Use the alpha channel.
for (int x = 0; x < image.get_x_size(); x++) {
for (int y = 0; y < image.get_y_size(); y++) {
image.set_alpha(x, y, alpha_image.get_alpha(x, y));
}
}
cdata->_alpha_file_channel = alpha_image.get_num_channels();
} else if (alpha_file_channel >= 1 && alpha_file_channel <= 3 &&
alpha_image.get_num_channels() >= 3) {
// Use the appropriate red, green, or blue channel.
for (int x = 0; x < image.get_x_size(); x++) {
for (int y = 0; y < image.get_y_size(); y++) {
image.set_alpha(x, y, alpha_image.get_channel_val(x, y, alpha_file_channel - 1));
}
}
cdata->_alpha_file_channel = alpha_file_channel;
} else {
// Use the grayscale channel.
for (int x = 0; x < image.get_x_size(); x++) {
for (int y = 0; y < image.get_y_size(); y++) {
image.set_alpha(x, y, alpha_image.get_gray(x, y));
}
}
cdata->_alpha_file_channel = 0;
}
}
if (read_floating_point) {
if (!do_load_one(cdata, pfm, fullpath.get_basename(), z, n, options)) {
return false;
}
} else {
// Now see if we want to pad the image within a larger power-of-2
// image.
int pad_x_size = 0;
int pad_y_size = 0;
if (do_get_auto_texture_scale(cdata) == ATS_pad) {
int new_x_size = image.get_x_size();
int new_y_size = image.get_y_size();
if (do_adjust_this_size(cdata, new_x_size, new_y_size, fullpath.get_basename(), true)) {
pad_x_size = new_x_size - image.get_x_size();
pad_y_size = new_y_size - image.get_y_size();
PNMImage new_image(new_x_size, new_y_size, image.get_num_channels(),
image.get_maxval());
new_image.copy_sub_image(image, 0, new_y_size - image.get_y_size());
image.take_from(new_image);
}
}
if (!do_load_one(cdata, image, fullpath.get_basename(), z, n, options)) {
return false;
}
do_set_pad_size(cdata, pad_x_size, pad_y_size, 0);
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_load_one
// Access: Protected, Virtual
// Description: Internal method to load a single page or mipmap
// level.
////////////////////////////////////////////////////////////////////
bool Texture::
do_load_one(CData *cdata, const PNMImage &pnmimage, const string &name, int z, int n,
const LoaderOptions &options) {
if (cdata->_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
// image properties like size and number of components.
if (!do_reconsider_z_size(cdata, z, options)) {
return false;
}
nassertr(z >= 0 && z < cdata->_z_size * cdata->_num_views, false);
if (z == 0) {
ComponentType component_type = T_unsigned_byte;
xelval maxval = pnmimage.get_maxval();
if (maxval > 255) {
component_type = T_unsigned_short;
}
if (!do_reconsider_image_properties(cdata, pnmimage.get_x_size(), pnmimage.get_y_size(),
pnmimage.get_num_channels(), component_type,
z, options)) {
return false;
}
}
do_modify_ram_image(cdata);
cdata->_loaded_from_image = true;
}
do_modify_ram_mipmap_image(cdata, n);
// Ensure the PNMImage is an appropriate size.
int x_size = do_get_expected_mipmap_x_size(cdata, n);
int y_size = do_get_expected_mipmap_y_size(cdata, n);
if (pnmimage.get_x_size() != x_size ||
pnmimage.get_y_size() != y_size) {
gobj_cat.info()
<< "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";
PNMImage scaled(x_size, y_size, pnmimage.get_num_channels(),
pnmimage.get_maxval(), pnmimage.get_type());
scaled.quick_filter_from(pnmimage);
Thread::consider_yield();
convert_from_pnmimage(cdata->_ram_images[n]._image,
do_get_expected_ram_mipmap_page_size(cdata, n),
x_size, 0, 0, z, scaled,
cdata->_num_components, cdata->_component_width);
} else {
// Now copy the pixel data from the PNMImage into our internal
// cdata->_image component.
convert_from_pnmimage(cdata->_ram_images[n]._image,
do_get_expected_ram_mipmap_page_size(cdata, n),
x_size, 0, 0, z, pnmimage,
cdata->_num_components, cdata->_component_width);
}
Thread::consider_yield();
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_load_one
// Access: Protected, Virtual
// Description: Internal method to load a single page or mipmap
// level.
////////////////////////////////////////////////////////////////////
bool Texture::
do_load_one(CData *cdata, const PfmFile &pfm, const string &name, int z, int n,
const LoaderOptions &options) {
if (cdata->_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
// image properties like size and number of components.
if (!do_reconsider_z_size(cdata, z, options)) {
return false;
}
nassertr(z >= 0 && z < cdata->_z_size * cdata->_num_views, false);
if (z == 0) {
ComponentType component_type = T_float;
if (!do_reconsider_image_properties(cdata, pfm.get_x_size(), pfm.get_y_size(),
pfm.get_num_channels(), component_type,
z, options)) {
return false;
}
}
do_modify_ram_image(cdata);
cdata->_loaded_from_image = true;
}
do_modify_ram_mipmap_image(cdata, n);
// Ensure the PfmFile is an appropriate size.
int x_size = do_get_expected_mipmap_x_size(cdata, n);
int y_size = do_get_expected_mipmap_y_size(cdata, n);
if (pfm.get_x_size() != x_size ||
pfm.get_y_size() != y_size) {
gobj_cat.info()
<< "Automatically rescaling " << name;
if (n != 0) {
gobj_cat.info(false)
<< " mipmap level " << n;
}
gobj_cat.info(false)
<< " from " << pfm.get_x_size() << " by "
<< pfm.get_y_size() << " to " << x_size << " by "
<< y_size << "\n";
PfmFile scaled(pfm);
scaled.resize(x_size, y_size);
Thread::consider_yield();
convert_from_pfm(cdata->_ram_images[n]._image,
do_get_expected_ram_mipmap_page_size(cdata, n), z,
scaled, cdata->_num_components, cdata->_component_width);
} else {
// Now copy the pixel data from the PfmFile into our internal
// cdata->_image component.
convert_from_pfm(cdata->_ram_images[n]._image,
do_get_expected_ram_mipmap_page_size(cdata, n), z,
pfm, cdata->_num_components, cdata->_component_width);
}
Thread::consider_yield();
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_load_sub_image
// Access: Protected, Virtual
// Description: Internal method to load an image into a section of
// a texture page or mipmap level.
////////////////////////////////////////////////////////////////////
bool Texture::
do_load_sub_image(CData *cdata, const PNMImage &image, int x, int y, int z, int n) {
nassertr(n >= 0 && n < cdata->_ram_images.size(), false);
int tex_x_size = do_get_expected_mipmap_x_size(cdata, n);
int tex_y_size = do_get_expected_mipmap_y_size(cdata, n);
int tex_z_size = do_get_expected_mipmap_z_size(cdata, n);
nassertr(x >= 0 && x < tex_x_size, false);
nassertr(y >= 0 && y < tex_y_size, false);
nassertr(z >= 0 && z < tex_z_size, false);
nassertr(image.get_x_size() + x < tex_x_size, false);
nassertr(image.get_y_size() + y < tex_y_size, false);
// Flip y
y = cdata->_y_size - (image.get_y_size() + y);
cdata->inc_image_modified();
do_modify_ram_mipmap_image(cdata, n);
convert_from_pnmimage(cdata->_ram_images[n]._image,
do_get_expected_ram_mipmap_page_size(cdata, n),
tex_x_size, x, y, z, image,
cdata->_num_components, cdata->_component_width);
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_read_txo_file
// Access: Protected
// Description: Called internally when read() detects a txo file.
// Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
bool Texture::
do_read_txo_file(CData *cdata, const Filename &fullpath) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
Filename filename = Filename::binary_filename(fullpath);
PT(VirtualFile) file = vfs->get_file(filename);
if (file == (VirtualFile *)NULL) {
// No such file.
gobj_cat.error()
<< "Could not find " << fullpath << "\n";
return false;
}
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Reading texture object " << filename << "\n";
}
istream *in = file->open_read_file(true);
bool success = do_read_txo(cdata, *in, fullpath);
vfs->close_read_file(in);
cdata->_fullpath = fullpath;
cdata->_alpha_fullpath = Filename();
cdata->_keep_ram_image = false;
return success;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_read_txo
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
bool Texture::
do_read_txo(CData *cdata, istream &in, const string &filename) {
PT(Texture) other = make_from_txo(in, filename);
if (other == (Texture *)NULL) {
return false;
}
CDReader cdata_other(other->_cycler);
Namable::operator = (*other);
do_assign(cdata, other, cdata_other);
cdata->_loaded_from_image = true;
cdata->_loaded_from_txo = true;
cdata->_has_read_pages = false;
cdata->_has_read_mipmaps = false;
cdata->_num_mipmap_levels_read = 0;
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_read_dds_file
// Access: Private
// Description: Called internally when read() detects a DDS file.
// Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
bool Texture::
do_read_dds_file(CData *cdata, const Filename &fullpath, bool header_only) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
Filename filename = Filename::binary_filename(fullpath);
PT(VirtualFile) file = vfs->get_file(filename);
if (file == (VirtualFile *)NULL) {
// No such file.
gobj_cat.error()
<< "Could not find " << fullpath << "\n";
return false;
}
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Reading DDS file " << filename << "\n";
}
istream *in = file->open_read_file(true);
bool success = do_read_dds(cdata, *in, fullpath, header_only);
vfs->close_read_file(in);
if (!has_name()) {
set_name(fullpath.get_basename_wo_extension());
}
cdata->_fullpath = fullpath;
cdata->_alpha_fullpath = Filename();
cdata->_keep_ram_image = false;
return success;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_read_dds
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
bool Texture::
do_read_dds(CData *cdata, istream &in, const string &filename, bool header_only) {
StreamReader dds(in);
// DDS header (19 words)
DDSHeader header;
header.dds_magic = dds.get_uint32();
header.dds_size = dds.get_uint32();
header.dds_flags = dds.get_uint32();
header.height = dds.get_uint32();
header.width = dds.get_uint32();
header.pitch = dds.get_uint32();
header.depth = dds.get_uint32();
header.num_levels = dds.get_uint32();
dds.skip_bytes(44);
// Pixelformat (8 words)
header.pf.pf_size = dds.get_uint32();
header.pf.pf_flags = dds.get_uint32();
header.pf.four_cc = dds.get_uint32();
header.pf.rgb_bitcount = dds.get_uint32();
header.pf.r_mask = dds.get_uint32();
header.pf.g_mask = dds.get_uint32();
header.pf.b_mask = dds.get_uint32();
header.pf.a_mask = dds.get_uint32();
// Caps (4 words)
header.caps.caps1 = dds.get_uint32();
header.caps.caps2 = dds.get_uint32();
header.caps.ddsx = dds.get_uint32();
dds.skip_bytes(4);
// Pad out to 32 words
dds.skip_bytes(4);
if (header.dds_magic != DDS_MAGIC || (in.fail() || in.eof())) {
gobj_cat.error()
<< filename << " is not a DDS file.\n";
return false;
}
if ((header.dds_flags & DDSD_MIPMAPCOUNT) == 0) {
// No bit set means only the base mipmap level.
header.num_levels = 1;
} else if (header.num_levels == 0) {
// Some files seem to have this set to 0 for some reason--existing
// readers assume 0 means 1.
header.num_levels = 1;
}
TextureType texture_type;
if (header.caps.caps2 & DDSCAPS2_CUBEMAP) {
static const unsigned int all_faces =
(DDSCAPS2_CUBEMAP_POSITIVEX |
DDSCAPS2_CUBEMAP_POSITIVEY |
DDSCAPS2_CUBEMAP_POSITIVEZ |
DDSCAPS2_CUBEMAP_NEGATIVEX |
DDSCAPS2_CUBEMAP_NEGATIVEY |
DDSCAPS2_CUBEMAP_NEGATIVEZ);
if ((header.caps.caps2 & all_faces) != all_faces) {
gobj_cat.error()
<< filename << " is missing some cube map faces; cannot load.\n";
return false;
}
header.depth = 6;
texture_type = TT_cube_map;
} else if (header.caps.caps2 & DDSCAPS2_VOLUME) {
texture_type = TT_3d_texture;
} else {
texture_type = TT_2d_texture;
header.depth = 1;
}
// Determine the function to use to read the DDS image.
typedef PTA_uchar (*ReadDDSLevelFunc)(Texture *tex, Texture::CData *cdata,
const DDSHeader &header, int n, istream &in);
ReadDDSLevelFunc func = NULL;
Format format = F_rgb;
do_clear_ram_image(cdata);
CompressionMode compression = CM_off;
if (header.pf.pf_flags & DDPF_FOURCC) {
// Some compressed texture format.
if (texture_type == TT_3d_texture) {
gobj_cat.error()
<< filename << ": unsupported compression on 3-d texture.\n";
return false;
}
if (header.pf.four_cc == 0x31545844) { // 'DXT1', little-endian.
compression = CM_dxt1;
func = read_dds_level_dxt1;
} else if (header.pf.four_cc == 0x32545844) { // 'DXT2'
compression = CM_dxt2;
func = read_dds_level_dxt23;
} else if (header.pf.four_cc == 0x33545844) { // 'DXT3'
compression = CM_dxt3;
func = read_dds_level_dxt23;
} else if (header.pf.four_cc == 0x34545844) { // 'DXT4'
compression = CM_dxt4;
func = read_dds_level_dxt45;
} else if (header.pf.four_cc == 0x35545844) { // 'DXT5'
compression = CM_dxt5;
func = read_dds_level_dxt45;
} else {
gobj_cat.error()
<< filename << ": unsupported texture compression.\n";
return false;
}
// All of the compressed formats support alpha, even DXT1 (to some
// extent, at least).
format = F_rgba;
} else {
// An uncompressed texture format.
func = read_dds_level_generic_uncompressed;
if (header.pf.pf_flags & DDPF_ALPHAPIXELS) {
// An uncompressed format that involves alpha.
format = F_rgba;
if (header.pf.rgb_bitcount == 32 &&
header.pf.r_mask == 0x000000ff &&
header.pf.g_mask == 0x0000ff00 &&
header.pf.b_mask == 0x00ff0000 &&
header.pf.a_mask == 0xff000000U) {
func = read_dds_level_abgr8;
} else if (header.pf.rgb_bitcount == 32 &&
header.pf.r_mask == 0x00ff0000 &&
header.pf.g_mask == 0x0000ff00 &&
header.pf.b_mask == 0x000000ff &&
header.pf.a_mask == 0xff000000U) {
func = read_dds_level_rgba8;
} else if (header.pf.r_mask != 0 &&
header.pf.g_mask == 0 &&
header.pf.b_mask == 0) {
func = read_dds_level_luminance_uncompressed;
format = F_luminance_alpha;
}
} else {
// An uncompressed format that doesn't involve alpha.
if (header.pf.rgb_bitcount == 24 &&
header.pf.r_mask == 0x00ff0000 &&
header.pf.g_mask == 0x0000ff00 &&
header.pf.b_mask == 0x000000ff) {
func = read_dds_level_bgr8;
} else if (header.pf.rgb_bitcount == 24 &&
header.pf.r_mask == 0x000000ff &&
header.pf.g_mask == 0x0000ff00 &&
header.pf.b_mask == 0x00ff0000) {
func = read_dds_level_rgb8;
} else if (header.pf.r_mask != 0 &&
header.pf.g_mask == 0 &&
header.pf.b_mask == 0) {
func = read_dds_level_luminance_uncompressed;
format = F_luminance;
}
}
}
do_setup_texture(cdata, texture_type, header.width, header.height, header.depth,
T_unsigned_byte, format);
cdata->_orig_file_x_size = cdata->_x_size;
cdata->_orig_file_y_size = cdata->_y_size;
cdata->_compression = compression;
cdata->_ram_image_compression = compression;
if (!header_only) {
switch (texture_type) {
case TT_3d_texture:
{
// 3-d textures store all the depth slices for mipmap level 0,
// then all the depth slices for mipmap level 1, and so on.
for (int n = 0; n < (int)header.num_levels; ++n) {
int z_size = do_get_expected_mipmap_z_size(cdata, n);
pvector<PTA_uchar> pages;
size_t page_size = 0;
int z;
for (z = 0; z < z_size; ++z) {
PTA_uchar page = func(this, cdata, header, n, in);
if (page.is_null()) {
return false;
}
nassertr(page_size == 0 || page_size == page.size(), false);
page_size = page.size();
pages.push_back(page);
}
// Now reassemble the pages into one big image. Because
// this is a Microsoft format, the images are stacked in
// reverse order; re-reverse them.
PTA_uchar image = PTA_uchar::empty_array(page_size * z_size);
unsigned char *imagep = (unsigned char *)image.p();
for (z = 0; z < z_size; ++z) {
int fz = z_size - 1 - z;
memcpy(imagep + z * page_size, pages[fz].p(), page_size);
}
do_set_ram_mipmap_image(cdata, n, image, page_size);
}
}
break;
case TT_cube_map:
{
// Cube maps store all the mipmap levels for face 0, then all
// the mipmap levels for face 1, and so on.
pvector<pvector<PTA_uchar> > pages;
pages.reserve(6);
int z, n;
for (z = 0; z < 6; ++z) {
pages.push_back(pvector<PTA_uchar>());
pvector<PTA_uchar> &levels = pages.back();
levels.reserve(header.num_levels);
for (n = 0; n < (int)header.num_levels; ++n) {
PTA_uchar image = func(this, cdata, header, n, in);
if (image.is_null()) {
return false;
}
levels.push_back(image);
}
}
// Now, for each level, reassemble the pages into one big
// image. Because this is a Microsoft format, the levels are
// arranged in a rotated order.
static const int level_remap[6] = {
0, 1, 5, 4, 2, 3
};
for (n = 0; n < (int)header.num_levels; ++n) {
size_t page_size = pages[0][n].size();
PTA_uchar image = PTA_uchar::empty_array(page_size * 6);
unsigned char *imagep = (unsigned char *)image.p();
for (z = 0; z < 6; ++z) {
int fz = level_remap[z];
nassertr(pages[fz][n].size() == page_size, false);
memcpy(imagep + z * page_size, pages[fz][n].p(), page_size);
}
do_set_ram_mipmap_image(cdata, n, image, page_size);
}
}
break;
default:
// Normal 2-d textures simply store the mipmap levels.
{
for (int n = 0; n < (int)header.num_levels; ++n) {
PTA_uchar image = func(this, cdata, header, n, in);
if (image.is_null()) {
return false;
}
do_set_ram_mipmap_image(cdata, n, image, 0);
}
}
}
cdata->_has_read_pages = true;
cdata->_has_read_mipmaps = true;
cdata->_num_mipmap_levels_read = cdata->_ram_images.size();
}
if (in.fail() || in.eof()) {
gobj_cat.error()
<< filename << ": truncated DDS file.\n";
return false;
}
cdata->_loaded_from_image = true;
cdata->_loaded_from_txo = true;
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_write
// Access: Protected
// Description: Internal method to write a series of pages and/or
// mipmap levels to disk files.
////////////////////////////////////////////////////////////////////
bool Texture::
do_write(CData *cdata,
const Filename &fullpath, int z, int n, bool write_pages, bool write_mipmaps) {
if (is_txo_filename(fullpath)) {
if (!do_has_bam_rawdata(cdata)) {
do_get_bam_rawdata(cdata);
}
nassertr(do_has_bam_rawdata(cdata), false);
return do_write_txo_file(cdata, fullpath);
}
if (!do_has_uncompressed_ram_image(cdata)) {
do_get_uncompressed_ram_image(cdata);
}
nassertr(do_has_ram_mipmap_image(cdata, n), false);
nassertr(cdata->_ram_image_compression == CM_off, false);
if (write_pages && write_mipmaps) {
// Write a sequence of pages * mipmap levels.
Filename fullpath_pattern = Filename::pattern_filename(fullpath);
int num_levels = cdata->_ram_images.size();
for (int n = 0; n < num_levels; ++n) {
int num_pages = do_get_expected_mipmap_num_pages(cdata, n);
for (z = 0; z < num_pages; ++z) {
Filename n_pattern = Filename::pattern_filename(fullpath_pattern.get_filename_index(z));
if (!n_pattern.has_hash()) {
gobj_cat.error()
<< "Filename requires two different hash sequences: " << fullpath
<< "\n";
return false;
}
if (!do_write_one(cdata, n_pattern.get_filename_index(n), z, n)) {
return false;
}
}
}
} else if (write_pages) {
// Write a sequence of pages.
Filename fullpath_pattern = Filename::pattern_filename(fullpath);
if (!fullpath_pattern.has_hash()) {
gobj_cat.error()
<< "Filename requires a hash mark: " << fullpath
<< "\n";
return false;
}
int num_pages = cdata->_z_size * cdata->_num_views;
for (z = 0; z < num_pages; ++z) {
if (!do_write_one(cdata, fullpath_pattern.get_filename_index(z), z, n)) {
return false;
}
}
} else if (write_mipmaps) {
// Write a sequence of mipmap images.
Filename fullpath_pattern = Filename::pattern_filename(fullpath);
if (!fullpath_pattern.has_hash()) {
gobj_cat.error()
<< "Filename requires a hash mark: " << fullpath
<< "\n";
return false;
}
int num_levels = cdata->_ram_images.size();
for (int n = 0; n < num_levels; ++n) {
if (!do_write_one(cdata, fullpath_pattern.get_filename_index(n), z, n)) {
return false;
}
}
} else {
// Write a single file.
if (!do_write_one(cdata, fullpath, z, n)) {
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_write_one
// Access: Protected
// Description: Internal method to write the indicated page and
// mipmap level to a disk image file.
////////////////////////////////////////////////////////////////////
bool Texture::
do_write_one(CData *cdata, const Filename &fullpath, int z, int n) {
if (!do_has_ram_mipmap_image(cdata, n)) {
return false;
}
nassertr(cdata->_ram_image_compression == CM_off, false);
bool success;
if (cdata->_component_type == T_float) {
// Writing a floating-point texture.
PfmFile pfm;
if (!do_store_one(cdata, pfm, z, n)) {
return false;
}
success = pfm.write(fullpath);
} else {
// Writing a normal, integer texture.
PNMImage pnmimage;
if (!do_store_one(cdata, pnmimage, z, n)) {
return false;
}
success = pnmimage.write(fullpath);
}
if (!success) {
gobj_cat.error()
<< "Texture::write() - couldn't write: " << fullpath << endl;
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_store_one
// Access: Protected
// Description: Internal method to copy a page and/or mipmap level to
// a PNMImage.
////////////////////////////////////////////////////////////////////
bool Texture::
do_store_one(CData *cdata, PNMImage &pnmimage, int z, int n) {
// First, reload the ram image if necessary.
do_get_uncompressed_ram_image(cdata);
if (!do_has_ram_mipmap_image(cdata, n)) {
return false;
}
nassertr(z >= 0 && z < do_get_expected_mipmap_num_pages(cdata, n), false);
nassertr(cdata->_ram_image_compression == CM_off, false);
if (cdata->_component_type == T_float) {
// PNMImage by way of PfmFile.
PfmFile pfm;
bool success = convert_to_pfm(pfm,
do_get_expected_mipmap_x_size(cdata, n),
do_get_expected_mipmap_y_size(cdata, n),
cdata->_num_components, cdata->_component_width,
cdata->_ram_images[n]._image,
do_get_ram_mipmap_page_size(cdata, n), z);
if (!success) {
return false;
}
return pfm.store(pnmimage);
}
return convert_to_pnmimage(pnmimage,
do_get_expected_mipmap_x_size(cdata, n),
do_get_expected_mipmap_y_size(cdata, n),
cdata->_num_components, cdata->_component_width,
cdata->_ram_images[n]._image,
do_get_ram_mipmap_page_size(cdata, n), z);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_store_one
// Access: Protected
// Description: Internal method to copy a page and/or mipmap level to
// a PfmFile.
////////////////////////////////////////////////////////////////////
bool Texture::
do_store_one(CData *cdata, PfmFile &pfm, int z, int n) {
// First, reload the ram image if necessary.
do_get_uncompressed_ram_image(cdata);
if (!do_has_ram_mipmap_image(cdata, n)) {
return false;
}
nassertr(z >= 0 && z < do_get_expected_mipmap_num_pages(cdata, n), false);
nassertr(cdata->_ram_image_compression == CM_off, false);
if (cdata->_component_type != T_float) {
// PfmFile by way of PNMImage.
PNMImage pnmimage;
bool success = convert_to_pnmimage(pnmimage,
do_get_expected_mipmap_x_size(cdata, n),
do_get_expected_mipmap_y_size(cdata, n),
cdata->_num_components, cdata->_component_width,
cdata->_ram_images[n]._image,
do_get_ram_mipmap_page_size(cdata, n), z);
if (!success) {
return false;
}
return pfm.load(pnmimage);
}
return convert_to_pfm(pfm,
do_get_expected_mipmap_x_size(cdata, n),
do_get_expected_mipmap_y_size(cdata, n),
cdata->_num_components, cdata->_component_width,
cdata->_ram_images[n]._image,
do_get_ram_mipmap_page_size(cdata, n), z);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_write_txo_file
// Access: Private
// Description: Called internally when write() detects a txo
// filename.
////////////////////////////////////////////////////////////////////
bool Texture::
do_write_txo_file(const CData *cdata, const Filename &fullpath) const {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
Filename filename = Filename::binary_filename(fullpath);
ostream *out = vfs->open_write_file(filename, true, true);
if (out == NULL) {
gobj_cat.error()
<< "Unable to open " << filename << "\n";
return false;
}
bool success = do_write_txo(cdata, *out, fullpath);
vfs->close_write_file(out);
return success;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_write_txo
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
bool Texture::
do_write_txo(const CData *cdata, ostream &out, const string &filename) const {
DatagramOutputFile dout;
if (!dout.open(out, filename)) {
gobj_cat.error()
<< "Could not write texture object: " << filename << "\n";
return false;
}
if (!dout.write_header(_bam_header)) {
gobj_cat.error()
<< "Unable to write to " << filename << "\n";
return false;
}
BamWriter writer(&dout);
if (!writer.init()) {
return false;
}
writer.set_file_texture_mode(BamWriter::BTM_rawdata);
if (!writer.write_object(this)) {
return false;
}
if (!do_has_bam_rawdata(cdata)) {
gobj_cat.error()
<< get_name() << " does not have ram image\n";
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::unlocked_ensure_ram_image
// Access: Protected, Virtual
// Description: If the texture has a ram image already, this acquires
// the CData write lock and returns it.
//
// If the texture lacks a ram image, this performs
// do_reload_ram_image(), but without holding the lock
// on this particular Texture object, to avoid holding
// the lock across what might be a slow operation.
// Instead, the reload is performed in a copy of the
// texture object, and then the lock is acquired and the
// data is copied in.
//
// In any case, the return value is a locked CData
// object, which must be released with an explicit call
// to release_write(). The CData object will have a ram
// image unless for some reason do_reload_ram_image()
// fails.
////////////////////////////////////////////////////////////////////
Texture::CData *Texture::
unlocked_ensure_ram_image(bool allow_compression) {
Thread *current_thread = Thread::get_current_thread();
// First, wait for any other threads that might be simultaneously
// performing the same operation.
MutexHolder holder(_lock);
while (_reloading) {
_cvar.wait();
}
// Then make sure we still need to reload before continuing.
const CData *cdata = _cycler.read(current_thread);
bool has_ram_image = do_has_ram_image(cdata);
if (has_ram_image && !allow_compression && cdata->_ram_image_compression != Texture::CM_off) {
// If we don't want compression, but the ram image we have is
// pre-compressed, we don't consider it.
has_ram_image = false;
}
if (has_ram_image || !do_can_reload(cdata)) {
// We don't need to reload after all, or maybe we can't reload
// anyway. Return, but elevate the lock first, as we promised.
return _cycler.elevate_read_upstream(cdata, false, current_thread);
}
// We need to reload.
nassertr(!_reloading, NULL);
_reloading = true;
PT(Texture) tex = do_make_copy(cdata);
_cycler.release_read(cdata);
_lock.release();
// Perform the actual reload in a copy of the texture, while our
// own mutex is left unlocked.
CDWriter cdata_tex(tex->_cycler, true);
tex->do_reload_ram_image(cdata_tex, allow_compression);
_lock.acquire();
CData *cdataw = _cycler.write_upstream(false, current_thread);
// Rather than calling do_assign(), which would copy *all* of the
// reloaded texture's properties over, we only copy in the ones
// which are relevant to the ram image. This way, if the
// properties have changed during the reload (for instance,
// because we reloaded a txo), it won't contaminate the original
// texture.
cdataw->_orig_file_x_size = cdata_tex->_orig_file_x_size;
cdataw->_orig_file_y_size = cdata_tex->_orig_file_y_size;
// If any of *these* properties have changed, the texture has
// changed in some fundamental way. Update it appropriately.
if (cdata_tex->_x_size != cdataw->_x_size ||
cdata_tex->_y_size != cdataw->_y_size ||
cdata_tex->_z_size != cdataw->_z_size ||
cdata_tex->_num_views != cdataw->_num_views ||
cdata_tex->_num_components != cdataw->_num_components ||
cdata_tex->_component_width != cdataw->_component_width ||
cdata_tex->_texture_type != cdataw->_texture_type ||
cdata_tex->_component_type != cdataw->_component_type) {
cdataw->_x_size = cdata_tex->_x_size;
cdataw->_y_size = cdata_tex->_y_size;
cdataw->_z_size = cdata_tex->_z_size;
cdataw->_num_views = cdata_tex->_num_views;
cdataw->_num_components = cdata_tex->_num_components;
cdataw->_component_width = cdata_tex->_component_width;
cdataw->_texture_type = cdata_tex->_texture_type;
cdataw->_format = cdata_tex->_format;
cdataw->_component_type = cdata_tex->_component_type;
cdataw->inc_properties_modified();
cdataw->inc_image_modified();
}
cdataw->_keep_ram_image = cdata_tex->_keep_ram_image;
cdataw->_ram_image_compression = cdata_tex->_ram_image_compression;
cdataw->_ram_images = cdata_tex->_ram_images;
nassertr(_reloading, NULL);
_reloading = false;
// We don't generally increment the cdata->_image_modified semaphore,
// because this is just a reload, and presumably the image hasn't
// changed (unless we hit the if condition above).
_cvar.notify_all();
// Return the still-locked cdata.
return cdataw;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_reload_ram_image
// Access: Protected, Virtual
// Description: Called when the Texture image is required but the ram
// image is not available, this will reload it from disk
// or otherwise do whatever is required to make it
// available, if possible.
//
// Assumes the lock is already held. The lock will be
// held during the duration of this operation.
////////////////////////////////////////////////////////////////////
void Texture::
do_reload_ram_image(CData *cdata, bool allow_compression) {
BamCache *cache = BamCache::get_global_ptr();
PT(BamCacheRecord) record;
if (!do_has_compression(cdata)) {
allow_compression = false;
}
if ((cache->get_cache_textures() || (allow_compression && cache->get_cache_compressed_textures())) && !textures_header_only) {
// See if the texture can be found in the on-disk cache, if it is
// active.
record = cache->lookup(cdata->_fullpath, "txo");
if (record != (BamCacheRecord *)NULL &&
record->has_data()) {
PT(Texture) tex = DCAST(Texture, record->get_data());
// But don't use the cache record if the config parameters have
// changed, and we want a different-sized texture now.
int x_size = cdata->_orig_file_x_size;
int y_size = cdata->_orig_file_y_size;
do_adjust_this_size(cdata, x_size, y_size, cdata->_filename.get_basename(), true);
if (x_size != tex->get_x_size() || y_size != tex->get_y_size()) {
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Cached texture " << *this << " has size "
<< tex->get_x_size() << " x " << tex->get_y_size()
<< " instead of " << x_size << " x " << y_size
<< "; ignoring cache.\n";
}
} else {
// Also don't keep the cached version if it's compressed but
// we want uncompressed.
if (!allow_compression && tex->get_ram_image_compression() != Texture::CM_off) {
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Cached texture " << *this
<< " is compressed in cache; ignoring cache.\n";
}
} else {
gobj_cat.info()
<< "Texture " << get_name() << " reloaded from disk cache\n";
// We don't want to replace all the texture parameters--for
// instance, we don't want to change the filter type or the
// border color or anything--we just want to get the image and
// necessary associated parameters.
CDReader cdata_tex(tex->_cycler);
cdata->_x_size = cdata_tex->_x_size;
cdata->_y_size = cdata_tex->_y_size;
if (cdata->_num_components != cdata_tex->_num_components) {
cdata->_num_components = cdata_tex->_num_components;
cdata->_format = cdata_tex->_format;
}
cdata->_component_type = cdata_tex->_component_type;
cdata->_compression = cdata_tex->_compression;
cdata->_ram_image_compression = cdata_tex->_ram_image_compression;
cdata->_ram_images = cdata_tex->_ram_images;
cdata->_loaded_from_image = true;
bool was_compressed = (cdata->_ram_image_compression != CM_off);
if (do_consider_auto_process_ram_image(cdata, uses_mipmaps(), allow_compression)) {
bool is_compressed = (cdata->_ram_image_compression != CM_off);
if (!was_compressed && is_compressed &&
cache->get_cache_compressed_textures()) {
// We've re-compressed the image after loading it from the
// cache. To keep the cache current, rewrite it to the
// cache now, in its newly compressed form.
record->set_data(this, this);
cache->store(record);
}
}
return;
}
}
}
}
gobj_cat.info()
<< "Reloading texture " << get_name() << "\n";
int z = 0;
int n = 0;
if (cdata->_has_read_pages) {
z = cdata->_z_size;
}
if (cdata->_has_read_mipmaps) {
n = cdata->_num_mipmap_levels_read;
}
cdata->_loaded_from_image = false;
Format orig_format = cdata->_format;
int orig_num_components = cdata->_num_components;
LoaderOptions options;
options.set_texture_flags(LoaderOptions::TF_preload);
do_read(cdata, cdata->_fullpath, cdata->_alpha_fullpath,
cdata->_primary_file_num_channels, cdata->_alpha_file_channel,
z, n, cdata->_has_read_pages, cdata->_has_read_mipmaps, options, NULL);
if (orig_num_components == cdata->_num_components) {
// Restore the original format, in case it was needlessly changed
// during the reload operation.
cdata->_format = orig_format;
}
if (do_has_ram_image(cdata) && record != (BamCacheRecord *)NULL) {
if (cache->get_cache_textures() || (cdata->_ram_image_compression != CM_off && cache->get_cache_compressed_textures())) {
// Update the cache.
if (record != (BamCacheRecord *)NULL) {
record->add_dependent_file(cdata->_fullpath);
}
record->set_data(this, this);
cache->store(record);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_modify_ram_image
// Access: Protected
// Description: This is called internally to uniquify the ram image
// pointer without updating cdata->_image_modified.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
do_modify_ram_image(CData *cdata) {
if (cdata->_ram_images.empty() || cdata->_ram_images[0]._image.empty() ||
cdata->_ram_image_compression != CM_off) {
do_make_ram_image(cdata);
} else {
do_clear_ram_mipmap_images(cdata);
}
return cdata->_ram_images[0]._image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_make_ram_image
// Access: Protected
// Description: This is called internally to make a new ram image
// without updating cdata->_image_modified.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
do_make_ram_image(CData *cdata) {
cdata->_ram_images.clear();
cdata->_ram_images.push_back(RamImage());
cdata->_ram_images[0]._page_size = do_get_expected_ram_page_size(cdata);
cdata->_ram_images[0]._image = PTA_uchar::empty_array(do_get_expected_ram_image_size(cdata), get_class_type());
cdata->_ram_images[0]._pointer_image = NULL;
cdata->_ram_image_compression = CM_off;
return cdata->_ram_images[0]._image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_ram_image
// Access: Protected
// Description: Replaces the current system-RAM image with the new
// data. If compression is not CM_off, it indicates
// that the new data is already pre-compressed in the
// indicated format.
//
// This does *not* affect keep_ram_image.
////////////////////////////////////////////////////////////////////
void Texture::
do_set_ram_image(CData *cdata, CPTA_uchar image, Texture::CompressionMode compression,
size_t page_size) {
nassertv(compression != CM_default);
nassertv(compression != CM_off || image.size() == do_get_expected_ram_image_size(cdata));
if (cdata->_ram_images.empty()) {
cdata->_ram_images.push_back(RamImage());
} else {
do_clear_ram_mipmap_images(cdata);
}
if (page_size == 0) {
page_size = image.size();
}
if (cdata->_ram_images[0]._image != image ||
cdata->_ram_images[0]._page_size != page_size ||
cdata->_ram_image_compression != compression) {
cdata->_ram_images[0]._image = image.cast_non_const();
cdata->_ram_images[0]._page_size = page_size;
cdata->_ram_images[0]._pointer_image = NULL;
cdata->_ram_image_compression = compression;
cdata->inc_image_modified();
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_modify_ram_mipmap_image
// Access: Protected
// Description: This is called internally to uniquify the nth mipmap
// image pointer without updating cdata->_image_modified.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
do_modify_ram_mipmap_image(CData *cdata, int n) {
nassertr(cdata->_ram_image_compression == CM_off, PTA_uchar());
if (n >= (int)cdata->_ram_images.size() ||
cdata->_ram_images[n]._image.empty()) {
do_make_ram_mipmap_image(cdata, n);
}
return cdata->_ram_images[n]._image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_make_ram_mipmap_image
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
do_make_ram_mipmap_image(CData *cdata, int n) {
nassertr(cdata->_ram_image_compression == CM_off, PTA_uchar(get_class_type()));
while (n >= (int)cdata->_ram_images.size()) {
cdata->_ram_images.push_back(RamImage());
}
cdata->_ram_images[n]._image = PTA_uchar::empty_array(do_get_expected_ram_mipmap_image_size(cdata, n), get_class_type());
cdata->_ram_images[n]._pointer_image = NULL;
cdata->_ram_images[n]._page_size = do_get_expected_ram_mipmap_page_size(cdata, n);
return cdata->_ram_images[n]._image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_ram_mipmap_image
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_ram_mipmap_image(CData *cdata, int n, CPTA_uchar image, size_t page_size) {
nassertv(cdata->_ram_image_compression != CM_off || image.size() == do_get_expected_ram_mipmap_image_size(cdata, n));
while (n >= (int)cdata->_ram_images.size()) {
cdata->_ram_images.push_back(RamImage());
}
if (page_size == 0) {
page_size = image.size();
}
if (cdata->_ram_images[n]._image != image ||
cdata->_ram_images[n]._page_size != page_size) {
cdata->_ram_images[n]._image = image.cast_non_const();
cdata->_ram_images[n]._pointer_image = NULL;
cdata->_ram_images[n]._page_size = page_size;
cdata->inc_image_modified();
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::consider_auto_process_ram_image
// Access: Protected
// Description: Should be called after a texture has been loaded into
// RAM, this considers generating mipmaps and/or
// compressing the RAM image.
//
// Returns true if the image was modified by this
// operation, false if it wasn't.
////////////////////////////////////////////////////////////////////
bool Texture::
consider_auto_process_ram_image(bool generate_mipmaps, bool allow_compression) {
CDWriter cdata(_cycler, false);
return do_consider_auto_process_ram_image(cdata, generate_mipmaps, allow_compression);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_consider_auto_process_ram_image
// Access: Protected
// Description: Should be called after a texture has been loaded into
// RAM, this considers generating mipmaps and/or
// compressing the RAM image.
//
// Returns true if the image was modified by this
// operation, false if it wasn't.
////////////////////////////////////////////////////////////////////
bool Texture::
do_consider_auto_process_ram_image(CData *cdata, bool generate_mipmaps,
bool allow_compression) {
bool modified = false;
if (generate_mipmaps && !driver_generate_mipmaps &&
cdata->_ram_images.size() == 1) {
do_generate_ram_mipmap_images(cdata);
modified = true;
}
if (allow_compression && !driver_compress_textures) {
CompressionMode compression = cdata->_compression;
if (compression == CM_default && compressed_textures) {
compression = CM_on;
}
if (compression != CM_off && cdata->_ram_image_compression == CM_off) {
GraphicsStateGuardianBase *gsg = GraphicsStateGuardianBase::get_default_gsg();
if (do_compress_ram_image(cdata, compression, QL_default, gsg)) {
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Compressed " << get_name() << " with "
<< cdata->_ram_image_compression << "\n";
}
modified = true;
}
}
}
return modified;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_compress_ram_image
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
bool Texture::
do_compress_ram_image(CData *cdata, Texture::CompressionMode compression,
Texture::QualityLevel quality_level,
GraphicsStateGuardianBase *gsg) {
nassertr(compression != CM_off, false);
if (compression == CM_on) {
// Select an appropriate compression mode automatically.
switch (cdata->_format) {
case Texture::F_rgbm:
case Texture::F_rgb:
case Texture::F_rgb5:
case Texture::F_rgba5:
case Texture::F_rgb8:
case Texture::F_rgb12:
case Texture::F_rgb332:
case Texture::F_rgb16:
case Texture::F_rgb32:
if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt1)) {
compression = CM_dxt1;
} else if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt3)) {
compression = CM_dxt3;
} else if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt5)) {
compression = CM_dxt5;
}
break;
case Texture::F_rgba4:
if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt3)) {
compression = CM_dxt3;
} else if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt5)) {
compression = CM_dxt5;
}
break;
case Texture::F_rgba:
case Texture::F_rgba8:
case Texture::F_rgba12:
case Texture::F_rgba16:
case Texture::F_rgba32:
if (gsg == NULL || gsg->get_supports_compressed_texture_format(CM_dxt5)) {
compression = CM_dxt5;
}
break;
default:
break;
}
}
// Choose an appropriate quality level.
if (quality_level == Texture::QL_default) {
quality_level = cdata->_quality_level;
}
if (quality_level == Texture::QL_default) {
quality_level = texture_quality_level;
}
#ifdef HAVE_SQUISH
if (cdata->_texture_type != TT_3d_texture &&
cdata->_texture_type != TT_2d_texture_array &&
cdata->_component_type == T_unsigned_byte) {
int squish_flags = 0;
switch (compression) {
case CM_dxt1:
squish_flags |= squish::kDxt1;
break;
case CM_dxt3:
squish_flags |= squish::kDxt3;
break;
case CM_dxt5:
squish_flags |= squish::kDxt5;
break;
default:
break;
}
if (squish_flags != 0) {
// This compression mode is supported by squish; use it.
switch (quality_level) {
case QL_fastest:
squish_flags |= squish::kColourRangeFit;
break;
case QL_normal:
// ColourClusterFit is just too slow for everyday use.
squish_flags |= squish::kColourRangeFit;
// squish_flags |= squish::kColourClusterFit;
break;
case QL_best:
squish_flags |= squish::kColourIterativeClusterFit;
break;
default:
break;
}
if (do_squish(cdata, compression, squish_flags)) {
return true;
}
}
}
#endif // HAVE_SQUISH
return false;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_uncompress_ram_image
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
bool Texture::
do_uncompress_ram_image(CData *cdata) {
#ifdef HAVE_SQUISH
if (cdata->_texture_type != TT_3d_texture &&
cdata->_texture_type != TT_2d_texture_array &&
cdata->_component_type == T_unsigned_byte) {
int squish_flags = 0;
switch (cdata->_ram_image_compression) {
case CM_dxt1:
squish_flags |= squish::kDxt1;
break;
case CM_dxt3:
squish_flags |= squish::kDxt3;
break;
case CM_dxt5:
squish_flags |= squish::kDxt5;
break;
default:
break;
}
if (squish_flags != 0) {
// This compression mode is supported by squish; use it.
if (do_unsquish(cdata, squish_flags)) {
return true;
}
}
}
#endif // HAVE_SQUISH
return false;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_has_all_ram_mipmap_images
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
bool Texture::
do_has_all_ram_mipmap_images(const CData *cdata) const {
if (cdata->_ram_images.empty() || cdata->_ram_images[0]._image.empty()) {
// If we don't even have a base image, the answer is no.
return false;
}
if (!uses_mipmaps()) {
// If we have a base image and don't require mipmapping, the
// answer is yes.
return true;
}
// Check that we have enough mipmap levels to meet the size
// requirements.
int size = max(cdata->_x_size, max(cdata->_y_size, cdata->_z_size));
int n = 0;
int x = 1;
while (x < size) {
x = (x << 1);
++n;
if (n >= (int)cdata->_ram_images.size() || cdata->_ram_images[n]._image.empty()) {
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_reconsider_z_size
// Access: Protected
// Description: Considers whether the z_size (or num_views) should
// automatically be adjusted when the user loads a new
// page. Returns true if the z size is valid, false
// otherwise.
//
// Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
bool Texture::
do_reconsider_z_size(CData *cdata, int z, const LoaderOptions &options) {
if (z >= cdata->_z_size * cdata->_num_views) {
bool num_views_specified = true;
if (options.get_texture_flags() & LoaderOptions::TF_multiview) {
// This flag is false if is a multiview texture with a specified
// number of views. It is true if it is not a multiview
// texture, or if it is but the number of views is explicitly
// specified.
num_views_specified = (options.get_texture_num_views() != 0);
}
if (num_views_specified &&
(cdata->_texture_type == Texture::TT_3d_texture ||
cdata->_texture_type == Texture::TT_2d_texture_array)) {
// If we're loading a page past _z_size, treat it as an implicit
// request to enlarge _z_size. However, this is only legal if
// this is, in fact, a 3-d texture or a 2d texture array (cube maps
// always have z_size 6, and other types have z_size 1).
nassertr(cdata->_num_views != 0, false);
cdata->_z_size = (z / cdata->_num_views) + 1;
} else if (cdata->_z_size != 0) {
// In the case of a 2-d texture or cube map, or a 3-d texture
// with an unspecified _num_views, assume we're loading views of
// a multiview texture.
cdata->_num_views = (z / cdata->_z_size) + 1;
} else {
// The first image loaded sets an implicit z-size.
cdata->_z_size = 1;
}
// Increase the size of the data buffer to make room for the new
// texture level.
do_allocate_pages(cdata);
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_allocate_pages
// Access: Protected, Virtual
// Description: Called internally by do_reconsider_z_size() to
// allocate new memory in _ram_images[0] for the new
// number of pages.
//
// Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
void Texture::
do_allocate_pages(CData *cdata) {
size_t new_size = do_get_expected_ram_image_size(cdata);
if (!cdata->_ram_images.empty() &&
!cdata->_ram_images[0]._image.empty() &&
new_size > cdata->_ram_images[0]._image.size()) {
cdata->_ram_images[0]._image.insert(cdata->_ram_images[0]._image.end(), new_size - cdata->_ram_images[0]._image.size(), 0);
nassertv(cdata->_ram_images[0]._image.size() == new_size);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_reconsider_image_properties
// Access: Protected
// Description: Resets the internal Texture properties when a new
// image file is loaded. Returns true if the new image
// is valid, false otherwise.
//
// Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
bool Texture::
do_reconsider_image_properties(CData *cdata, int x_size, int y_size, int num_components,
Texture::ComponentType component_type, int z,
const LoaderOptions &options) {
if (!cdata->_loaded_from_image || num_components != cdata->_num_components || component_type != cdata->_component_type) {
// Come up with a default format based on the number of channels.
// But only do this the first time the file is loaded, or if the
// number of channels in the image changes on subsequent loads.
//TODO: handle sRGB properly
switch (num_components) {
case 1:
cdata->_format = F_luminance;
break;
case 2:
cdata->_format = F_luminance_alpha;
break;
case 3:
cdata->_format = F_rgb;
break;
case 4:
cdata->_format = F_rgba;
break;
default:
// Eh?
nassertr(false, false);
cdata->_format = F_rgb;
}
}
if (!cdata->_loaded_from_image) {
if ((options.get_texture_flags() & LoaderOptions::TF_allow_1d) &&
cdata->_texture_type == TT_2d_texture && x_size != 1 && y_size == 1) {
// If we're loading an Nx1 size texture, infer a 1-d texture type.
cdata->_texture_type = TT_1d_texture;
}
#ifndef NDEBUG
if (cdata->_texture_type == TT_1d_texture) {
nassertr(y_size == 1, false);
} else if (cdata->_texture_type == TT_cube_map) {
nassertr(x_size == y_size, false);
}
#endif
if ((cdata->_x_size != x_size)||(cdata->_y_size != y_size)) {
do_set_pad_size(cdata, 0, 0, 0);
}
cdata->_x_size = x_size;
cdata->_y_size = y_size;
cdata->_num_components = num_components;
do_set_component_type(cdata, component_type);
} else {
if (cdata->_x_size != x_size ||
cdata->_y_size != y_size ||
cdata->_num_components != num_components ||
cdata->_component_type != component_type) {
gobj_cat.error()
<< "Texture properties have changed for texture " << get_name()
<< " page " << z << ".\n";
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_rescale_texture
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
bool Texture::
do_rescale_texture(CData *cdata) {
int new_x_size = cdata->_x_size;
int new_y_size = cdata->_y_size;
if (cdata->_z_size * cdata->_num_views != 1) {
nassert_raise("rescale_texture() doesn't support 3-d or multiview textures.");
return false;
}
if (do_adjust_this_size(cdata, new_x_size, new_y_size, get_name(), false)) {
// OK, we have to scale the image.
PNMImage orig_image;
if (!do_store_one(cdata, orig_image, 0, 0)) {
gobj_cat.warning()
<< "Couldn't get image in rescale_texture()\n";
return false;
}
gobj_cat.info()
<< "Resizing " << get_name() << " to " << new_x_size << " x "
<< new_y_size << "\n";
PNMImage new_image(new_x_size, new_y_size, orig_image.get_num_channels(),
orig_image.get_maxval());
new_image.quick_filter_from(orig_image);
do_clear_ram_image(cdata);
cdata->inc_image_modified();
cdata->_x_size = new_x_size;
cdata->_y_size = new_y_size;
if (!do_load_one(cdata, new_image, get_name(), 0, 0, LoaderOptions())) {
return false;
}
return true;
}
// Maybe we should pad the image.
int pad_x_size = 0;
int pad_y_size = 0;
if (do_get_auto_texture_scale(cdata) == ATS_pad) {
new_x_size = cdata->_x_size;
new_y_size = cdata->_y_size;
if (do_adjust_this_size(cdata, new_x_size, new_y_size, get_name(), true)) {
pad_x_size = new_x_size - cdata->_x_size;
pad_y_size = new_y_size - cdata->_y_size;
PNMImage orig_image;
if (!do_store_one(cdata, orig_image, 0, 0)) {
gobj_cat.warning()
<< "Couldn't get image in rescale_texture()\n";
return false;
}
PNMImage new_image(new_x_size, new_y_size, orig_image.get_num_channels(),
orig_image.get_maxval());
new_image.copy_sub_image(orig_image, 0, new_y_size - orig_image.get_y_size());
do_clear_ram_image(cdata);
cdata->_loaded_from_image = false;
cdata->inc_image_modified();
if (!do_load_one(cdata, new_image, get_name(), 0, 0, LoaderOptions())) {
return false;
}
do_set_pad_size(cdata, pad_x_size, pad_y_size, 0);
return true;
}
}
// No changes needed.
return false;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::make_copy_impl
// Access: Protected, Virtual
// Description:
////////////////////////////////////////////////////////////////////
PT(Texture) Texture::
make_copy_impl() const {
CDReader cdata(_cycler);
return do_make_copy(cdata);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_make_copy
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
PT(Texture) Texture::
do_make_copy(const CData *cdata) const {
PT(Texture) tex = new Texture(get_name());
CDWriter cdata_tex(tex->_cycler, true);
tex->do_assign(cdata_tex, this, cdata);
return tex;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_assign
// Access: Protected
// Description: The internal implementation of operator =(). Assumes
// the lock is already held on both Textures.
////////////////////////////////////////////////////////////////////
void Texture::
do_assign(CData *cdata, const Texture *copy, const CData *cdata_copy) {
cdata->do_assign(cdata_copy);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_clear
// Access: Protected, Virtual
// Description: The protected implementation of clear(). Assumes the
// lock is already held.
////////////////////////////////////////////////////////////////////
void Texture::
do_clear(CData *cdata) {
Texture tex;
tex.local_object();
CDReader cdata_tex(tex._cycler);
do_assign(cdata, &tex, cdata_tex);
cdata->inc_properties_modified();
cdata->inc_image_modified();
cdata->inc_simple_image_modified();
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_setup_texture
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_setup_texture(CData *cdata, Texture::TextureType texture_type, int x_size, int y_size,
int z_size, Texture::ComponentType component_type,
Texture::Format format) {
switch (texture_type) {
case TT_1d_texture:
nassertv(y_size == 1 && z_size == 1);
break;
case TT_2d_texture:
nassertv(z_size == 1);
break;
case TT_3d_texture:
break;
case TT_2d_texture_array:
break;
case TT_cube_map:
// Cube maps must always consist of six square images.
nassertv(x_size == y_size && z_size == 6);
// In principle the wrap mode shouldn't mean anything to a cube
// map, but some drivers seem to misbehave if it's other than
// WM_clamp.
cdata->_wrap_u = WM_clamp;
cdata->_wrap_v = WM_clamp;
cdata->_wrap_w = WM_clamp;
break;
}
if (texture_type != TT_2d_texture) {
do_clear_simple_ram_image(cdata);
}
cdata->_texture_type = texture_type;
cdata->_x_size = x_size;
cdata->_y_size = y_size;
cdata->_z_size = z_size;
cdata->_num_views = 1;
do_set_component_type(cdata, component_type);
do_set_format(cdata, format);
do_clear_ram_image(cdata);
do_set_pad_size(cdata, 0, 0, 0);
cdata->_orig_file_x_size = 0;
cdata->_orig_file_y_size = 0;
cdata->_loaded_from_image = false;
cdata->_loaded_from_txo = false;
cdata->_has_read_pages = false;
cdata->_has_read_mipmaps = false;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_format
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_format(CData *cdata, Texture::Format format) {
if (format == cdata->_format) {
return;
}
cdata->_format = format;
cdata->inc_properties_modified();
switch (cdata->_format) {
case F_color_index:
case F_depth_stencil:
case F_depth_component:
case F_depth_component16:
case F_depth_component24:
case F_depth_component32:
case F_red:
case F_green:
case F_blue:
case F_alpha:
case F_luminance:
case F_r16:
case F_sluminance:
case F_r32i:
case F_r32:
cdata->_num_components = 1;
break;
case F_luminance_alpha:
case F_luminance_alphamask:
case F_rg16:
case F_sluminance_alpha:
case F_rg32:
cdata->_num_components = 2;
break;
case F_rgb:
case F_rgb5:
case F_rgb8:
case F_rgb12:
case F_rgb332:
case F_rgb16:
case F_srgb:
case F_rgb32:
cdata->_num_components = 3;
break;
case F_rgba:
case F_rgbm:
case F_rgba4:
case F_rgba5:
case F_rgba8:
case F_rgba12:
case F_rgba16:
case F_rgba32:
case F_srgb_alpha:
cdata->_num_components = 4;
break;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_component_type
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_component_type(CData *cdata, Texture::ComponentType component_type) {
cdata->_component_type = component_type;
switch (component_type) {
case T_unsigned_byte:
cdata->_component_width = 1;
break;
case T_unsigned_short:
cdata->_component_width = 2;
break;
case T_float:
cdata->_component_width = 4;
break;
case T_unsigned_int_24_8:
cdata->_component_width = 4;
break;
case T_int:
cdata->_component_width = 4;
break;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_x_size
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_x_size(CData *cdata, int x_size) {
if (cdata->_x_size != x_size) {
cdata->_x_size = x_size;
cdata->inc_image_modified();
do_clear_ram_image(cdata);
do_set_pad_size(cdata, 0, 0, 0);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_y_size
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_y_size(CData *cdata, int y_size) {
if (cdata->_y_size != y_size) {
nassertv(cdata->_texture_type != Texture::TT_1d_texture || y_size == 1);
cdata->_y_size = y_size;
cdata->inc_image_modified();
do_clear_ram_image(cdata);
do_set_pad_size(cdata, 0, 0, 0);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_z_size
// Access: Protected
// Description: Changes the z size indicated for the texture. This
// also implicitly unloads the texture if it has already
// been loaded.
////////////////////////////////////////////////////////////////////
void Texture::
do_set_z_size(CData *cdata, int z_size) {
if (cdata->_z_size != z_size) {
nassertv((cdata->_texture_type == Texture::TT_3d_texture) ||
(cdata->_texture_type == Texture::TT_cube_map && z_size == 6) ||
(cdata->_texture_type == Texture::TT_2d_texture_array) || (z_size == 1));
cdata->_z_size = z_size;
cdata->inc_image_modified();
do_clear_ram_image(cdata);
do_set_pad_size(cdata, 0, 0, 0);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_num_views
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_num_views(CData *cdata, int num_views) {
nassertv(num_views >= 1);
if (cdata->_num_views != num_views) {
cdata->_num_views = num_views;
if (do_has_ram_image(cdata)) {
cdata->inc_image_modified();
do_clear_ram_image(cdata);
}
do_set_pad_size(cdata, 0, 0, 0);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_wrap_u
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_wrap_u(CData *cdata, Texture::WrapMode wrap) {
if (cdata->_wrap_u != wrap) {
cdata->inc_properties_modified();
cdata->_wrap_u = wrap;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_wrap_v
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_wrap_v(CData *cdata, Texture::WrapMode wrap) {
if (cdata->_wrap_v != wrap) {
cdata->inc_properties_modified();
cdata->_wrap_v = wrap;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_wrap_w
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_wrap_w(CData *cdata, Texture::WrapMode wrap) {
if (cdata->_wrap_w != wrap) {
cdata->inc_properties_modified();
cdata->_wrap_w = wrap;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_minfilter
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_minfilter(CData *cdata, Texture::FilterType filter) {
if (cdata->_minfilter != filter) {
cdata->inc_properties_modified();
cdata->_minfilter = filter;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_magfilter
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_magfilter(CData *cdata, Texture::FilterType filter) {
if (cdata->_magfilter != filter) {
cdata->inc_properties_modified();
cdata->_magfilter = filter;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_anisotropic_degree
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_anisotropic_degree(CData *cdata, int anisotropic_degree) {
if (cdata->_anisotropic_degree != anisotropic_degree) {
cdata->inc_properties_modified();
cdata->_anisotropic_degree = anisotropic_degree;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_border_color
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_border_color(CData *cdata, const LColor &color) {
if (cdata->_border_color != color) {
cdata->inc_properties_modified();
cdata->_border_color = color;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_compression
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_compression(CData *cdata, Texture::CompressionMode compression) {
if (cdata->_compression != compression) {
cdata->inc_properties_modified();
cdata->_compression = compression;
if (do_has_ram_image(cdata)) {
bool has_compression = do_has_compression(cdata);
bool has_ram_image_compression = (cdata->_ram_image_compression != CM_off);
if (has_compression != has_ram_image_compression ||
has_compression) {
// Reload if we're turning compression on or off, or if we're
// changing the compression mode to a different kind of
// compression.
do_reload(cdata);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_quality_level
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_quality_level(CData *cdata, Texture::QualityLevel quality_level) {
if (cdata->_quality_level != quality_level) {
cdata->inc_properties_modified();
cdata->_quality_level = quality_level;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_has_compression
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
bool Texture::
do_has_compression(const CData *cdata) const {
if (cdata->_compression == CM_default) {
return compressed_textures;
} else {
return (cdata->_compression != CM_off);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_has_ram_image
// Access: Protected, Virtual
// Description: The protected implementation of has_ram_image().
// Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
bool Texture::
do_has_ram_image(const CData *cdata) const {
return !cdata->_ram_images.empty() && !cdata->_ram_images[0]._image.empty();
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_has_uncompressed_ram_image
// Access: Protected, Virtual
// Description: The protected implementation of
// has_uncompressed_ram_image(). Assumes the lock is
// already held.
////////////////////////////////////////////////////////////////////
bool Texture::
do_has_uncompressed_ram_image(const CData *cdata) const {
return !cdata->_ram_images.empty() && !cdata->_ram_images[0]._image.empty() && cdata->_ram_image_compression == CM_off;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_get_ram_image
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
CPTA_uchar Texture::
do_get_ram_image(CData *cdata) {
if (!do_has_ram_image(cdata) && do_can_reload(cdata)) {
do_reload_ram_image(cdata, true);
if (do_has_ram_image(cdata)) {
// Normally, we don't update the cdata->_modified semaphores in a do_blah
// method, but we'll make an exception in this case, because it's
// easiest to modify these here, and only when we know it's
// needed.
cdata->inc_image_modified();
cdata->inc_properties_modified();
}
}
if (cdata->_ram_images.empty()) {
return CPTA_uchar(get_class_type());
}
return cdata->_ram_images[0]._image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_get_uncompressed_ram_image
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
CPTA_uchar Texture::
do_get_uncompressed_ram_image(CData *cdata) {
if (!cdata->_ram_images.empty() && cdata->_ram_image_compression != CM_off) {
// We have an image in-ram, but it's compressed. Try to
// uncompress it first.
if (do_uncompress_ram_image(cdata)) {
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Uncompressed " << get_name() << "\n";
}
return cdata->_ram_images[0]._image;
}
}
// Couldn't uncompress the existing image. Try to reload it.
if ((!do_has_ram_image(cdata) || cdata->_ram_image_compression != CM_off) && do_can_reload(cdata)) {
do_reload_ram_image(cdata, false);
}
if (!cdata->_ram_images.empty() && cdata->_ram_image_compression != CM_off) {
// Great, now we have an image.
if (do_uncompress_ram_image(cdata)) {
gobj_cat.info()
<< "Uncompressed " << get_name() << "\n";
return cdata->_ram_images[0]._image;
}
}
if (cdata->_ram_images.empty() || cdata->_ram_image_compression != CM_off) {
return CPTA_uchar(get_class_type());
}
return cdata->_ram_images[0]._image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::get_ram_image_as
// Access: Published
// Description: Returns the uncompressed system-RAM image data
// associated with the texture. Rather than
// just returning a pointer to the data, like
// get_uncompressed_ram_image, this function first
// processes the data and reorders the components
// using the specified format string, and places these
// into a new char array. The 'format' argument should
// specify in which order the components of the texture
// must be. For example, valid format strings are
// "RGBA", "GA", "ABRG" or "AAA". A component can
// also be written as "0" or "1", which means an
// empty/black or a full/white channel, respectively.
// This function is particularly useful to
// copy an image in-memory to a different library
// (for example, PIL or wxWidgets) that require
// a different component order than Panda's internal
// format, BGRA. Note, however, that this conversion
// can still be too slow if you want to do it every
// frame, and should thus be avoided for that purpose.
// The only requirement for the reordering is that
// an uncompressed image must be available. If the
// RAM image is compressed, it will attempt to re-load
// the texture from disk, if it doesn't find an
// uncompressed image there, it will return NULL.
////////////////////////////////////////////////////////////////////
CPTA_uchar Texture::
get_ram_image_as(const string &requested_format) {
CDWriter cdata(_cycler, false);
string format = upcase(requested_format);
// Make sure we can grab something that's uncompressed.
CPTA_uchar data = do_get_uncompressed_ram_image(cdata);
if (data == NULL) {
gobj_cat.error() << "Couldn't find an uncompressed RAM image!\n";
return CPTA_uchar(get_class_type());
}
int imgsize = cdata->_x_size * cdata->_y_size;
nassertr(cdata->_num_components > 0 && cdata->_num_components <= 4, CPTA_uchar(get_class_type()));
nassertr(data.size() == (size_t)(cdata->_component_width * cdata->_num_components * imgsize), CPTA_uchar(get_class_type()));
// Check if the format is already what we have internally.
if ((cdata->_num_components == 1 && format.size() == 1) ||
(cdata->_num_components == 2 && format.size() == 2 && format.at(1) == 'A' && format.at(0) != 'A') ||
(cdata->_num_components == 3 && format == "BGR") ||
(cdata->_num_components == 4 && format == "BGRA")) {
// The format string is already our format, so we just need to copy it.
return CPTA_uchar(data);
}
// Create a new empty array that can hold our image.
PTA_uchar newdata = PTA_uchar::empty_array(imgsize * format.size() * cdata->_component_width, get_class_type());
// These ifs are for optimization of commonly used image types.
if (format == "RGBA" && cdata->_num_components == 4 && cdata->_component_width == 1) {
imgsize *= 4;
for (int p = 0; p < imgsize; p += 4) {
newdata[p ] = data[p + 2];
newdata[p + 1] = data[p + 1];
newdata[p + 2] = data[p ];
newdata[p + 3] = data[p + 3];
}
return newdata;
}
if (format == "RGB" && cdata->_num_components == 3 && cdata->_component_width == 1) {
imgsize *= 3;
for (int p = 0; p < imgsize; p += 3) {
newdata[p ] = data[p + 2];
newdata[p + 1] = data[p + 1];
newdata[p + 2] = data[p ];
}
return newdata;
}
if (format == "A" && cdata->_component_width == 1 && cdata->_num_components != 3) {
// We can generally rely on alpha to be the last component.
int component = cdata->_num_components - 1;
for (int p = 0; p < imgsize; ++p) {
newdata[p] = data[component];
}
return newdata;
}
if (cdata->_component_width == 1) {
for (int p = 0; p < imgsize; ++p) {
for (uchar s = 0; s < format.size(); ++s) {
signed char component = -1;
if (format.at(s) == 'B' || (cdata->_num_components <= 2 && format.at(s) != 'A')) {
component = 0;
} else if (format.at(s) == 'G') {
component = 1;
} else if (format.at(s) == 'R') {
component = 2;
} else if (format.at(s) == 'A') {
nassertr(cdata->_num_components != 3, CPTA_uchar(get_class_type()));
component = cdata->_num_components - 1;
} else if (format.at(s) == '0') {
newdata[p * format.size() + s] = 0x00;
} else if (format.at(s) == '1') {
newdata[p * format.size() + s] = 0xff;
} else {
gobj_cat.error() << "Unexpected component character '"
<< format.at(s) << "', expected one of RGBA!\n";
return CPTA_uchar(get_class_type());
}
if (component >= 0) {
newdata[p * format.size() + s] = data[p * cdata->_num_components + component];
}
}
}
return newdata;
}
for (int p = 0; p < imgsize; ++p) {
for (uchar s = 0; s < format.size(); ++s) {
signed char component = -1;
if (format.at(s) == 'B' || (cdata->_num_components <= 2 && format.at(s) != 'A')) {
component = 0;
} else if (format.at(s) == 'G') {
component = 1;
} else if (format.at(s) == 'R') {
component = 2;
} else if (format.at(s) == 'A') {
nassertr(cdata->_num_components != 3, CPTA_uchar(get_class_type()));
component = cdata->_num_components - 1;
} else if (format.at(s) == '0') {
memset((void*)(newdata + (p * format.size() + s) * cdata->_component_width), 0, cdata->_component_width);
} else if (format.at(s) == '1') {
memset((void*)(newdata + (p * format.size() + s) * cdata->_component_width), -1, cdata->_component_width);
} else {
gobj_cat.error() << "Unexpected component character '"
<< format.at(s) << "', expected one of RGBA!\n";
return CPTA_uchar(get_class_type());
}
if (component >= 0) {
memcpy((void*)(newdata + (p * format.size() + s) * cdata->_component_width),
(void*)(data + (p * cdata->_num_components + component) * cdata->_component_width),
cdata->_component_width);
}
}
}
return newdata;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_simple_ram_image
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_simple_ram_image(CData *cdata, CPTA_uchar image, int x_size, int y_size) {
nassertv(cdata->_texture_type == TT_2d_texture);
size_t expected_page_size = (size_t)(x_size * y_size * 4);
nassertv(image.size() == expected_page_size);
cdata->_simple_x_size = x_size;
cdata->_simple_y_size = y_size;
cdata->_simple_ram_image._image = image.cast_non_const();
cdata->_simple_ram_image._page_size = image.size();
cdata->_simple_image_date_generated = (PN_int32)time(NULL);
cdata->inc_simple_image_modified();
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_get_expected_num_mipmap_levels
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
int Texture::
do_get_expected_num_mipmap_levels(const CData *cdata) const {
int size = max(cdata->_x_size, max(cdata->_y_size, cdata->_z_size));
int count = 1;
while (size > 1) {
size >>= 1;
++count;
}
return count;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_get_ram_mipmap_page_size
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
size_t Texture::
do_get_ram_mipmap_page_size(const CData *cdata, int n) const {
if (cdata->_ram_image_compression != CM_off) {
if (n >= 0 && n < (int)cdata->_ram_images.size()) {
return cdata->_ram_images[n]._page_size;
}
return 0;
} else {
return do_get_expected_ram_mipmap_page_size(cdata, n);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_get_expected_mipmap_x_size
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
int Texture::
do_get_expected_mipmap_x_size(const CData *cdata, int n) const {
int size = max(cdata->_x_size, 1);
while (n > 0 && size > 1) {
size >>= 1;
--n;
}
return size;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_get_expected_mipmap_y_size
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
int Texture::
do_get_expected_mipmap_y_size(const CData *cdata, int n) const {
int size = max(cdata->_y_size, 1);
while (n > 0 && size > 1) {
size >>= 1;
--n;
}
return size;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_get_expected_mipmap_z_size
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
int Texture::
do_get_expected_mipmap_z_size(const CData *cdata, int n) const {
// 3-D textures have a different number of pages per each mipmap
// level. Other kinds of textures--especially, cube map
// textures--always have the same.
if (cdata->_texture_type == Texture::TT_3d_texture) {
int size = max(cdata->_z_size, 1);
while (n > 0 && size > 1) {
size >>= 1;
--n;
}
return size;
} else {
return cdata->_z_size;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_clear_simple_ram_image
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_clear_simple_ram_image(CData *cdata) {
cdata->_simple_x_size = 0;
cdata->_simple_y_size = 0;
cdata->_simple_ram_image._image.clear();
cdata->_simple_ram_image._page_size = 0;
cdata->_simple_image_date_generated = 0;
// We allow this exception: we update the _simple_image_modified
// here, since no one really cares much about that anyway, and it's
// convenient to do it here.
cdata->inc_simple_image_modified();
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_clear_ram_mipmap_images
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_clear_ram_mipmap_images(CData *cdata) {
if (!cdata->_ram_images.empty()) {
cdata->_ram_images.erase(cdata->_ram_images.begin() + 1, cdata->_ram_images.end());
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_generate_ram_mipmap_images
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_generate_ram_mipmap_images(CData *cdata) {
nassertv(do_has_ram_image(cdata));
if (do_get_expected_num_mipmap_levels(cdata) == 1) {
// Don't bother.
return;
}
RamImage orig_compressed_image;
CompressionMode orig_compression_mode = CM_off;
if (cdata->_ram_image_compression != CM_off) {
// The RAM image is compressed. This means we need to uncompress
// it in order to generate mipmap images. Save the original
// first, to avoid lossy recompression.
orig_compressed_image = cdata->_ram_images[0];
orig_compression_mode = cdata->_ram_image_compression;
// Now try to get the uncompressed source image.
do_get_uncompressed_ram_image(cdata);
nassertv(cdata->_ram_image_compression == CM_off);
}
do_clear_ram_mipmap_images(cdata);
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Generating mipmap levels for " << *this << "\n";
}
if (cdata->_texture_type == Texture::TT_3d_texture && cdata->_z_size != 1) {
// Eek, a 3-D texture.
int x_size = cdata->_x_size;
int y_size = cdata->_y_size;
int z_size = cdata->_z_size;
int n = 0;
while (x_size > 1 || y_size > 1 || z_size > 1) {
cdata->_ram_images.push_back(RamImage());
do_filter_3d_mipmap_level(cdata, cdata->_ram_images[n + 1], cdata->_ram_images[n],
x_size, y_size, z_size);
x_size = max(x_size >> 1, 1);
y_size = max(y_size >> 1, 1);
z_size = max(z_size >> 1, 1);
++n;
}
} else {
// A 1-D, 2-D, or cube map texture.
int x_size = cdata->_x_size;
int y_size = cdata->_y_size;
int n = 0;
while (x_size > 1 || y_size > 1) {
cdata->_ram_images.push_back(RamImage());
do_filter_2d_mipmap_pages(cdata, cdata->_ram_images[n + 1], cdata->_ram_images[n],
x_size, y_size);
x_size = max(x_size >> 1, 1);
y_size = max(y_size >> 1, 1);
++n;
}
}
if (orig_compression_mode != CM_off) {
// Now attempt to recompress the mipmap images according to the
// original compression mode. We don't need to bother compressing
// the first image (it was already compressed, after all), so
// temporarily remove it from the top of the mipmap stack, and
// compress all of the rest of them instead.
nassertv(cdata->_ram_images.size() > 1);
int l0_x_size = cdata->_x_size;
int l0_y_size = cdata->_y_size;
int l0_z_size = cdata->_z_size;
cdata->_x_size = do_get_expected_mipmap_x_size(cdata, 1);
cdata->_y_size = do_get_expected_mipmap_y_size(cdata, 1);
cdata->_z_size = do_get_expected_mipmap_z_size(cdata, 1);
RamImage uncompressed_image = cdata->_ram_images[0];
cdata->_ram_images.erase(cdata->_ram_images.begin());
bool success = do_compress_ram_image(cdata, orig_compression_mode, QL_default, NULL);
// Now restore the toplevel image.
if (success) {
cdata->_ram_images.insert(cdata->_ram_images.begin(), orig_compressed_image);
} else {
cdata->_ram_images.insert(cdata->_ram_images.begin(), uncompressed_image);
}
cdata->_x_size = l0_x_size;
cdata->_y_size = l0_y_size;
cdata->_z_size = l0_z_size;
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_set_pad_size
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
void Texture::
do_set_pad_size(CData *cdata, int x, int y, int z) {
if (x > cdata->_x_size) {
x = cdata->_x_size;
}
if (y > cdata->_y_size) {
y = cdata->_y_size;
}
if (z > cdata->_z_size) {
z = cdata->_z_size;
}
cdata->_pad_x_size = x;
cdata->_pad_y_size = y;
cdata->_pad_z_size = z;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_can_reload
// Access: Protected, Virtual
// Description: Returns true if we can safely call
// do_reload_ram_image() in order to make the image
// available, or false if we shouldn't do this (because
// we know from a priori knowledge that it wouldn't work
// anyway).
////////////////////////////////////////////////////////////////////
bool Texture::
do_can_reload(const CData *cdata) const {
return (cdata->_loaded_from_image && !cdata->_fullpath.empty());
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_reload
// Access: Protected
// Description:
////////////////////////////////////////////////////////////////////
bool Texture::
do_reload(CData *cdata) {
if (do_can_reload(cdata)) {
do_clear_ram_image(cdata);
do_reload_ram_image(cdata, true);
if (do_has_ram_image(cdata)) {
// An explicit call to reload() should increment image_modified.
cdata->inc_image_modified();
return true;
}
return false;
}
// We don't have a filename to load from.
return false;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_has_bam_rawdata
// Access: Protected, Virtual
// Description: Returns true if there is a rawdata image that we have
// available to write to the bam stream. For a normal
// Texture, this is the same thing as
// do_has_ram_image(), but a movie texture might define
// it differently.
////////////////////////////////////////////////////////////////////
bool Texture::
do_has_bam_rawdata(const CData *cdata) const {
return do_has_ram_image(cdata);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_get_bam_rawdata
// Access: Protected, Virtual
// Description: If do_has_bam_rawdata() returned false, this attempts
// to reload the rawdata image if possible.
////////////////////////////////////////////////////////////////////
void Texture::
do_get_bam_rawdata(CData *cdata) {
do_get_ram_image(cdata);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::convert_from_pnmimage
// Access: Private, Static
// Description: Internal method to convert pixel data from the
// indicated PNMImage into the given ram_image.
////////////////////////////////////////////////////////////////////
void Texture::
convert_from_pnmimage(PTA_uchar &image, size_t page_size,
int row_stride, int x, int y, int z,
const PNMImage &pnmimage, int num_components,
int component_width) {
int x_size = pnmimage.get_x_size();
int y_size = pnmimage.get_y_size();
xelval maxval = pnmimage.get_maxval();
int pixel_size = num_components * component_width;
int row_skip = 0;
if (row_stride == 0) {
row_stride = x_size;
} else {
row_skip = (row_stride - x_size) * pixel_size;
nassertv(row_skip >= 0);
}
bool is_grayscale = (num_components == 1 || num_components == 2);
bool has_alpha = (num_components == 2 || num_components == 4);
bool img_has_alpha = pnmimage.has_alpha();
int idx = page_size * z;
nassertv(idx + page_size <= image.size());
unsigned char *p = &image[idx];
if (x != 0 || y != 0) {
p += (row_stride * y + x) * pixel_size;
}
if (maxval == 255 && component_width == 1) {
// Most common case: one byte per pixel, and the source image
// shows a maxval of 255. No scaling is necessary.
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
if (is_grayscale) {
store_unscaled_byte(p, pnmimage.get_gray_val(i, j));
} else {
store_unscaled_byte(p, pnmimage.get_blue_val(i, j));
store_unscaled_byte(p, pnmimage.get_green_val(i, j));
store_unscaled_byte(p, pnmimage.get_red_val(i, j));
}
if (has_alpha) {
if (img_has_alpha) {
store_unscaled_byte(p, pnmimage.get_alpha_val(i, j));
} else {
store_unscaled_byte(p, 255);
}
}
}
p += row_skip;
}
} else if (maxval == 65535 && component_width == 2) {
// Another possible case: two bytes per pixel, and the source
// image shows a maxval of 65535. Again, no scaling is necessary.
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
if (is_grayscale) {
store_unscaled_short(p, pnmimage.get_gray_val(i, j));
} else {
store_unscaled_short(p, pnmimage.get_blue_val(i, j));
store_unscaled_short(p, pnmimage.get_green_val(i, j));
store_unscaled_short(p, pnmimage.get_red_val(i, j));
}
if (has_alpha) {
if (img_has_alpha) {
store_unscaled_short(p, pnmimage.get_alpha_val(i, j));
} else {
store_unscaled_short(p, 65535);
}
}
}
p += row_skip;
}
} else if (component_width == 1) {
// A less common case: one byte per pixel, but the maxval is
// something other than 255. In this case, we should scale the
// pixel values up to the appropriate amount.
double scale = 255.0 / (double)maxval;
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
if (is_grayscale) {
store_scaled_byte(p, pnmimage.get_gray_val(i, j), scale);
} else {
store_scaled_byte(p, pnmimage.get_blue_val(i, j), scale);
store_scaled_byte(p, pnmimage.get_green_val(i, j), scale);
store_scaled_byte(p, pnmimage.get_red_val(i, j), scale);
}
if (has_alpha) {
if (img_has_alpha) {
store_scaled_byte(p, pnmimage.get_alpha_val(i, j), scale);
} else {
store_unscaled_byte(p, 255);
}
}
}
p += row_skip;
}
} else { // component_width == 2
// Another uncommon case: two bytes per pixel, and the maxval is
// something other than 65535. Again, we must scale the pixel
// values.
double scale = 65535.0 / (double)maxval;
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
if (is_grayscale) {
store_scaled_short(p, pnmimage.get_gray_val(i, j), scale);
} else {
store_scaled_short(p, pnmimage.get_blue_val(i, j), scale);
store_scaled_short(p, pnmimage.get_green_val(i, j), scale);
store_scaled_short(p, pnmimage.get_red_val(i, j), scale);
}
if (has_alpha) {
if (img_has_alpha) {
store_scaled_short(p, pnmimage.get_alpha_val(i, j), 1.0);
} else {
store_unscaled_short(p, 65535);
}
}
}
p += row_skip;
}
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::convert_from_pfm
// Access: Private, Static
// Description: Internal method to convert pixel data from the
// indicated PfmFile into the given ram_image.
////////////////////////////////////////////////////////////////////
void Texture::
convert_from_pfm(PTA_uchar &image, size_t page_size, int z,
const PfmFile &pfm, int num_components, int component_width) {
nassertv(component_width == 4); // Currently only PN_float32 is expected.
int x_size = pfm.get_x_size();
int y_size = pfm.get_y_size();
int idx = page_size * z;
nassertv(idx + page_size <= image.size());
PN_float32 *p = (PN_float32 *)&image[idx];
switch (num_components) {
case 1:
{
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
p[0] = pfm.get_channel(i, j, 0);
++p;
}
}
}
break;
case 2:
{
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
p[0] = pfm.get_channel(i, j, 0);
p[1] = pfm.get_channel(i, j, 1);
p += 2;
}
}
}
break;
case 3:
{
// RGB -> BGR
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
p[0] = pfm.get_channel(i, j, 2);
p[1] = pfm.get_channel(i, j, 1);
p[2] = pfm.get_channel(i, j, 0);
p += 3;
}
}
}
break;
case 4:
{
// RGBA -> BGRA
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
p[0] = pfm.get_channel(i, j, 2);
p[1] = pfm.get_channel(i, j, 1);
p[2] = pfm.get_channel(i, j, 0);
p[3] = pfm.get_channel(i, j, 3);
p += 4;
}
}
}
break;
default:
nassertv(false);
}
nassertv((unsigned char *)p == &image[idx] + page_size);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::convert_to_pnmimage
// Access: Private, Static
// Description: Internal method to convert pixel data to the
// indicated PNMImage from the given ram_image.
////////////////////////////////////////////////////////////////////
bool Texture::
convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
int num_components, int component_width,
CPTA_uchar image, size_t page_size, int z) {
xelval maxval = 0xff;
if (component_width > 1) {
maxval = 0xffff;
}
pnmimage.clear(x_size, y_size, num_components, maxval);
bool has_alpha = pnmimage.has_alpha();
bool is_grayscale = pnmimage.is_grayscale();
int idx = page_size * z;
nassertr(idx + page_size <= image.size(), false);
const unsigned char *p = &image[idx];
if (component_width == 1) {
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
if (is_grayscale) {
pnmimage.set_gray(i, j, get_unsigned_byte(p));
} else {
pnmimage.set_blue(i, j, get_unsigned_byte(p));
pnmimage.set_green(i, j, get_unsigned_byte(p));
pnmimage.set_red(i, j, get_unsigned_byte(p));
}
if (has_alpha) {
pnmimage.set_alpha(i, j, get_unsigned_byte(p));
}
}
}
} else if (component_width == 2) {
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
if (is_grayscale) {
pnmimage.set_gray(i, j, get_unsigned_short(p));
} else {
pnmimage.set_blue(i, j, get_unsigned_short(p));
pnmimage.set_green(i, j, get_unsigned_short(p));
pnmimage.set_red(i, j, get_unsigned_short(p));
}
if (has_alpha) {
pnmimage.set_alpha(i, j, get_unsigned_short(p));
}
}
}
} else {
return false;
}
nassertr(p == &image[idx] + page_size, false);
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::convert_to_pfm
// Access: Private, Static
// Description: Internal method to convert pixel data to the
// indicated PfmFile from the given ram_image.
////////////////////////////////////////////////////////////////////
bool Texture::
convert_to_pfm(PfmFile &pfm, int x_size, int y_size,
int num_components, int component_width,
CPTA_uchar image, size_t page_size, int z) {
nassertr(component_width == 4, false); // Currently only PN_float32 is expected.
pfm.clear(x_size, y_size, num_components);
int idx = page_size * z;
nassertr(idx + page_size <= image.size(), false);
const PN_float32 *p = (const PN_float32 *)&image[idx];
switch (num_components) {
case 1:
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
pfm.set_channel(i, j, 0, p[0]);
++p;
}
}
break;
case 2:
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
pfm.set_channel(i, j, 0, p[0]);
pfm.set_channel(i, j, 1, p[1]);
p += 2;
}
}
break;
case 3:
// BGR -> RGB
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
pfm.set_channel(i, j, 2, p[0]);
pfm.set_channel(i, j, 1, p[1]);
pfm.set_channel(i, j, 0, p[2]);
p += 3;
}
}
break;
case 4:
// BGRA -> RGBA
for (int j = y_size-1; j >= 0; j--) {
for (int i = 0; i < x_size; i++) {
pfm.set_channel(i, j, 2, p[0]);
pfm.set_channel(i, j, 1, p[1]);
pfm.set_channel(i, j, 0, p[2]);
pfm.set_channel(i, j, 3, p[3]);
p += 4;
}
}
break;
default:
nassertr(false, false);
}
nassertr((unsigned char *)p == &image[idx] + page_size, false);
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read_dds_level_bgr8
// Access: Private, Static
// Description: Called by read_dds for a DDS file in BGR8 format.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
read_dds_level_bgr8(Texture *tex, CData *cdata, const DDSHeader &header, int n, istream &in) {
// This is in order B, G, R.
int x_size = tex->do_get_expected_mipmap_x_size(cdata, n);
int y_size = tex->do_get_expected_mipmap_y_size(cdata, n);
size_t size = tex->do_get_expected_ram_mipmap_page_size(cdata, n);
size_t row_bytes = x_size * 3;
PTA_uchar image = PTA_uchar::empty_array(size);
for (int y = y_size - 1; y >= 0; --y) {
unsigned char *p = image.p() + y * row_bytes;
nassertr(p + row_bytes <= image.p() + size, PTA_uchar());
in.read((char *)p, row_bytes);
}
return image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read_dds_level_rgb8
// Access: Private, Static
// Description: Called by read_dds for a DDS file in RGB8 format.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
read_dds_level_rgb8(Texture *tex, CData *cdata, const DDSHeader &header, int n, istream &in) {
// This is in order R, G, B.
int x_size = tex->do_get_expected_mipmap_x_size(cdata, n);
int y_size = tex->do_get_expected_mipmap_y_size(cdata, n);
size_t size = tex->do_get_expected_ram_mipmap_page_size(cdata, n);
size_t row_bytes = x_size * 3;
PTA_uchar image = PTA_uchar::empty_array(size);
for (int y = y_size - 1; y >= 0; --y) {
unsigned char *p = image.p() + y * row_bytes;
nassertr(p + row_bytes <= image.p() + size, PTA_uchar());
in.read((char *)p, row_bytes);
// Now reverse the r, g, b triples.
for (int x = 0; x < x_size; ++x) {
unsigned char r = p[0];
p[0] = p[2];
p[2] = r;
p += 3;
}
nassertr(p <= image.p() + size, PTA_uchar());
}
return image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read_dds_level_abgr8
// Access: Private, Static
// Description: Called by read_dds for a DDS file in ABGR8 format.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
read_dds_level_abgr8(Texture *tex, CData *cdata, const DDSHeader &header, int n, istream &in) {
// This is laid out in order R, G, B, A.
int x_size = tex->do_get_expected_mipmap_x_size(cdata, n);
int y_size = tex->do_get_expected_mipmap_y_size(cdata, n);
size_t size = tex->do_get_expected_ram_mipmap_page_size(cdata, n);
size_t row_bytes = x_size * 4;
PTA_uchar image = PTA_uchar::empty_array(size);
for (int y = y_size - 1; y >= 0; --y) {
unsigned char *p = image.p() + y * row_bytes;
in.read((char *)p, row_bytes);
PN_uint32 *pw = (PN_uint32 *)p;
for (int x = 0; x < x_size; ++x) {
PN_uint32 w = *pw;
#ifdef WORDS_BIGENDIAN
// bigendian: convert R, G, B, A to B, G, R, A.
w = ((w & 0xff00) << 16) | ((w & 0xff000000U) >> 16) | (w & 0xff00ff);
#else
// littendian: convert A, B, G, R to to A, R, G, B.
w = ((w & 0xff) << 16) | ((w & 0xff0000) >> 16) | (w & 0xff00ff00U);
#endif
*pw = w;
++pw;
}
nassertr((unsigned char *)pw <= image.p() + size, PTA_uchar());
}
return image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read_dds_level_rgba8
// Access: Private, Static
// Description: Called by read_dds for a DDS file in RGBA8 format.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
read_dds_level_rgba8(Texture *tex, CData *cdata, const DDSHeader &header, int n, istream &in) {
// This is actually laid out in order B, G, R, A.
int x_size = tex->do_get_expected_mipmap_x_size(cdata, n);
int y_size = tex->do_get_expected_mipmap_y_size(cdata, n);
size_t size = tex->do_get_expected_ram_mipmap_page_size(cdata, n);
size_t row_bytes = x_size * 4;
PTA_uchar image = PTA_uchar::empty_array(size);
for (int y = y_size - 1; y >= 0; --y) {
unsigned char *p = image.p() + y * row_bytes;
nassertr(p + row_bytes <= image.p() + size, PTA_uchar());
in.read((char *)p, row_bytes);
}
return image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read_dds_level_generic_uncompressed
// Access: Private, Static
// Description: Called by read_dds for a DDS file whose format isn't
// one we've specifically optimized.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
read_dds_level_generic_uncompressed(Texture *tex, CData *cdata, const DDSHeader &header,
int n, istream &in) {
int x_size = tex->do_get_expected_mipmap_x_size(cdata, n);
int y_size = tex->do_get_expected_mipmap_y_size(cdata, n);
int pitch = (x_size * header.pf.rgb_bitcount) / 8;
// MS says the pitch can be supplied in the header file and must be
// DWORD aligned, but this appears to apply to level 0 mipmaps only
// (where it almost always will be anyway). Other mipmap levels
// seem to be tightly packed, but there isn't a separate pitch for
// each mipmap level. Weird.
if (n == 0) {
pitch = ((pitch + 3) / 4) * 4;
if (header.dds_flags & DDSD_PITCH) {
pitch = header.pitch;
}
}
int bpp = header.pf.rgb_bitcount / 8;
int skip_bytes = pitch - (bpp * x_size);
nassertr(skip_bytes >= 0, PTA_uchar());
unsigned int r_mask = header.pf.r_mask;
unsigned int g_mask = header.pf.g_mask;
unsigned int b_mask = header.pf.b_mask;
unsigned int a_mask = header.pf.a_mask;
// Determine the number of bits to shift each mask to the right so
// that the lowest on bit is at bit 0.
int r_shift = get_lowest_on_bit(r_mask);
int g_shift = get_lowest_on_bit(g_mask);
int b_shift = get_lowest_on_bit(b_mask);
int a_shift = get_lowest_on_bit(a_mask);
// Then determine the scale factor required to raise the highest
// color value to 0xff000000.
unsigned int r_scale = 0;
if (r_mask != 0) {
r_scale = 0xff000000 / (r_mask >> r_shift);
}
unsigned int g_scale = 0;
if (g_mask != 0) {
g_scale = 0xff000000 / (g_mask >> g_shift);
}
unsigned int b_scale = 0;
if (b_mask != 0) {
b_scale = 0xff000000 / (b_mask >> b_shift);
}
unsigned int a_scale = 0;
if (a_mask != 0) {
a_scale = 0xff000000 / (a_mask >> a_shift);
}
bool add_alpha = has_alpha(cdata->_format);
size_t size = tex->do_get_expected_ram_mipmap_page_size(cdata, n);
size_t row_bytes = x_size * cdata->_num_components;
PTA_uchar image = PTA_uchar::empty_array(size);
for (int y = y_size - 1; y >= 0; --y) {
unsigned char *p = image.p() + y * row_bytes;
for (int x = 0; x < x_size; ++x) {
// Read a little-endian numeric value of bpp bytes.
unsigned int pixel = 0;
int shift = 0;
for (int bi = 0; bi < bpp; ++bi) {
unsigned int ch = (unsigned char)in.get();
pixel |= (ch << shift);
shift += 8;
}
// Then break apart that value into its R, G, B, and maybe A
// components.
unsigned int r = (((pixel & r_mask) >> r_shift) * r_scale) >> 24;
unsigned int g = (((pixel & g_mask) >> g_shift) * g_scale) >> 24;
unsigned int b = (((pixel & b_mask) >> b_shift) * b_scale) >> 24;
// Store the components in the Texture's image data.
store_unscaled_byte(p, b);
store_unscaled_byte(p, g);
store_unscaled_byte(p, r);
if (add_alpha) {
unsigned int a = (((pixel & a_mask) >> a_shift) * a_scale) >> 24;
store_unscaled_byte(p, a);
}
}
nassertr(p <= image.p() + size, PTA_uchar());
for (int bi = 0; bi < skip_bytes; ++bi) {
in.get();
}
}
return image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read_dds_level_luminance_uncompressed
// Access: Private, Static
// Description: Called by read_dds for a DDS file in uncompressed
// luminance or luminance-alpha format.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
read_dds_level_luminance_uncompressed(Texture *tex, CData *cdata, const DDSHeader &header,
int n, istream &in) {
int x_size = tex->do_get_expected_mipmap_x_size(cdata, n);
int y_size = tex->do_get_expected_mipmap_y_size(cdata, n);
int pitch = (x_size * header.pf.rgb_bitcount) / 8;
// MS says the pitch can be supplied in the header file and must be
// DWORD aligned, but this appears to apply to level 0 mipmaps only
// (where it almost always will be anyway). Other mipmap levels
// seem to be tightly packed, but there isn't a separate pitch for
// each mipmap level. Weird.
if (n == 0) {
pitch = ((pitch + 3) / 4) * 4;
if (header.dds_flags & DDSD_PITCH) {
pitch = header.pitch;
}
}
int bpp = header.pf.rgb_bitcount / 8;
int skip_bytes = pitch - (bpp * x_size);
nassertr(skip_bytes >= 0, PTA_uchar());
unsigned int r_mask = header.pf.r_mask;
unsigned int a_mask = header.pf.a_mask;
// Determine the number of bits to shift each mask to the right so
// that the lowest on bit is at bit 0.
int r_shift = get_lowest_on_bit(r_mask);
int a_shift = get_lowest_on_bit(a_mask);
// Then determine the scale factor required to raise the highest
// color value to 0xff000000.
unsigned int r_scale = 0;
if (r_mask != 0) {
r_scale = 0xff000000 / (r_mask >> r_shift);
}
unsigned int a_scale = 0;
if (a_mask != 0) {
a_scale = 0xff000000 / (a_mask >> a_shift);
}
bool add_alpha = has_alpha(cdata->_format);
size_t size = tex->do_get_expected_ram_mipmap_page_size(cdata, n);
size_t row_bytes = x_size * cdata->_num_components;
PTA_uchar image = PTA_uchar::empty_array(size);
for (int y = y_size - 1; y >= 0; --y) {
unsigned char *p = image.p() + y * row_bytes;
for (int x = 0; x < x_size; ++x) {
// Read a little-endian numeric value of bpp bytes.
unsigned int pixel = 0;
int shift = 0;
for (int bi = 0; bi < bpp; ++bi) {
unsigned int ch = (unsigned char)in.get();
pixel |= (ch << shift);
shift += 8;
}
unsigned int r = (((pixel & r_mask) >> r_shift) * r_scale) >> 24;
// Store the components in the Texture's image data.
store_unscaled_byte(p, r);
if (add_alpha) {
unsigned int a = (((pixel & a_mask) >> a_shift) * a_scale) >> 24;
store_unscaled_byte(p, a);
}
}
nassertr(p <= image.p() + size, PTA_uchar());
for (int bi = 0; bi < skip_bytes; ++bi) {
in.get();
}
}
return image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read_dds_level_dxt1
// Access: Private, Static
// Description: Called by read_dds for DXT1 file format.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
read_dds_level_dxt1(Texture *tex, CData *cdata, const DDSHeader &header, int n, istream &in) {
int x_size = tex->do_get_expected_mipmap_x_size(cdata, n);
int y_size = tex->do_get_expected_mipmap_y_size(cdata, n);
static const int div = 4;
static const int block_bytes = 8;
// The DXT1 image is divided into num_rows x num_cols blocks, where
// each block represents 4x4 pixels.
int num_cols = max(div, x_size) / div;
int num_rows = max(div, y_size) / div;
int row_length = num_cols * block_bytes;
int linear_size = row_length * num_rows;
if (n == 0) {
if (header.dds_flags & DDSD_LINEARSIZE) {
nassertr(linear_size == (int)header.pitch, PTA_uchar());
}
}
PTA_uchar image = PTA_uchar::empty_array(linear_size);
if (y_size >= 4) {
// We have to flip the image as we read it, because of DirectX's
// inverted sense of up. That means we (a) reverse the order of the
// rows of blocks . . .
for (int ri = num_rows - 1; ri >= 0; --ri) {
unsigned char *p = image.p() + row_length * ri;
in.read((char *)p, row_length);
for (int ci = 0; ci < num_cols; ++ci) {
// . . . and (b) within each block, we reverse the 4 individual
// rows of 4 pixels.
PN_uint32 *cells = (PN_uint32 *)p;
PN_uint32 w = cells[1];
w = ((w & 0xff) << 24) | ((w & 0xff00) << 8) | ((w & 0xff0000) >> 8) | ((w & 0xff000000U) >> 24);
cells[1] = w;
p += block_bytes;
}
}
} else if (y_size >= 2) {
// To invert a two-pixel high image, we just flip two rows within a cell.
unsigned char *p = image.p();
in.read((char *)p, row_length);
for (int ci = 0; ci < num_cols; ++ci) {
PN_uint32 *cells = (PN_uint32 *)p;
PN_uint32 w = cells[1];
w = ((w & 0xff) << 8) | ((w & 0xff00) >> 8);
cells[1] = w;
p += block_bytes;
}
} else if (y_size >= 1) {
// No need to invert a one-pixel-high image.
unsigned char *p = image.p();
in.read((char *)p, row_length);
}
return image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read_dds_level_dxt23
// Access: Private, Static
// Description: Called by read_dds for DXT2 or DXT3 file format.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
read_dds_level_dxt23(Texture *tex, CData *cdata, const DDSHeader &header, int n, istream &in) {
int x_size = tex->do_get_expected_mipmap_x_size(cdata, n);
int y_size = tex->do_get_expected_mipmap_y_size(cdata, n);
static const int div = 4;
static const int block_bytes = 16;
// The DXT3 image is divided into num_rows x num_cols blocks, where
// each block represents 4x4 pixels. Unlike DXT1, each block
// consists of two 8-byte chunks, representing the alpha and color
// separately.
int num_cols = max(div, x_size) / div;
int num_rows = max(div, y_size) / div;
int row_length = num_cols * block_bytes;
int linear_size = row_length * num_rows;
if (n == 0) {
if (header.dds_flags & DDSD_LINEARSIZE) {
nassertr(linear_size == (int)header.pitch, PTA_uchar());
}
}
PTA_uchar image = PTA_uchar::empty_array(linear_size);
if (y_size >= 4) {
// We have to flip the image as we read it, because of DirectX's
// inverted sense of up. That means we (a) reverse the order of the
// rows of blocks . . .
for (int ri = num_rows - 1; ri >= 0; --ri) {
unsigned char *p = image.p() + row_length * ri;
in.read((char *)p, row_length);
for (int ci = 0; ci < num_cols; ++ci) {
// . . . and (b) within each block, we reverse the 4 individual
// rows of 4 pixels.
PN_uint32 *cells = (PN_uint32 *)p;
// Alpha. The block is four 16-bit words of pixel data.
PN_uint32 w0 = cells[0];
PN_uint32 w1 = cells[1];
w0 = ((w0 & 0xffff) << 16) | ((w0 & 0xffff0000U) >> 16);
w1 = ((w1 & 0xffff) << 16) | ((w1 & 0xffff0000U) >> 16);
cells[0] = w1;
cells[1] = w0;
// Color. Only the second 32-bit dword of the color block
// represents the pixel data.
PN_uint32 w = cells[3];
w = ((w & 0xff) << 24) | ((w & 0xff00) << 8) | ((w & 0xff0000) >> 8) | ((w & 0xff000000U) >> 24);
cells[3] = w;
p += block_bytes;
}
}
} else if (y_size >= 2) {
// To invert a two-pixel high image, we just flip two rows within a cell.
unsigned char *p = image.p();
in.read((char *)p, row_length);
for (int ci = 0; ci < num_cols; ++ci) {
PN_uint32 *cells = (PN_uint32 *)p;
PN_uint32 w0 = cells[0];
w0 = ((w0 & 0xffff) << 16) | ((w0 & 0xffff0000U) >> 16);
cells[0] = w0;
PN_uint32 w = cells[3];
w = ((w & 0xff) << 8) | ((w & 0xff00) >> 8);
cells[3] = w;
p += block_bytes;
}
} else if (y_size >= 1) {
// No need to invert a one-pixel-high image.
unsigned char *p = image.p();
in.read((char *)p, row_length);
}
return image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::read_dds_level_dxt45
// Access: Private, Static
// Description: Called by read_dds for DXT4 or DXT5 file format.
////////////////////////////////////////////////////////////////////
PTA_uchar Texture::
read_dds_level_dxt45(Texture *tex, CData *cdata, const DDSHeader &header, int n, istream &in) {
int x_size = tex->do_get_expected_mipmap_x_size(cdata, n);
int y_size = tex->do_get_expected_mipmap_y_size(cdata, n);
static const int div = 4;
static const int block_bytes = 16;
// The DXT5 image is similar to DXT3, in that there each 4x4 block
// of pixels consists of an alpha block and a color block, but the
// layout of the alpha block is different.
int num_cols = max(div, x_size) / div;
int num_rows = max(div, y_size) / div;
int row_length = num_cols * block_bytes;
int linear_size = row_length * num_rows;
if (n == 0) {
if (header.dds_flags & DDSD_LINEARSIZE) {
nassertr(linear_size == (int)header.pitch, PTA_uchar());
}
}
PTA_uchar image = PTA_uchar::empty_array(linear_size);
if (y_size >= 4) {
// We have to flip the image as we read it, because of DirectX's
// inverted sense of up. That means we (a) reverse the order of the
// rows of blocks . . .
for (int ri = num_rows - 1; ri >= 0; --ri) {
unsigned char *p = image.p() + row_length * ri;
in.read((char *)p, row_length);
for (int ci = 0; ci < num_cols; ++ci) {
// . . . and (b) within each block, we reverse the 4 individual
// rows of 4 pixels.
PN_uint32 *cells = (PN_uint32 *)p;
// Alpha. The block is one 16-bit word of reference values,
// followed by six words of pixel values, in 12-bit rows.
// Tricky to invert.
unsigned char p2 = p[2];
unsigned char p3 = p[3];
unsigned char p4 = p[4];
unsigned char p5 = p[5];
unsigned char p6 = p[6];
unsigned char p7 = p[7];
p[2] = ((p7 & 0xf) << 4) | ((p6 & 0xf0) >> 4);
p[3] = ((p5 & 0xf) << 4) | ((p7 & 0xf0) >> 4);
p[4] = ((p6 & 0xf) << 4) | ((p5 & 0xf0) >> 4);
p[5] = ((p4 & 0xf) << 4) | ((p3 & 0xf0) >> 4);
p[6] = ((p2 & 0xf) << 4) | ((p4 & 0xf0) >> 4);
p[7] = ((p3 & 0xf) << 4) | ((p2 & 0xf0) >> 4);
// Color. Only the second 32-bit dword of the color block
// represents the pixel data.
PN_uint32 w = cells[3];
w = ((w & 0xff) << 24) | ((w & 0xff00) << 8) | ((w & 0xff0000) >> 8) | ((w & 0xff000000U) >> 24);
cells[3] = w;
p += block_bytes;
}
}
} else if (y_size >= 2) {
// To invert a two-pixel high image, we just flip two rows within a cell.
unsigned char *p = image.p();
in.read((char *)p, row_length);
for (int ci = 0; ci < num_cols; ++ci) {
PN_uint32 *cells = (PN_uint32 *)p;
unsigned char p2 = p[2];
unsigned char p3 = p[3];
unsigned char p4 = p[4];
p[2] = ((p4 & 0xf) << 4) | ((p3 & 0xf0) >> 4);
p[3] = ((p2 & 0xf) << 4) | ((p4 & 0xf0) >> 4);
p[4] = ((p3 & 0xf) << 4) | ((p2 & 0xf0) >> 4);
PN_uint32 w0 = cells[0];
w0 = ((w0 & 0xffff) << 16) | ((w0 & 0xffff0000U) >> 16);
cells[0] = w0;
PN_uint32 w = cells[3];
w = ((w & 0xff) << 8) | ((w & 0xff00) >> 8);
cells[3] = w;
p += block_bytes;
}
} else if (y_size >= 1) {
// No need to invert a one-pixel-high image.
unsigned char *p = image.p();
in.read((char *)p, row_length);
}
return image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::clear_prepared
// Access: Private
// Description: Removes the indicated PreparedGraphicsObjects table
// from the Texture's table, without actually releasing
// the texture. This is intended to be called only from
// PreparedGraphicsObjects::release_texture(); it should
// never be called by user code.
////////////////////////////////////////////////////////////////////
void Texture::
clear_prepared(int view, PreparedGraphicsObjects *prepared_objects) {
PreparedViews::iterator pvi;
pvi = _prepared_views.find(prepared_objects);
if (pvi != _prepared_views.end()) {
Contexts &contexts = (*pvi).second;
Contexts::iterator ci;
ci = contexts.find(view);
if (ci != contexts.end()) {
contexts.erase(ci);
}
if (contexts.empty()) {
_prepared_views.erase(pvi);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::consider_downgrade
// Access: Private, Static
// Description: Reduces the number of channels in the texture, if
// necessary, according to num_channels.
////////////////////////////////////////////////////////////////////
void Texture::
consider_downgrade(PNMImage &pnmimage, int num_channels, const string &name) {
if (num_channels != 0 && num_channels < pnmimage.get_num_channels()) {
// One special case: we can't reduce from 3 to 2 components, since
// that would require adding an alpha channel.
if (pnmimage.get_num_channels() == 3 && num_channels == 2) {
return;
}
gobj_cat.info()
<< "Downgrading " << name << " from "
<< pnmimage.get_num_channels() << " components to "
<< num_channels << ".\n";
pnmimage.set_num_channels(num_channels);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::compare_images
// Access: Private, Static
// Description: Called by generate_simple_ram_image(), this compares
// the two PNMImages pixel-by-pixel. If they're similar
// enough (within a given threshold), returns true.
////////////////////////////////////////////////////////////////////
bool Texture::
compare_images(const PNMImage &a, const PNMImage &b) {
nassertr(a.get_maxval() == 255 && b.get_maxval() == 255, false);
nassertr(a.get_num_channels() == 4 && b.get_num_channels() == 4, false);
nassertr(a.get_x_size() == b.get_x_size() &&
a.get_y_size() == b.get_y_size(), false);
int delta = 0;
for (int yi = 0; yi < a.get_y_size(); ++yi) {
for (int xi = 0; xi < a.get_x_size(); ++xi) {
delta += abs(a.get_red_val(xi, yi) - b.get_red_val(xi, yi));
delta += abs(a.get_green_val(xi, yi) - b.get_green_val(xi, yi));
delta += abs(a.get_blue_val(xi, yi) - b.get_blue_val(xi, yi));
delta += abs(a.get_alpha_val(xi, yi) - b.get_alpha_val(xi, yi));
}
}
double average_delta = (double)delta / ((double)a.get_x_size() * (double)b.get_y_size() * (double)a.get_maxval());
return (average_delta <= simple_image_threshold);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_filter_2d_mipmap_pages
// Access: Private
// Description: Generates the next mipmap level from the previous
// one. If there are multiple pages (e.g. a cube map),
// generates each page independently.
//
// x_size and y_size are the size of the previous level.
// They need not be a power of 2, or even a multiple of
// 2.
//
// Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
void Texture::
do_filter_2d_mipmap_pages(const CData *cdata,
Texture::RamImage &to, const Texture::RamImage &from,
int x_size, int y_size) const {
Filter2DComponent *filter_component;
Filter2DComponent *filter_alpha;
if (is_srgb(cdata->_format)) {
// We currently only support sRGB mipmap generation for
// unsigned byte textures, due to our use of a lookup table.
nassertv(cdata->_component_type == T_unsigned_byte);
filter_component = &filter_2d_unsigned_byte_srgb;
// Alpha is always linear.
filter_alpha = &filter_2d_unsigned_byte;
} else {
switch (cdata->_component_type) {
case T_unsigned_byte:
filter_component = &filter_2d_unsigned_byte;
break;
case T_unsigned_short:
filter_component = &filter_2d_unsigned_short;
break;
case T_float:
filter_component = &filter_2d_float;
break;
default:
gobj_cat.error()
<< "Unable to generate mipmaps for 2D texture with component type "
<< cdata->_component_type << "!";
return;
}
filter_alpha = filter_component;
}
size_t pixel_size = cdata->_num_components * cdata->_component_width;
size_t row_size = (size_t)x_size * pixel_size;
int to_x_size = max(x_size >> 1, 1);
int to_y_size = max(y_size >> 1, 1);
size_t to_row_size = (size_t)to_x_size * pixel_size;
to._page_size = (size_t)to_y_size * to_row_size;
to._image = PTA_uchar::empty_array(to._page_size * cdata->_z_size * cdata->_num_views, get_class_type());
bool alpha = has_alpha(cdata->_format);
int num_color_components = cdata->_num_components;
if (alpha) {
--num_color_components;
}
int num_pages = cdata->_z_size * cdata->_num_views;
for (int z = 0; z < num_pages; ++z) {
// For each level.
unsigned char *p = to._image.p() + z * to._page_size;
nassertv(p <= to._image.p() + to._image.size() + to._page_size);
const unsigned char *q = from._image.p() + z * from._page_size;
nassertv(q <= from._image.p() + from._image.size() + from._page_size);
if (y_size != 1) {
int y;
for (y = 0; y < y_size - 1; y += 2) {
// For each row.
nassertv(p == to._image.p() + z * to._page_size + (y / 2) * to_row_size);
nassertv(q == from._image.p() + z * from._page_size + y * row_size);
if (x_size != 1) {
int x;
for (x = 0; x < x_size - 1; x += 2) {
// For each pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, pixel_size, row_size);
}
if (alpha) {
filter_alpha(p, q, pixel_size, row_size);
}
q += pixel_size;
}
if (x < x_size) {
// Skip the last odd pixel.
q += pixel_size;
}
} else {
// Just one pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, 0, row_size);
}
if (alpha) {
filter_alpha(p, q, 0, row_size);
}
}
q += row_size;
Thread::consider_yield();
}
if (y < y_size) {
// Skip the last odd row.
q += row_size;
}
} else {
// Just one row.
if (x_size != 1) {
int x;
for (x = 0; x < x_size - 1; x += 2) {
// For each pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, pixel_size, 0);
}
if (alpha) {
filter_alpha(p, q, pixel_size, 0);
}
q += pixel_size;
}
if (x < x_size) {
// Skip the last odd pixel.
q += pixel_size;
}
} else {
// Just one pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, 0, 0);
}
if (alpha) {
filter_alpha(p, q, pixel_size, 0);
}
}
}
nassertv(p == to._image.p() + (z + 1) * to._page_size);
nassertv(q == from._image.p() + (z + 1) * from._page_size);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_filter_3d_mipmap_level
// Access: Private
// Description: Generates the next mipmap level from the previous
// one, treating all the pages of the level as a single
// 3-d block of pixels.
//
// x_size, y_size, and z_size are the size of the
// previous level. They need not be a power of 2, or
// even a multiple of 2.
//
// Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
void Texture::
do_filter_3d_mipmap_level(const CData *cdata,
Texture::RamImage &to, const Texture::RamImage &from,
int x_size, int y_size, int z_size) const {
Filter3DComponent *filter_component;
Filter3DComponent *filter_alpha;
if (is_srgb(cdata->_format)) {
// We currently only support sRGB mipmap generation for
// unsigned byte textures, due to our use of a lookup table.
nassertv(cdata->_component_type == T_unsigned_byte);
filter_component = &filter_3d_unsigned_byte_srgb;
// Alpha is always linear.
filter_alpha = &filter_3d_unsigned_byte;
} else {
switch (cdata->_component_type) {
case T_unsigned_byte:
filter_component = &filter_3d_unsigned_byte;
break;
case T_unsigned_short:
filter_component = &filter_3d_unsigned_short;
break;
case T_float:
filter_component = &filter_3d_float;
break;
default:
gobj_cat.error()
<< "Unable to generate mipmaps for 3D texture with component type "
<< cdata->_component_type << "!";
return;
}
filter_alpha = filter_component;
}
size_t pixel_size = cdata->_num_components * cdata->_component_width;
size_t row_size = (size_t)x_size * pixel_size;
size_t page_size = (size_t)y_size * row_size;
size_t view_size = (size_t)z_size * page_size;
int to_x_size = max(x_size >> 1, 1);
int to_y_size = max(y_size >> 1, 1);
int to_z_size = max(z_size >> 1, 1);
size_t to_row_size = (size_t)to_x_size * pixel_size;
size_t to_page_size = (size_t)to_y_size * to_row_size;
size_t to_view_size = (size_t)to_z_size * to_page_size;
to._page_size = to_page_size;
to._image = PTA_uchar::empty_array(to_page_size * to_z_size * cdata->_num_views, get_class_type());
bool alpha = has_alpha(cdata->_format);
int num_color_components = cdata->_num_components;
if (alpha) {
--num_color_components;
}
for (int view = 0; view < cdata->_num_views; ++view) {
unsigned char *start_to = to._image.p() + view * to_view_size;
const unsigned char *start_from = from._image.p() + view * view_size;
nassertv(start_to + to_view_size <= to._image.p() + to._image.size());
nassertv(start_from + view_size <= from._image.p() + from._image.size());
unsigned char *p = start_to;
const unsigned char *q = start_from;
if (z_size != 1) {
int z;
for (z = 0; z < z_size - 1; z += 2) {
// For each level.
nassertv(p == start_to + (z / 2) * to_page_size);
nassertv(q == start_from + z * page_size);
if (y_size != 1) {
int y;
for (y = 0; y < y_size - 1; y += 2) {
// For each row.
nassertv(p == start_to + (z / 2) * to_page_size + (y / 2) * to_row_size);
nassertv(q == start_from + z * page_size + y * row_size);
if (x_size != 1) {
int x;
for (x = 0; x < x_size - 1; x += 2) {
// For each pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, pixel_size, row_size, page_size);
}
if (alpha) {
filter_alpha(p, q, pixel_size, row_size, page_size);
}
q += pixel_size;
}
if (x < x_size) {
// Skip the last odd pixel.
q += pixel_size;
}
} else {
// Just one pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, 0, row_size, page_size);
}
if (alpha) {
filter_alpha(p, q, 0, row_size, page_size);
}
}
q += row_size;
Thread::consider_yield();
}
if (y < y_size) {
// Skip the last odd row.
q += row_size;
}
} else {
// Just one row.
if (x_size != 1) {
int x;
for (x = 0; x < x_size - 1; x += 2) {
// For each pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, pixel_size, 0, page_size);
}
if (alpha) {
filter_alpha(p, q, pixel_size, 0, page_size);
}
q += pixel_size;
}
if (x < x_size) {
// Skip the last odd pixel.
q += pixel_size;
}
} else {
// Just one pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, 0, 0, page_size);
}
if (alpha) {
filter_alpha(p, q, 0, 0, page_size);
}
}
}
q += page_size;
}
if (z < z_size) {
// Skip the last odd page.
q += page_size;
}
} else {
// Just one page.
if (y_size != 1) {
int y;
for (y = 0; y < y_size - 1; y += 2) {
// For each row.
nassertv(p == start_to + (y / 2) * to_row_size);
nassertv(q == start_from + y * row_size);
if (x_size != 1) {
int x;
for (x = 0; x < x_size - 1; x += 2) {
// For each pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, pixel_size, row_size, 0);
}
if (alpha) {
filter_alpha(p, q, pixel_size, row_size, 0);
}
q += pixel_size;
}
if (x < x_size) {
// Skip the last odd pixel.
q += pixel_size;
}
} else {
// Just one pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, 0, row_size, 0);
}
if (alpha) {
filter_alpha(p, q, 0, row_size, 0);
}
}
q += row_size;
Thread::consider_yield();
}
if (y < y_size) {
// Skip the last odd row.
q += row_size;
}
} else {
// Just one row.
if (x_size != 1) {
int x;
for (x = 0; x < x_size - 1; x += 2) {
// For each pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, pixel_size, 0, 0);
}
if (alpha) {
filter_alpha(p, q, pixel_size, 0, 0);
}
q += pixel_size;
}
if (x < x_size) {
// Skip the last odd pixel.
q += pixel_size;
}
} else {
// Just one pixel.
for (int c = 0; c < num_color_components; ++c) {
// For each component.
filter_component(p, q, 0, 0, 0);
}
if (alpha) {
filter_alpha(p, q, 0, 0, 0);
}
}
}
}
nassertv(p == start_to + to_z_size * to_page_size);
nassertv(q == start_from + z_size * page_size);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::filter_2d_unsigned_byte
// Access: Public, Static
// Description: Averages a 2x2 block of pixel components into a
// single pixel component, for producing the next mipmap
// level. Increments p and q to the next component.
////////////////////////////////////////////////////////////////////
void Texture::
filter_2d_unsigned_byte(unsigned char *&p, const unsigned char *&q,
size_t pixel_size, size_t row_size) {
unsigned int result = ((unsigned int)q[0] +
(unsigned int)q[pixel_size] +
(unsigned int)q[row_size] +
(unsigned int)q[pixel_size + row_size]) >> 2;
*p = (unsigned char)result;
++p;
++q;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::filter_2d_unsigned_byte_srgb
// Access: Public, Static
// Description: Averages a 2x2 block of pixel components into a
// single pixel component, for producing the next mipmap
// level. Increments p and q to the next component.
////////////////////////////////////////////////////////////////////
void Texture::
filter_2d_unsigned_byte_srgb(unsigned char *&p, const unsigned char *&q,
size_t pixel_size, size_t row_size) {
float result = (srgb_to_lrgbf[q[0]] +
srgb_to_lrgbf[q[pixel_size]] +
srgb_to_lrgbf[q[row_size]] +
srgb_to_lrgbf[q[pixel_size + row_size]]) / 4.0f;
// This is based on the formula out of the EXT_texture_sRGB
// specification, except the factors are multiplied with 255.0f.
if (result < 0.0031308f) {
*p = (unsigned char)(result * 3294.6f);
} else {
*p = (unsigned char)(269.025f * powf(result, 0.41666f) - 14.025f);
}
++p;
++q;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::filter_2d_unsigned_short
// Access: Public, Static
// Description: Averages a 2x2 block of pixel components into a
// single pixel component, for producing the next mipmap
// level. Increments p and q to the next component.
////////////////////////////////////////////////////////////////////
void Texture::
filter_2d_unsigned_short(unsigned char *&p, const unsigned char *&q,
size_t pixel_size, size_t row_size) {
unsigned int result = ((unsigned int)*(unsigned short *)&q[0] +
(unsigned int)*(unsigned short *)&q[pixel_size] +
(unsigned int)*(unsigned short *)&q[row_size] +
(unsigned int)*(unsigned short *)&q[pixel_size + row_size]) >> 2;
store_unscaled_short(p, result);
q += 2;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::filter_2d_float
// Access: Public, Static
// Description: Averages a 2x2 block of pixel components into a
// single pixel component, for producing the next mipmap
// level. Increments p and q to the next component.
////////////////////////////////////////////////////////////////////
void Texture::
filter_2d_float(unsigned char *&p, const unsigned char *&q,
size_t pixel_size, size_t row_size) {
*(float *)p = (*(float *)&q[0] +
*(float *)&q[pixel_size] +
*(float *)&q[row_size] +
*(float *)&q[pixel_size + row_size]) / 4.0f;
p += 4;
q += 4;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::filter_3d_unsigned_byte
// Access: Public, Static
// Description: Averages a 2x2x2 block of pixel components into a
// single pixel component, for producing the next mipmap
// level. Increments p and q to the next component.
////////////////////////////////////////////////////////////////////
void Texture::
filter_3d_unsigned_byte(unsigned char *&p, const unsigned char *&q,
size_t pixel_size, size_t row_size, size_t page_size) {
unsigned int result = ((unsigned int)q[0] +
(unsigned int)q[pixel_size] +
(unsigned int)q[row_size] +
(unsigned int)q[pixel_size + row_size] +
(unsigned int)q[page_size] +
(unsigned int)q[pixel_size + page_size] +
(unsigned int)q[row_size + page_size] +
(unsigned int)q[pixel_size + row_size + page_size]) >> 3;
*p = (unsigned char)result;
++p;
++q;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::filter_3d_unsigned_byte_srgb
// Access: Public, Static
// Description: Averages a 2x2x2 block of pixel components into a
// single pixel component, for producing the next mipmap
// level. Increments p and q to the next component.
////////////////////////////////////////////////////////////////////
void Texture::
filter_3d_unsigned_byte_srgb(unsigned char *&p, const unsigned char *&q,
size_t pixel_size, size_t row_size, size_t page_size) {
float result = (srgb_to_lrgbf[q[0]] +
srgb_to_lrgbf[q[pixel_size]] +
srgb_to_lrgbf[q[row_size]] +
srgb_to_lrgbf[q[pixel_size + row_size]] +
srgb_to_lrgbf[q[page_size]] +
srgb_to_lrgbf[q[pixel_size + page_size]] +
srgb_to_lrgbf[q[row_size + page_size]] +
srgb_to_lrgbf[q[pixel_size + row_size + page_size]]) / 8.0f;
// This is based on the formula out of the EXT_texture_sRGB
// specification, except the factors are multiplied with 255.0f.
if (result < 0.0031308f) {
*p = (unsigned char)(result * 3294.6f);
} else {
*p = (unsigned char)(269.025f * powf(result, 0.41666f) - 14.025f);
}
++p;
++q;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::filter_3d_unsigned_short
// Access: Public, Static
// Description: Averages a 2x2x2 block of pixel components into a
// single pixel component, for producing the next mipmap
// level. Increments p and q to the next component.
////////////////////////////////////////////////////////////////////
void Texture::
filter_3d_unsigned_short(unsigned char *&p, const unsigned char *&q,
size_t pixel_size, size_t row_size,
size_t page_size) {
unsigned int result = ((unsigned int)*(unsigned short *)&q[0] +
(unsigned int)*(unsigned short *)&q[pixel_size] +
(unsigned int)*(unsigned short *)&q[row_size] +
(unsigned int)*(unsigned short *)&q[pixel_size + row_size] +
(unsigned int)*(unsigned short *)&q[page_size] +
(unsigned int)*(unsigned short *)&q[pixel_size + page_size] +
(unsigned int)*(unsigned short *)&q[row_size + page_size] +
(unsigned int)*(unsigned short *)&q[pixel_size + row_size + page_size]) >> 3;
store_unscaled_short(p, result);
q += 2;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::filter_3d_float
// Access: Public, Static
// Description: Averages a 2x2x2 block of pixel components into a
// single pixel component, for producing the next mipmap
// level. Increments p and q to the next component.
////////////////////////////////////////////////////////////////////
void Texture::
filter_3d_float(unsigned char *&p, const unsigned char *&q,
size_t pixel_size, size_t row_size, size_t page_size) {
*(float *)p = (*(float *)&q[0] +
*(float *)&q[pixel_size] +
*(float *)&q[row_size] +
*(float *)&q[pixel_size + row_size] +
*(float *)&q[page_size] +
*(float *)&q[pixel_size + page_size] +
*(float *)&q[row_size + page_size] +
*(float *)&q[pixel_size + row_size + page_size]) / 8.0f;
p += 4;
q += 4;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_squish
// Access: Private
// Description: Invokes the squish library to compress the RAM
// image(s).
////////////////////////////////////////////////////////////////////
bool Texture::
do_squish(CData *cdata, Texture::CompressionMode compression, int squish_flags) {
#ifdef HAVE_SQUISH
if (cdata->_ram_images.empty() || cdata->_ram_image_compression != CM_off) {
return false;
}
if (!do_has_all_ram_mipmap_images(cdata)) {
// If we're about to compress the RAM image, we should ensure that
// we have all of the mipmap levels first.
do_generate_ram_mipmap_images(cdata);
}
RamImages compressed_ram_images;
compressed_ram_images.reserve(cdata->_ram_images.size());
for (size_t n = 0; n < cdata->_ram_images.size(); ++n) {
RamImage compressed_image;
int x_size = do_get_expected_mipmap_x_size(cdata, n);
int y_size = do_get_expected_mipmap_y_size(cdata, n);
int num_pages = do_get_expected_mipmap_num_pages(cdata, n);
int page_size = squish::GetStorageRequirements(x_size, y_size, squish_flags);
int cell_size = squish::GetStorageRequirements(4, 4, squish_flags);
compressed_image._page_size = page_size;
compressed_image._image = PTA_uchar::empty_array(page_size * num_pages);
for (int z = 0; z < num_pages; ++z) {
unsigned char *dest_page = compressed_image._image.p() + z * page_size;
unsigned const char *source_page = cdata->_ram_images[n]._image.p() + z * cdata->_ram_images[n]._page_size;
unsigned const char *source_page_end = source_page + cdata->_ram_images[n]._page_size;
// Convert one 4 x 4 cell at a time.
unsigned char *d = dest_page;
for (int y = 0; y < y_size; y += 4) {
for (int x = 0; x < x_size; x += 4) {
unsigned char tb[16 * 4];
int mask = 0;
unsigned char *t = tb;
for (int i = 0; i < 16; ++i) {
int xi = x + i % 4;
int yi = y + i / 4;
unsigned const char *s = source_page + (yi * x_size + xi) * cdata->_num_components;
if (s < source_page_end) {
switch (cdata->_num_components) {
case 1:
t[0] = s[0]; // r
t[1] = s[0]; // g
t[2] = s[0]; // b
t[3] = 255; // a
break;
case 2:
t[0] = s[0]; // r
t[1] = s[0]; // g
t[2] = s[0]; // b
t[3] = s[1]; // a
break;
case 3:
t[0] = s[2]; // r
t[1] = s[1]; // g
t[2] = s[0]; // b
t[3] = 255; // a
break;
case 4:
t[0] = s[2]; // r
t[1] = s[1]; // g
t[2] = s[0]; // b
t[3] = s[3]; // a
break;
}
mask |= (1 << i);
}
t += 4;
}
squish::CompressMasked(tb, mask, d, squish_flags);
d += cell_size;
Thread::consider_yield();
}
}
}
compressed_ram_images.push_back(compressed_image);
}
cdata->_ram_images.swap(compressed_ram_images);
cdata->_ram_image_compression = compression;
return true;
#else // HAVE_SQUISH
return false;
#endif // HAVE_SQUISH
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_unsquish
// Access: Private
// Description: Invokes the squish library to uncompress the RAM
// image(s).
////////////////////////////////////////////////////////////////////
bool Texture::
do_unsquish(CData *cdata, int squish_flags) {
#ifdef HAVE_SQUISH
if (cdata->_ram_images.empty()) {
return false;
}
RamImages uncompressed_ram_images;
uncompressed_ram_images.reserve(cdata->_ram_images.size());
for (size_t n = 0; n < cdata->_ram_images.size(); ++n) {
RamImage uncompressed_image;
int x_size = do_get_expected_mipmap_x_size(cdata, n);
int y_size = do_get_expected_mipmap_y_size(cdata, n);
int num_pages = do_get_expected_mipmap_num_pages(cdata, n);
int page_size = squish::GetStorageRequirements(x_size, y_size, squish_flags);
int cell_size = squish::GetStorageRequirements(4, 4, squish_flags);
uncompressed_image._page_size = do_get_expected_ram_mipmap_page_size(cdata, n);
uncompressed_image._image = PTA_uchar::empty_array(uncompressed_image._page_size * num_pages);
for (int z = 0; z < num_pages; ++z) {
unsigned char *dest_page = uncompressed_image._image.p() + z * uncompressed_image._page_size;
unsigned char *dest_page_end = dest_page + uncompressed_image._page_size;
unsigned const char *source_page = cdata->_ram_images[n]._image.p() + z * page_size;
// Unconvert one 4 x 4 cell at a time.
unsigned const char *s = source_page;
for (int y = 0; y < y_size; y += 4) {
for (int x = 0; x < x_size; x += 4) {
unsigned char tb[16 * 4];
squish::Decompress(tb, s, squish_flags);
s += cell_size;
unsigned char *t = tb;
for (int i = 0; i < 16; ++i) {
int xi = x + i % 4;
int yi = y + i / 4;
unsigned char *d = dest_page + (yi * x_size + xi) * cdata->_num_components;
if (d < dest_page_end) {
switch (cdata->_num_components) {
case 1:
d[0] = t[1]; // g
break;
case 2:
d[0] = t[1]; // g
d[1] = t[3]; // a
break;
case 3:
d[2] = t[0]; // r
d[1] = t[1]; // g
d[0] = t[2]; // b
break;
case 4:
d[2] = t[0]; // r
d[1] = t[1]; // g
d[0] = t[2]; // b
d[3] = t[3]; // a
break;
}
}
t += 4;
}
}
Thread::consider_yield();
}
}
uncompressed_ram_images.push_back(uncompressed_image);
}
cdata->_ram_images.swap(uncompressed_ram_images);
cdata->_ram_image_compression = CM_off;
return true;
#else // HAVE_SQUISH
return false;
#endif // HAVE_SQUISH
}
////////////////////////////////////////////////////////////////////
// Function: Texture::register_with_read_factory
// Access: Public, Static
// Description: Factory method to generate a Texture object
////////////////////////////////////////////////////////////////////
void Texture::
register_with_read_factory() {
BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::write_datagram
// Access: Public, Virtual
// Description: Function to write the important information in
// the particular object to a Datagram
////////////////////////////////////////////////////////////////////
void Texture::
write_datagram(BamWriter *manager, Datagram &me) {
CDWriter cdata(_cycler, false);
bool has_rawdata = false;
do_write_datagram_header(cdata, manager, me, has_rawdata);
do_write_datagram_body(cdata, manager, me);
// If we are also including the texture's image data, then stuff it
// in here.
if (has_rawdata) {
do_write_datagram_rawdata(cdata, manager, me);
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::finalize
// Access: Public, Virtual
// Description: Called by the BamReader to perform any final actions
// needed for setting up the object after all objects
// have been read and all pointers have been completed.
////////////////////////////////////////////////////////////////////
void Texture::
finalize(BamReader *) {
// Unref the pointer that we explicitly reffed in make_from_bam().
unref();
// We should never get back to zero after unreffing our own count,
// because we expect to have been stored in a pointer somewhere. If
// we do get to zero, it's a memory leak; the way to avoid this is
// to call unref_delete() above instead of unref(), but this is
// dangerous to do from within a virtual function.
nassertv(get_ref_count() != 0);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_write_datagram_header
// Access: Protected
// Description: Writes the header part of the texture to the
// Datagram. This is the common part that is shared by
// all Texture subclasses, and contains the filename and
// rawdata flags. This method is not virtual because
// all Texture subclasses must write the same data at
// this step.
//
// This part must be read first before calling
// do_fillin_body() to determine whether to load the
// Texture from the TexturePool or directly from the bam
// stream.
//
// After this call, has_rawdata will be filled with
// either true or false, according to whether we expect
// to write the texture rawdata to the bam stream
// following the texture body.
////////////////////////////////////////////////////////////////////
void Texture::
do_write_datagram_header(CData *cdata, BamWriter *manager, Datagram &me, bool &has_rawdata) {
// Write out the texture's raw pixel data if (a) the current Bam
// Texture Mode requires that, or (b) there's no filename, so the
// file can't be loaded up from disk, but the raw pixel data is
// currently available in RAM.
// Otherwise, we just write out the filename, and assume whoever
// loads the bam file later will have access to the image file on
// disk.
BamWriter::BamTextureMode file_texture_mode = manager->get_file_texture_mode();
has_rawdata = (file_texture_mode == BamWriter::BTM_rawdata ||
(cdata->_filename.empty() && do_has_bam_rawdata(cdata)));
if (has_rawdata && !do_has_bam_rawdata(cdata)) {
do_get_bam_rawdata(cdata);
if (!do_has_bam_rawdata(cdata)) {
// No image data after all.
has_rawdata = false;
}
}
bool has_bam_dir = !manager->get_filename().empty();
Filename bam_dir = manager->get_filename().get_dirname();
Filename filename = cdata->_filename;
Filename alpha_filename = cdata->_alpha_filename;
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
switch (file_texture_mode) {
case BamWriter::BTM_unchanged:
case BamWriter::BTM_rawdata:
break;
case BamWriter::BTM_fullpath:
filename = cdata->_fullpath;
alpha_filename = cdata->_alpha_fullpath;
break;
case BamWriter::BTM_relative:
filename = cdata->_fullpath;
alpha_filename = cdata->_alpha_fullpath;
bam_dir.make_absolute(vfs->get_cwd());
if (!has_bam_dir || !filename.make_relative_to(bam_dir, true)) {
filename.find_on_searchpath(get_model_path());
}
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Texture file " << cdata->_fullpath
<< " found as " << filename << "\n";
}
if (!has_bam_dir || !alpha_filename.make_relative_to(bam_dir, true)) {
alpha_filename.find_on_searchpath(get_model_path());
}
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Alpha image " << cdata->_alpha_fullpath
<< " found as " << alpha_filename << "\n";
}
break;
case BamWriter::BTM_basename:
filename = cdata->_fullpath.get_basename();
alpha_filename = cdata->_alpha_fullpath.get_basename();
break;
default:
gobj_cat.error()
<< "Unsupported bam-texture-mode: " << (int)file_texture_mode << "\n";
}
if (filename.empty() && do_has_bam_rawdata(cdata)) {
// If we don't have a filename, we have to store rawdata anyway.
has_rawdata = true;
}
me.add_string(get_name());
me.add_string(filename);
me.add_string(alpha_filename);
me.add_uint8(cdata->_primary_file_num_channels);
me.add_uint8(cdata->_alpha_file_channel);
me.add_bool(has_rawdata);
me.add_uint8(cdata->_texture_type);
me.add_bool(cdata->_has_read_mipmaps);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_write_datagram_body
// Access: Protected, Virtual
// Description: Writes the body part of the texture to the
// Datagram. This is generally all of the texture
// parameters except for the header and the rawdata.
////////////////////////////////////////////////////////////////////
void Texture::
do_write_datagram_body(CData *cdata, BamWriter *manager, Datagram &me) {
me.add_uint8(cdata->_wrap_u);
me.add_uint8(cdata->_wrap_v);
me.add_uint8(cdata->_wrap_w);
me.add_uint8(cdata->_minfilter);
me.add_uint8(cdata->_magfilter);
me.add_int16(cdata->_anisotropic_degree);
cdata->_border_color.write_datagram(me);
me.add_uint8(cdata->_compression);
me.add_uint8(cdata->_quality_level);
me.add_uint8(cdata->_format);
me.add_uint8(cdata->_num_components);
me.add_uint8(cdata->_auto_texture_scale);
me.add_uint32(cdata->_orig_file_x_size);
me.add_uint32(cdata->_orig_file_y_size);
bool has_simple_ram_image = !cdata->_simple_ram_image._image.empty();
me.add_bool(has_simple_ram_image);
// Write out the simple image too, so it will be available later.
if (has_simple_ram_image) {
me.add_uint32(cdata->_simple_x_size);
me.add_uint32(cdata->_simple_y_size);
me.add_int32(cdata->_simple_image_date_generated);
me.add_uint32(cdata->_simple_ram_image._image.size());
me.append_data(cdata->_simple_ram_image._image, cdata->_simple_ram_image._image.size());
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_write_datagram_rawdata
// Access: Protected, Virtual
// Description: Writes the rawdata part of the texture to the
// Datagram.
////////////////////////////////////////////////////////////////////
void Texture::
do_write_datagram_rawdata(CData *cdata, BamWriter *manager, Datagram &me) {
me.add_uint32(cdata->_x_size);
me.add_uint32(cdata->_y_size);
me.add_uint32(cdata->_z_size);
me.add_uint32(cdata->_pad_x_size);
me.add_uint32(cdata->_pad_y_size);
me.add_uint32(cdata->_pad_z_size);
me.add_uint32(cdata->_num_views);
me.add_uint8(cdata->_component_type);
me.add_uint8(cdata->_component_width);
me.add_uint8(cdata->_ram_image_compression);
me.add_uint8(cdata->_ram_images.size());
for (size_t n = 0; n < cdata->_ram_images.size(); ++n) {
me.add_uint32(cdata->_ram_images[n]._page_size);
me.add_uint32(cdata->_ram_images[n]._image.size());
me.append_data(cdata->_ram_images[n]._image, cdata->_ram_images[n]._image.size());
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::make_from_bam
// Access: Protected, Static
// Description: Factory method to generate a Texture object
////////////////////////////////////////////////////////////////////
TypedWritable *Texture::
make_from_bam(const FactoryParams &params) {
PT(Texture) dummy = new Texture;
return dummy->make_this_from_bam(params);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::make_this_from_bam
// Access: Protected, Virtual
// Description: Called by make_from_bam() once the particular
// subclass of Texture is known. This is called on a
// newly-constructed Texture object of the appropriate
// subclass. It will return either the same Texture
// object (e.g. this), or a different Texture object
// loaded via the TexturePool, as appropriate.
////////////////////////////////////////////////////////////////////
TypedWritable *Texture::
make_this_from_bam(const FactoryParams &params) {
// The process of making a texture is slightly different than making
// other TypedWritable objects. That is because all creation of
// Textures should be done through calls to TexturePool, which
// ensures that any loads of the same filename refer to the same
// memory.
DatagramIterator scan;
BamReader *manager;
parse_params(params, scan, manager);
// Get the header information--the filenames and texture type--so we
// can look up the file on disk first.
string name = scan.get_string();
Filename filename = scan.get_string();
Filename alpha_filename = scan.get_string();
int primary_file_num_channels = scan.get_uint8();
int alpha_file_channel = scan.get_uint8();
bool has_rawdata = scan.get_bool();
TextureType texture_type = (TextureType)scan.get_uint8();
if (manager->get_file_minor_ver() < 25) {
// Between Panda3D releases 1.7.2 and 1.8.0 (bam versions 6.24 and
// 6.25), we added TT_2d_texture_array, shifting the definition
// for TT_cube_map.
if (texture_type == TT_2d_texture_array) {
texture_type = TT_cube_map;
}
}
bool has_read_mipmaps = false;
if (manager->get_file_minor_ver() >= 32) {
has_read_mipmaps = scan.get_bool();
}
Texture *me = NULL;
if (has_rawdata) {
// If the raw image data is included, then just load the texture
// directly from the stream, and return it. In this case we
// return the "this" pointer, since it's a newly-created Texture
// object of the appropriate type.
me = this;
me->set_name(name);
CDWriter cdata_me(me->_cycler, true);
cdata_me->_filename = filename;
cdata_me->_alpha_filename = alpha_filename;
cdata_me->_primary_file_num_channels = primary_file_num_channels;
cdata_me->_alpha_file_channel = alpha_file_channel;
cdata_me->_texture_type = texture_type;
cdata_me->_has_read_mipmaps = has_read_mipmaps;
// Read the texture attributes directly from the bam stream.
me->do_fillin_body(cdata_me, scan, manager);
me->do_fillin_rawdata(cdata_me, scan, manager);
// To manage the reference count, explicitly ref it now, then
// unref it in the finalize callback.
me->ref();
manager->register_finalize(me);
} else {
// The raw image data isn't included, so we'll be loading the
// Texture via the TexturePool. In this case we use the "this"
// pointer as a temporary object to read all of the attributes
// from the bam stream.
Texture *dummy = this;
AutoTextureScale auto_texture_scale = ATS_unspecified;
{
CDWriter cdata_dummy(dummy->_cycler, true);
dummy->do_fillin_body(cdata_dummy, scan, manager);
auto_texture_scale = cdata_dummy->_auto_texture_scale;
}
if (filename.empty()) {
// This texture has no filename; since we don't have an image to
// load, we can't actually create the texture.
gobj_cat.info()
<< "Cannot create texture '" << name << "' with no filename.\n";
} else {
// This texture does have a filename, so try to load it from disk.
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
if (!manager->get_filename().empty()) {
// If texture filename was given relative to the bam filename,
// expand it now.
Filename bam_dir = manager->get_filename().get_dirname();
vfs->resolve_filename(filename, bam_dir);
if (!alpha_filename.empty()) {
vfs->resolve_filename(alpha_filename, bam_dir);
}
}
LoaderOptions options = manager->get_loader_options();
if (dummy->uses_mipmaps()) {
options.set_texture_flags(options.get_texture_flags() | LoaderOptions::TF_generate_mipmaps);
}
options.set_auto_texture_scale(auto_texture_scale);
switch (texture_type) {
case TT_1d_texture:
case TT_2d_texture:
if (alpha_filename.empty()) {
me = TexturePool::load_texture(filename, primary_file_num_channels,
has_read_mipmaps, options);
} else {
me = TexturePool::load_texture(filename, alpha_filename,
primary_file_num_channels,
alpha_file_channel,
has_read_mipmaps, options);
}
break;
case TT_3d_texture:
me = TexturePool::load_3d_texture(filename, has_read_mipmaps, options);
break;
case TT_2d_texture_array:
me = TexturePool::load_2d_texture_array(filename, has_read_mipmaps, options);
break;
case TT_cube_map:
me = TexturePool::load_cube_map(filename, has_read_mipmaps, options);
break;
}
}
if (me != (Texture *)NULL) {
me->set_name(name);
CDWriter cdata_me(me->_cycler, true);
me->do_fillin_from(cdata_me, dummy);
// Since in this case me was loaded from the TexturePool,
// there's no need to explicitly manage the reference count.
// TexturePool will hold it safely.
}
}
return me;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_fillin_body
// Access: Protected, Virtual
// Description: Reads in the part of the Texture that was written
// with do_write_datagram_body().
////////////////////////////////////////////////////////////////////
void Texture::
do_fillin_body(CData *cdata, DatagramIterator &scan, BamReader *manager) {
cdata->_wrap_u = (WrapMode)scan.get_uint8();
cdata->_wrap_v = (WrapMode)scan.get_uint8();
cdata->_wrap_w = (WrapMode)scan.get_uint8();
cdata->_minfilter = (FilterType)scan.get_uint8();
cdata->_magfilter = (FilterType)scan.get_uint8();
cdata->_anisotropic_degree = scan.get_int16();
cdata->_border_color.read_datagram(scan);
if (manager->get_file_minor_ver() >= 1) {
cdata->_compression = (CompressionMode)scan.get_uint8();
}
if (manager->get_file_minor_ver() >= 16) {
cdata->_quality_level = (QualityLevel)scan.get_uint8();
}
cdata->_format = (Format)scan.get_uint8();
cdata->_num_components = scan.get_uint8();
cdata->inc_properties_modified();
cdata->_auto_texture_scale = ATS_unspecified;
if (manager->get_file_minor_ver() >= 28) {
cdata->_auto_texture_scale = (AutoTextureScale)scan.get_uint8();
}
bool has_simple_ram_image = false;
if (manager->get_file_minor_ver() >= 18) {
cdata->_orig_file_x_size = scan.get_uint32();
cdata->_orig_file_y_size = scan.get_uint32();
has_simple_ram_image = scan.get_bool();
}
if (has_simple_ram_image) {
cdata->_simple_x_size = scan.get_uint32();
cdata->_simple_y_size = scan.get_uint32();
cdata->_simple_image_date_generated = scan.get_int32();
size_t u_size = scan.get_uint32();
PTA_uchar image = PTA_uchar::empty_array(u_size, get_class_type());
for (size_t u_idx = 0; u_idx < u_size; ++u_idx) {
image[(int)u_idx] = scan.get_uint8();
}
cdata->_simple_ram_image._image = image;
cdata->_simple_ram_image._page_size = u_size;
cdata->inc_simple_image_modified();
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_fillin_rawdata
// Access: Protected, Virtual
// Description: Reads in the part of the Texture that was written
// with do_write_datagram_rawdata().
////////////////////////////////////////////////////////////////////
void Texture::
do_fillin_rawdata(CData *cdata, DatagramIterator &scan, BamReader *manager) {
cdata->_x_size = scan.get_uint32();
cdata->_y_size = scan.get_uint32();
cdata->_z_size = scan.get_uint32();
if (manager->get_file_minor_ver() >= 30) {
cdata->_pad_x_size = scan.get_uint32();
cdata->_pad_y_size = scan.get_uint32();
cdata->_pad_z_size = scan.get_uint32();
} else {
do_set_pad_size(cdata, 0, 0, 0);
}
cdata->_num_views = 1;
if (manager->get_file_minor_ver() >= 26) {
cdata->_num_views = scan.get_uint32();
}
cdata->_component_type = (ComponentType)scan.get_uint8();
cdata->_component_width = scan.get_uint8();
cdata->_ram_image_compression = CM_off;
if (manager->get_file_minor_ver() >= 1) {
cdata->_ram_image_compression = (CompressionMode)scan.get_uint8();
}
int num_ram_images = 1;
if (manager->get_file_minor_ver() >= 3) {
num_ram_images = scan.get_uint8();
}
cdata->_ram_images.clear();
cdata->_ram_images.reserve(num_ram_images);
for (int n = 0; n < num_ram_images; ++n) {
cdata->_ram_images.push_back(RamImage());
cdata->_ram_images[n]._page_size = get_expected_ram_page_size();
if (manager->get_file_minor_ver() >= 1) {
cdata->_ram_images[n]._page_size = scan.get_uint32();
}
size_t u_size = scan.get_uint32();
// fill the cdata->_image buffer with image data
PTA_uchar image = PTA_uchar::empty_array(u_size, get_class_type());
for (size_t u_idx = 0; u_idx < u_size; ++u_idx) {
image[(int)u_idx] = scan.get_uint8();
}
cdata->_ram_images[n]._image = image;
}
cdata->_loaded_from_image = true;
cdata->inc_image_modified();
}
////////////////////////////////////////////////////////////////////
// Function: Texture::do_fillin_from
// Access: Protected, Virtual
// Description: Called in make_from_bam(), this method properly
// copies the attributes from the bam stream (as stored
// in dummy) into this texture, updating the modified
// flags appropriately.
////////////////////////////////////////////////////////////////////
void Texture::
do_fillin_from(CData *cdata, const Texture *dummy) {
// Use the setters instead of setting these directly, so we can
// correctly avoid incrementing cdata->_properties_modified if none of
// these actually change. (Otherwise, we'd have to reload the
// texture to the GSG every time we loaded a new bam file that
// reference the texture, since each bam file reference passes
// through this function.)
CDReader cdata_dummy(dummy->_cycler);
do_set_wrap_u(cdata, cdata_dummy->_wrap_u);
do_set_wrap_v(cdata, cdata_dummy->_wrap_v);
do_set_wrap_w(cdata, cdata_dummy->_wrap_w);
do_set_border_color(cdata, cdata_dummy->_border_color);
if (cdata_dummy->_minfilter != FT_default) {
do_set_minfilter(cdata, cdata_dummy->_minfilter);
}
if (cdata_dummy->_magfilter != FT_default) {
do_set_magfilter(cdata, cdata_dummy->_magfilter);
}
if (cdata_dummy->_anisotropic_degree != 0) {
do_set_anisotropic_degree(cdata, cdata_dummy->_anisotropic_degree);
}
if (cdata_dummy->_compression != CM_default) {
do_set_compression(cdata, cdata_dummy->_compression);
}
if (cdata_dummy->_quality_level != QL_default) {
do_set_quality_level(cdata, cdata_dummy->_quality_level);
}
Format format = cdata_dummy->_format;
int num_components = cdata_dummy->_num_components;
if (num_components == cdata->_num_components) {
// Only reset the format if the number of components hasn't
// changed, since if the number of components has changed our
// texture no longer matches what it was when the bam was
// written.
do_set_format(cdata, format);
}
if (!cdata_dummy->_simple_ram_image._image.empty()) {
// Only replace the simple ram image if it was generated more
// recently than the one we already have.
if (cdata->_simple_ram_image._image.empty() ||
cdata_dummy->_simple_image_date_generated > cdata->_simple_image_date_generated) {
do_set_simple_ram_image(cdata,
cdata_dummy->_simple_ram_image._image,
cdata_dummy->_simple_x_size,
cdata_dummy->_simple_y_size);
cdata->_simple_image_date_generated = cdata_dummy->_simple_image_date_generated;
}
}
}
////////////////////////////////////////////////////////////////////
// Function: Texture::CData::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
Texture::CData::
CData() {
_primary_file_num_channels = 0;
_alpha_file_channel = 0;
_magfilter = FT_default;
_minfilter = FT_default;
_wrap_u = WM_repeat;
_wrap_v = WM_repeat;
_wrap_w = WM_repeat;
_anisotropic_degree = 0;
_keep_ram_image = true;
_border_color.set(0.0f, 0.0f, 0.0f, 1.0f);
_compression = CM_default;
_auto_texture_scale = ATS_unspecified;
_ram_image_compression = CM_off;
_render_to_texture = false;
_match_framebuffer_format = false;
_post_load_store_cache = false;
_quality_level = QL_default;
_texture_type = TT_2d_texture;
_x_size = 0;
_y_size = 1;
_z_size = 1;
_num_views = 1;
// We will override the format in a moment (in the Texture
// constructor), but set it to something else first to avoid the
// check in do_set_format depending on an uninitialized value.
_format = F_rgba;
_pad_x_size = 0;
_pad_y_size = 0;
_pad_z_size = 0;
_orig_file_x_size = 0;
_orig_file_y_size = 0;
_loaded_from_image = false;
_loaded_from_txo = false;
_has_read_pages = false;
_has_read_mipmaps = false;
_num_mipmap_levels_read = 0;
_simple_x_size = 0;
_simple_y_size = 0;
_simple_ram_image._page_size = 0;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::CData::Copy Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
Texture::CData::
CData(const Texture::CData &copy) {
_num_mipmap_levels_read = 0;
do_assign(&copy);
_properties_modified = copy._properties_modified;
_image_modified = copy._image_modified;
_simple_image_modified = copy._simple_image_modified;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::CData::make_copy
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
CycleData *Texture::CData::
make_copy() const {
return new CData(*this);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::CData::do_assign
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
void Texture::CData::
do_assign(const Texture::CData *copy) {
_filename = copy->_filename;
_alpha_filename = copy->_alpha_filename;
if (!copy->_fullpath.empty()) {
// Since the fullpath is often empty on a file loaded directly
// from a txo, we only assign the fullpath if it is not empty.
_fullpath = copy->_fullpath;
_alpha_fullpath = copy->_alpha_fullpath;
}
_primary_file_num_channels = copy->_primary_file_num_channels;
_alpha_file_channel = copy->_alpha_file_channel;
_x_size = copy->_x_size;
_y_size = copy->_y_size;
_z_size = copy->_z_size;
_num_views = copy->_num_views;
_pad_x_size = copy->_pad_x_size;
_pad_y_size = copy->_pad_y_size;
_pad_z_size = copy->_pad_z_size;
_orig_file_x_size = copy->_orig_file_x_size;
_orig_file_y_size = copy->_orig_file_y_size;
_num_components = copy->_num_components;
_component_width = copy->_component_width;
_texture_type = copy->_texture_type;
_format = copy->_format;
_component_type = copy->_component_type;
_loaded_from_image = copy->_loaded_from_image;
_loaded_from_txo = copy->_loaded_from_txo;
_has_read_pages = copy->_has_read_pages;
_has_read_mipmaps = copy->_has_read_mipmaps;
_num_mipmap_levels_read = copy->_num_mipmap_levels_read;
_wrap_u = copy->_wrap_u;
_wrap_v = copy->_wrap_v;
_wrap_w = copy->_wrap_w;
_minfilter = copy->_minfilter;
_magfilter = copy->_magfilter;
_anisotropic_degree = copy->_anisotropic_degree;
_keep_ram_image = copy->_keep_ram_image;
_border_color = copy->_border_color;
_compression = copy->_compression;
_match_framebuffer_format = copy->_match_framebuffer_format;
_quality_level = copy->_quality_level;
_auto_texture_scale = copy->_auto_texture_scale;
_ram_image_compression = copy->_ram_image_compression;
_ram_images = copy->_ram_images;
_simple_x_size = copy->_simple_x_size;
_simple_y_size = copy->_simple_y_size;
_simple_ram_image = copy->_simple_ram_image;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::CData::write_datagram
// Access: Public, Virtual
// Description: Writes the contents of this object to the datagram
// for shipping out to a Bam file.
////////////////////////////////////////////////////////////////////
void Texture::CData::
write_datagram(BamWriter *manager, Datagram &dg) const {
}
////////////////////////////////////////////////////////////////////
// Function: Texture::CData::complete_pointers
// Access: Public, Virtual
// Description: Receives an array of pointers, one for each time
// manager->read_pointer() was called in fillin().
// Returns the number of pointers processed.
////////////////////////////////////////////////////////////////////
int Texture::CData::
complete_pointers(TypedWritable **p_list, BamReader *manager) {
return 0;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::CData::fillin
// Access: Public, Virtual
// Description: This internal function is called by make_from_bam to
// read in all of the relevant data from the BamFile for
// the new Geom.
////////////////////////////////////////////////////////////////////
void Texture::CData::
fillin(DatagramIterator &scan, BamReader *manager) {
}
////////////////////////////////////////////////////////////////////
// Function: Texture::TextureType output operator
// Description:
////////////////////////////////////////////////////////////////////
ostream &
operator << (ostream &out, Texture::TextureType tt) {
return out << Texture::format_texture_type(tt);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::ComponentType output operator
// Description:
////////////////////////////////////////////////////////////////////
ostream &
operator << (ostream &out, Texture::ComponentType ct) {
return out << Texture::format_component_type(ct);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::Format output operator
// Description:
////////////////////////////////////////////////////////////////////
ostream &
operator << (ostream &out, Texture::Format f) {
return out << Texture::format_format(f);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::FilterType output operator
// Description:
////////////////////////////////////////////////////////////////////
ostream &
operator << (ostream &out, Texture::FilterType ft) {
return out << Texture::format_filter_type(ft);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::FilterType input operator
// Description:
////////////////////////////////////////////////////////////////////
istream &
operator >> (istream &in, Texture::FilterType &ft) {
string word;
in >> word;
ft = Texture::string_filter_type(word);
return in;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::WrapMode output operator
// Description:
////////////////////////////////////////////////////////////////////
ostream &
operator << (ostream &out, Texture::WrapMode wm) {
return out << Texture::format_wrap_mode(wm);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::WrapMode input operator
// Description:
////////////////////////////////////////////////////////////////////
istream &
operator >> (istream &in, Texture::WrapMode &wm) {
string word;
in >> word;
wm = Texture::string_wrap_mode(word);
return in;
}
////////////////////////////////////////////////////////////////////
// Function: Texture::CompressionMode output operator
// Description:
////////////////////////////////////////////////////////////////////
ostream &
operator << (ostream &out, Texture::CompressionMode cm) {
return out << Texture::format_compression_mode(cm);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::QualityLevel output operator
// Description:
////////////////////////////////////////////////////////////////////
ostream &
operator << (ostream &out, Texture::QualityLevel tql) {
return out << Texture::format_quality_level(tql);
}
////////////////////////////////////////////////////////////////////
// Function: Texture::QualityLevel input operator
// Description:
////////////////////////////////////////////////////////////////////
istream &
operator >> (istream &in, Texture::QualityLevel &tql) {
string word;
in >> word;
tql = Texture::string_quality_level(word);
return in;
}