diff --git a/panda/src/display/graphicsEngine.cxx b/panda/src/display/graphicsEngine.cxx index 03ec0b24ed..8952b6f27a 100644 --- a/panda/src/display/graphicsEngine.cxx +++ b/panda/src/display/graphicsEngine.cxx @@ -744,6 +744,63 @@ flip_frame() { do_flip_frame(); } } + +//////////////////////////////////////////////////////////////////// +// Function: GraphicsEngine::extract_texture_data +// Access: Published +// Description: Asks the indicated GraphicsStateGuardian to retrieve +// the texture memory image of the indicated texture and +// store it in the texture's ram_image field. The image +// can then be written to disk via Texture::write(), or +// otherwise manipulated on the CPU. +// +// This is useful for retrieving the contents of a +// texture that has been somehow generated on the +// graphics card, instead of having been loaded the +// normal way via Texture::read() or Texture::load(). +// It is particularly useful for getting the data +// associated with a compressed texture image. +// +// Since this requires a round-trip to the draw thread, +// it may require waiting for the current thread to +// finish rendering if it is called in a multithreaded +// environment. However, you can call this several +// consecutive times on different textures for little +// additional cost. +// +// If the texture has not yet been loaded to the GSG in +// question, it will be loaded immediately. +// +// The return value is true if the operation is +// successful, false otherwise. +//////////////////////////////////////////////////////////////////// +bool GraphicsEngine:: +extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg) { + MutexHolder holder(_lock); + + string draw_name = gsg->get_threading_model().get_draw_name(); + if (draw_name.empty()) { + // A single-threaded environment. No problem. + return gsg->extract_texture_data(tex); + + } else { + // A multi-threaded environment. We have to wait until the draw + // thread has finished its current task. + WindowRenderer *wr = get_window_renderer(draw_name, 0); + RenderThread *thread = (RenderThread *)wr; + MutexHolder holder2(thread->_cv_mutex); + + while (thread->_thread_state != TS_wait) { + thread->_cv_done.wait(); + } + + // OK, now the draw thread is idle. That's really good enough for + // our purposes; we don't *actually* need to make the draw thread + // do the work--it's sufficient that it's not doing anything else + // while we access the GSG. + return gsg->extract_texture_data(tex); + } +} //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::add_callback diff --git a/panda/src/display/graphicsEngine.h b/panda/src/display/graphicsEngine.h index 8cf4b020cf..d471aba9d5 100644 --- a/panda/src/display/graphicsEngine.h +++ b/panda/src/display/graphicsEngine.h @@ -97,6 +97,8 @@ PUBLISHED: void sync_frame(); void flip_frame(); + bool extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg); + public: enum ThreadState { TS_wait, diff --git a/panda/src/display/graphicsStateGuardian.I b/panda/src/display/graphicsStateGuardian.I index b266abc397..40be314c81 100644 --- a/panda/src/display/graphicsStateGuardian.I +++ b/panda/src/display/graphicsStateGuardian.I @@ -356,6 +356,29 @@ get_supports_tex_non_pow2() const { return _supports_tex_non_pow2; } +//////////////////////////////////////////////////////////////////// +// Function: GraphicsStateGuardian::get_supports_compressed_texture +// Access: Published +// Description: Returns true if this GSG can compress textures as it +// loads them into texture memory, and/or accept +// pre-compressed textures for storing. +//////////////////////////////////////////////////////////////////// +INLINE bool GraphicsStateGuardian:: +get_supports_compressed_texture() const { + return _supports_compressed_texture; +} + +//////////////////////////////////////////////////////////////////// +// Function: GraphicsStateGuardian::get_supports_compressed_texture_format +// Access: Published +// Description: Returns true if this GSG can accept textures +// pre-compressed in the indicated format. +//////////////////////////////////////////////////////////////////// +INLINE bool GraphicsStateGuardian:: +get_supports_compressed_texture_format(Texture::CompressionMode compression) const { + return _compressed_texture_formats.get_bit(compression); +} + //////////////////////////////////////////////////////////////////// // Function: GraphicsStateGuardian::get_max_lights // Access: Published diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx index 9661851e82..8989d61ff2 100644 --- a/panda/src/display/graphicsStateGuardian.cxx +++ b/panda/src/display/graphicsStateGuardian.cxx @@ -136,6 +136,9 @@ GraphicsStateGuardian(const FrameBufferProperties &properties, _supports_3d_texture = false; _supports_cube_map = false; _supports_tex_non_pow2 = false; + _supports_compressed_texture = false; + _compressed_texture_formats.clear(); + _compressed_texture_formats.set_bit(Texture::CM_off); // Assume no limits on number of lights or clip planes. _max_lights = -1; @@ -415,6 +418,23 @@ void GraphicsStateGuardian:: release_texture(TextureContext *) { } +//////////////////////////////////////////////////////////////////// +// Function: GraphicsStateGuardian::extract_texture_data +// Access: Public, Virtual +// Description: This method should only be called by the +// GraphicsEngine. Do not call it directly; call +// GraphicsEngine::extract_texture_data() instead. +// +// This method will be called in the draw thread to +// download the texture memory's image into its +// ram_image value. It returns true on success, false +// otherwise. +//////////////////////////////////////////////////////////////////// +bool GraphicsStateGuardian:: +extract_texture_data(Texture *) { + return false; +} + //////////////////////////////////////////////////////////////////// // Function: GraphicsStateGuardian::prepare_geom // Access: Public, Virtual diff --git a/panda/src/display/graphicsStateGuardian.h b/panda/src/display/graphicsStateGuardian.h index 8f3e41fefc..2e72ccf0e0 100644 --- a/panda/src/display/graphicsStateGuardian.h +++ b/panda/src/display/graphicsStateGuardian.h @@ -43,6 +43,8 @@ #include "pvector.h" #include "attribSlots.h" #include "shaderContext.h" +#include "bitMask.h" +#include "texture.h" class DrawableRegion; class GraphicsEngine; @@ -99,6 +101,9 @@ PUBLISHED: INLINE bool get_supports_cube_map() const; INLINE bool get_supports_tex_non_pow2() const; + INLINE bool get_supports_compressed_texture() const; + INLINE bool get_supports_compressed_texture_format(Texture::CompressionMode compression) const; + INLINE int get_max_lights() const; INLINE int get_max_clip_planes() const; @@ -132,6 +137,7 @@ public: virtual TextureContext *prepare_texture(Texture *tex); virtual void release_texture(TextureContext *tc); + virtual bool extract_texture_data(Texture *tex); virtual GeomContext *prepare_geom(Geom *geom); virtual void release_geom(GeomContext *gc); @@ -347,6 +353,9 @@ protected: bool _supports_cube_map; bool _supports_tex_non_pow2; + bool _supports_compressed_texture; + BitMask32 _compressed_texture_formats; + int _max_lights; int _max_clip_planes; diff --git a/panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx b/panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx index 41d6d679b2..932878522b 100644 --- a/panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx +++ b/panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx @@ -148,6 +148,13 @@ DXGraphicsStateGuardian8:: TextureContext *DXGraphicsStateGuardian8:: prepare_texture(Texture *tex) { DXTextureContext8 *dtc = new DXTextureContext8(tex); + + if (!get_supports_compressed_texture_format(tex->get_ram_image_compression())) { + dxgsg8_cat.error() + << *dtc->_texture << " is stored in an unsupported compressed format.\n"; + return NULL; + } + if (!dtc->create_texture(*_screen)) { delete dtc; return NULL; @@ -189,7 +196,14 @@ apply_texture(int i, TextureContext *tc) { // error or oversight. if ((dirty & Texture::DF_image) == 0) { dxgsg8_cat.warning() - << "Texture " << *dtc->_texture << " has changed mipmap state.\n"; + << *dtc->_texture << " has changed mipmap state.\n"; + } + + if (!get_supports_compressed_texture_format(tc->_texture->get_ram_image_compression())) { + dxgsg8_cat.error() + << *dtc->_texture << " is stored in an unsupported compressed format.\n"; + _d3d_device->SetTextureStageState(i, D3DTSS_COLOROP, D3DTOP_DISABLE); + return; } if (!dtc->create_texture(*_screen)) { diff --git a/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx b/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx index b8b42c0434..b571a9e7d1 100755 --- a/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx +++ b/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx @@ -191,6 +191,13 @@ DXGraphicsStateGuardian9:: TextureContext *DXGraphicsStateGuardian9:: prepare_texture(Texture *tex) { DXTextureContext9 *dtc = new DXTextureContext9(tex); + + if (!get_supports_compressed_texture_format(tex->get_ram_image_compression())) { + dxgsg9_cat.error() + << *dtc->_texture << " is stored in an unsupported compressed format.\n"; + return NULL; + } + if (!dtc->create_texture(*_screen)) { delete dtc; return NULL; @@ -237,7 +244,14 @@ apply_texture(int i, TextureContext *tc) { // error or oversight. if ((dirty & Texture::DF_image) == 0) { dxgsg9_cat.warning() - << "Texture " << *dtc->_texture << " has changed mipmap state.\n"; + << *dtc->_texture << " has changed mipmap state.\n"; + } + + if (!get_supports_compressed_texture_format(tc->_texture->get_ram_image_compression())) { + dxgsg9_cat.error() + << *dtc->_texture << " is stored in an unsupported compressed format.\n"; + _d3d_device->SetTextureStageState(i, D3DTSS_COLOROP, D3DTOP_DISABLE); + return; } if (!dtc->create_texture(*_screen)) { diff --git a/panda/src/egg/eggTexture.I b/panda/src/egg/eggTexture.I index 435545d60c..f6ffe53ade 100644 --- a/panda/src/egg/eggTexture.I +++ b/panda/src/egg/eggTexture.I @@ -63,6 +63,26 @@ get_format() const { return _format; } +//////////////////////////////////////////////////////////////////// +// Function: EggTexture::set_compression_mode +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void EggTexture:: +set_compression_mode(CompressionMode mode) { + _compression_mode = mode; +} + +//////////////////////////////////////////////////////////////////// +// Function: EggTexture::get_compression_mode +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE EggTexture::CompressionMode EggTexture:: +get_compression_mode() const { + return _compression_mode; +} + //////////////////////////////////////////////////////////////////// // Function: EggTexture::set_wrap_mode // Access: Published diff --git a/panda/src/egg/eggTexture.cxx b/panda/src/egg/eggTexture.cxx index a8642daa92..02f3032c59 100644 --- a/panda/src/egg/eggTexture.cxx +++ b/panda/src/egg/eggTexture.cxx @@ -37,6 +37,7 @@ EggTexture(const string &tref_name, const string &filename) { _texture_type = TT_unspecified; _format = F_unspecified; + _compression_mode = CM_default; _wrap_mode = WM_unspecified; _wrap_u = WM_unspecified; _wrap_v = WM_unspecified; @@ -80,6 +81,7 @@ operator = (const EggTexture ©) { _texture_type = copy._texture_type; _format = copy._format; + _compression_mode = copy._compression_mode; _wrap_mode = copy._wrap_mode; _wrap_u = copy._wrap_u; _wrap_v = copy._wrap_v; @@ -152,6 +154,11 @@ write(ostream &out, int indent_level) const { << " format { " << get_format() << " }\n"; } + if (get_compression_mode() != CM_default) { + indent(out, indent_level + 2) + << " compression { " << get_compression_mode() << " }\n"; + } + if (get_wrap_mode() != WM_unspecified) { indent(out, indent_level + 2) << " wrap { " << get_wrap_mode() << " }\n"; @@ -360,6 +367,7 @@ is_equivalent_to(const EggTexture &other, int eq) const { //cout << "compared by attributes" << endl; if (_texture_type != other._texture_type || _format != other._format || + _compression_mode != other._compression_mode || _wrap_mode != other._wrap_mode || _wrap_u != other._wrap_u || _wrap_v != other._wrap_v || @@ -442,6 +450,9 @@ sorts_less_than(const EggTexture &other, int eq) const { if (_format != other._format) { return (int)_format < (int)other._format; } + if (_compression_mode != other._compression_mode) { + return (int)_compression_mode < (int)other._compression_mode; + } if (_wrap_mode != other._wrap_mode) { return (int)_wrap_mode < (int)other._wrap_mode; } @@ -617,7 +628,7 @@ string_texture_type(const string &string) { } else if (cmp_nocase_uh(string, "3d") == 0 || cmp_nocase_uh(string, "3dtexture") == 0 || cmp_nocase_uh(string, "3d_texture") == 0) { - return TT_2d_texture; + return TT_3d_texture; } else if (cmp_nocase_uh(string, "cube") == 0 || cmp_nocase_uh(string, "cubemap") == 0 || @@ -680,6 +691,36 @@ string_format(const string &string) { } } +//////////////////////////////////////////////////////////////////// +// Function: EggTexture::string_compression_mode +// Access: Published, Static +// Description: Returns the CompressionMode value associated with the given +// string representation, or CM_default if the string +// does not match any known CompressionMode value. +//////////////////////////////////////////////////////////////////// +EggTexture::CompressionMode EggTexture:: +string_compression_mode(const string &string) { + if (cmp_nocase_uh(string, "off") == 0) { + return CM_off; + } else if (cmp_nocase_uh(string, "on") == 0) { + return CM_on; + } else if (cmp_nocase_uh(string, "fxt1") == 0) { + return CM_fxt1; + } else if (cmp_nocase_uh(string, "dxt1") == 0) { + return CM_dxt1; + } else if (cmp_nocase_uh(string, "dxt2") == 0) { + return CM_dxt2; + } else if (cmp_nocase_uh(string, "dxt3") == 0) { + return CM_dxt3; + } else if (cmp_nocase_uh(string, "dxt4") == 0) { + return CM_dxt4; + } else if (cmp_nocase_uh(string, "dxt5") == 0) { + return CM_dxt5; + } else { + return CM_default; + } +} + //////////////////////////////////////////////////////////////////// // Function: EggTexture::string_wrap_mode // Access: Published, Static @@ -1067,6 +1108,36 @@ ostream &operator << (ostream &out, EggTexture::Format format) { return out << "(**invalid**)"; } +//////////////////////////////////////////////////////////////////// +// Function: CompressionMode output operator +// Description: +//////////////////////////////////////////////////////////////////// +ostream &operator << (ostream &out, EggTexture::CompressionMode mode) { + switch (mode) { + case EggTexture::CM_default: + return out << "default"; + case EggTexture::CM_off: + return out << "off"; + case EggTexture::CM_on: + return out << "on"; + case EggTexture::CM_fxt1: + return out << "fxt1"; + case EggTexture::CM_dxt1: + return out << "dxt1"; + case EggTexture::CM_dxt2: + return out << "dxt2"; + case EggTexture::CM_dxt3: + return out << "dxt3"; + case EggTexture::CM_dxt4: + return out << "dxt4"; + case EggTexture::CM_dxt5: + return out << "dxt5"; + } + + nassertr(false, out); + return out << "(**invalid**)"; +} + //////////////////////////////////////////////////////////////////// // Function: WrapMode output operator // Description: diff --git a/panda/src/egg/eggTexture.h b/panda/src/egg/eggTexture.h index 83769085ac..f9d4db9ebe 100644 --- a/panda/src/egg/eggTexture.h +++ b/panda/src/egg/eggTexture.h @@ -69,6 +69,10 @@ PUBLISHED: F_red, F_green, F_blue, F_alpha, F_luminance, F_luminance_alpha, F_luminance_alphamask }; + enum CompressionMode { + CM_default, CM_off, CM_on, + CM_fxt1, CM_dxt1, CM_dxt2, CM_dxt3, CM_dxt4, CM_dxt5, + }; enum WrapMode { WM_unspecified, WM_clamp, WM_repeat, WM_mirror, WM_mirror_once, WM_border_color @@ -157,6 +161,9 @@ PUBLISHED: INLINE void set_format(Format format); INLINE Format get_format() const; + INLINE void set_compression_mode(CompressionMode mode); + INLINE CompressionMode get_compression_mode() const; + INLINE void set_wrap_mode(WrapMode mode); INLINE WrapMode get_wrap_mode() const; @@ -253,6 +260,7 @@ PUBLISHED: static TextureType string_texture_type(const string &string); static Format string_format(const string &string); + static CompressionMode string_compression_mode(const string &string); static WrapMode string_wrap_mode(const string &string); static FilterType string_filter_type(const string &string); static EnvType string_env_type(const string &string); @@ -286,6 +294,7 @@ private: TextureType _texture_type; Format _format; + CompressionMode _compression_mode; WrapMode _wrap_mode, _wrap_u, _wrap_v, _wrap_w; FilterType _minfilter, _magfilter; int _anisotropic_degree; @@ -367,6 +376,7 @@ INLINE ostream &operator << (ostream &out, const EggTexture &n) { EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::TextureType texture_type); EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::Format format); +EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::CompressionMode mode); EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::WrapMode mode); EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::FilterType type); EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::EnvType type); diff --git a/panda/src/egg/parser.yxx b/panda/src/egg/parser.yxx index 847931149c..cd33a2bf57 100644 --- a/panda/src/egg/parser.yxx +++ b/panda/src/egg/parser.yxx @@ -360,6 +360,14 @@ texture_body: texture->set_format(f); } + } else if (cmp_nocase_uh(name, "compression") == 0) { + EggTexture::CompressionMode w = EggTexture::string_compression_mode(strval); + if (w == EggTexture::CM_default) { + eggyywarning("Unknown texture compression mode " + strval); + } else { + texture->set_compression_mode(w); + } + } else if (cmp_nocase_uh(name, "wrap") == 0) { EggTexture::WrapMode w = EggTexture::string_wrap_mode(strval); if (w == EggTexture::WM_unspecified) { diff --git a/panda/src/egg2pg/eggLoader.cxx b/panda/src/egg2pg/eggLoader.cxx index a78258c7e6..fa91cba260 100644 --- a/panda/src/egg2pg/eggLoader.cxx +++ b/panda/src/egg2pg/eggLoader.cxx @@ -933,9 +933,23 @@ load_texture(TextureDef &def, const EggTexture *egg_tex) { //////////////////////////////////////////////////////////////////// void EggLoader:: apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) { - tex->set_wrap_u(convert_wrap_mode(egg_tex->determine_wrap_u())); - tex->set_wrap_v(convert_wrap_mode(egg_tex->determine_wrap_v())); - tex->set_wrap_w(convert_wrap_mode(egg_tex->determine_wrap_w())); + if (egg_tex->get_compression_mode() != EggTexture::CM_default) { + tex->set_compression(convert_compression_mode(egg_tex->get_compression_mode())); + } + + EggTexture::WrapMode wrap_u = egg_tex->determine_wrap_u(); + EggTexture::WrapMode wrap_v = egg_tex->determine_wrap_v(); + EggTexture::WrapMode wrap_w = egg_tex->determine_wrap_w(); + + if (wrap_u != EggTexture::WM_unspecified) { + tex->set_wrap_u(convert_wrap_mode(wrap_u)); + } + if (wrap_v != EggTexture::WM_unspecified) { + tex->set_wrap_v(convert_wrap_mode(wrap_v)); + } + if (wrap_w != EggTexture::WM_unspecified) { + tex->set_wrap_w(convert_wrap_mode(wrap_w)); + } if (egg_tex->has_border_color()) { tex->set_border_color(egg_tex->get_border_color()); @@ -1013,12 +1027,7 @@ apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) { break; case EggTexture::FT_unspecified: - // Default is bilinear, unless egg_ignore_filters is specified. - if (egg_ignore_filters) { - tex->set_minfilter(Texture::FT_nearest); - } else { - tex->set_minfilter(Texture::FT_linear); - } + break; } switch (egg_tex->get_magfilter()) { @@ -1041,12 +1050,7 @@ apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) { break; case EggTexture::FT_unspecified: - // Default is bilinear, unless egg_ignore_filters is specified. - if (egg_ignore_filters) { - tex->set_magfilter(Texture::FT_nearest); - } else { - tex->set_magfilter(Texture::FT_linear); - } + break; } if (egg_tex->has_anisotropic_degree()) { @@ -1176,6 +1180,50 @@ apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) { } } +//////////////////////////////////////////////////////////////////// +// Function: EggLoader::convert_compression_mode +// Access: Private +// Description: Returns the Texture::CompressionMode enum +// corresponding to the EggTexture::CompressionMode. +// Returns CM_default if the compression mode is +// unspecified. +//////////////////////////////////////////////////////////////////// +Texture::CompressionMode EggLoader:: +convert_compression_mode(EggTexture::CompressionMode compression_mode) const { + switch (compression_mode) { + case EggTexture::CM_off: + return Texture::CM_off; + + case EggTexture::CM_on: + return Texture::CM_on; + + case EggTexture::CM_fxt1: + return Texture::CM_fxt1; + + case EggTexture::CM_dxt1: + return Texture::CM_dxt1; + + case EggTexture::CM_dxt2: + return Texture::CM_dxt2; + + case EggTexture::CM_dxt3: + return Texture::CM_dxt3; + + case EggTexture::CM_dxt4: + return Texture::CM_dxt4; + + case EggTexture::CM_dxt5: + return Texture::CM_dxt5; + + case EggTexture::CM_default: + return Texture::CM_default; + } + + egg2pg_cat.warning() + << "Unexpected texture compression flag: " << (int)compression_mode << "\n"; + return Texture::CM_default; +} + //////////////////////////////////////////////////////////////////// // Function: EggLoader::convert_wrap_mode // Access: Private diff --git a/panda/src/egg2pg/eggLoader.h b/panda/src/egg2pg/eggLoader.h index e840de5cbd..6c67de6b12 100644 --- a/panda/src/egg2pg/eggLoader.h +++ b/panda/src/egg2pg/eggLoader.h @@ -122,6 +122,7 @@ private: void load_textures(); bool load_texture(TextureDef &def, const EggTexture *egg_tex); void apply_texture_attributes(Texture *tex, const EggTexture *egg_tex); + Texture::CompressionMode convert_compression_mode(EggTexture::CompressionMode compression_mode) const; Texture::WrapMode convert_wrap_mode(EggTexture::WrapMode wrap_mode) const; PT(TextureStage) make_texture_stage(const EggTexture *egg_tex); diff --git a/panda/src/framework/windowFramework.cxx b/panda/src/framework/windowFramework.cxx index af7e38776c..caae6babfe 100644 --- a/panda/src/framework/windowFramework.cxx +++ b/panda/src/framework/windowFramework.cxx @@ -593,12 +593,19 @@ load_model(const NodePath &parent, Filename filename) { LoaderFileType *model_type = reg->get_type_from_extension(extension); if (model_type == (LoaderFileType *)NULL) { - // The extension isn't a known model file type, is it a known - // texture extension? - TexturePool *texture_pool = TexturePool::get_global_ptr(); - if (texture_pool->get_texture_type(extension) != NULL) { - // It is a known texture extension. - is_image = true; + // The extension isn't a known model file type; is it a known + // image file extension? + if (extension == "txo") { + // A texture object. Not exactly an image, but certainly a + // texture. + is_image = true; + + } else { + TexturePool *texture_pool = TexturePool::get_global_ptr(); + if (texture_pool->get_texture_type(extension) != NULL) { + // It is a known image file extension. + is_image = true; + } } } } @@ -1093,7 +1100,7 @@ load_image_as_model(const Filename &filename) { int x_size = tex->get_x_size(); int y_size = tex->get_y_size(); - bool has_alpha = false; + bool has_alpha = true; LVecBase2f tex_scale(1.0f, 1.0f); if (tex->is_of_type(VideoTexture::get_class_type())) { @@ -1103,7 +1110,7 @@ load_image_as_model(const Filename &filename) { y_size = vtex->get_video_height(); tex_scale = vtex->get_tex_scale(); - } else { + } else if (!tex->get_loaded_from_txo()) { // Get the size from the original image (the texture may have // scaled it to make a power of 2). PNMImageHeader header; @@ -1127,7 +1134,6 @@ load_image_as_model(const Filename &filename) { float top = y_size / 2.0; PT(GeomNode) card_node = new GeomNode("card"); - card_node->set_attrib(ColorAttrib::make_flat(Colorf(1.0f, 1.0f, 1.0f, 1.0f))); card_node->set_attrib(TextureAttrib::make(tex)); if (has_alpha) { card_node->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index ae120b2464..51f864d7ec 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -427,8 +427,7 @@ reset() { _glDrawRangeElements = null_glDrawRangeElements; } - _supports_3d_texture = - has_extension("GL_EXT_texture3D") || is_at_least_version(1, 2); + _supports_3d_texture = false; if (is_at_least_version(1, 2)) { _supports_3d_texture = true; @@ -458,6 +457,72 @@ reset() { _supports_cube_map = has_extension("GL_ARB_texture_cube_map") || is_at_least_version(1, 3); + _supports_compressed_texture = false; + if (is_at_least_version(1, 3)) { + _supports_compressed_texture = true; + + _glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC) + get_extension_func(GLPREFIX_QUOTED, "CompressedTexImage2D"); + _glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) + get_extension_func(GLPREFIX_QUOTED, "CompressedTexSubImage2D"); + _glGetCompressedTexImage = (PFNGLGETCOMPRESSEDTEXIMAGEPROC) + get_extension_func(GLPREFIX_QUOTED, "GetCompressedTexImage"); + + } else if (has_extension("GL_ARB_texture_compression")) { + _supports_compressed_texture = true; + + _glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC) + get_extension_func(GLPREFIX_QUOTED, "CompressedTexImage2DARB"); + _glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) + get_extension_func(GLPREFIX_QUOTED, "CompressedTexSubImage2DARB"); + _glGetCompressedTexImage = (PFNGLGETCOMPRESSEDTEXIMAGEPROC) + get_extension_func(GLPREFIX_QUOTED, "GetCompressedTexImageARB"); + } + + if (_supports_compressed_texture) { + if (_glCompressedTexImage2D == NULL || + _glCompressedTexSubImage2D == NULL || + _glGetCompressedTexImage == NULL) { + GLCAT.warning() + << "Compressed textures advertised as supported by OpenGL runtime, but could not get pointers to extension functions.\n"; + _supports_compressed_texture = false; + } + } + + if (_supports_compressed_texture) { + _compressed_texture_formats.set_bit(Texture::CM_on); + + GLint num_compressed_formats; + GLP(GetIntegerv)(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &num_compressed_formats); + GLint *formats = new GLint[num_compressed_formats]; + GLP(GetIntegerv)(GL_COMPRESSED_TEXTURE_FORMATS, formats); + for (int i = 0; i < num_compressed_formats; ++i) { + switch (formats[i]) { + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + _compressed_texture_formats.set_bit(Texture::CM_dxt1); + break; + + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + _compressed_texture_formats.set_bit(Texture::CM_dxt3); + break; + + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + _compressed_texture_formats.set_bit(Texture::CM_dxt5); + break; + + case GL_COMPRESSED_RGB_FXT1_3DFX: + case GL_COMPRESSED_RGBA_FXT1_3DFX: + _compressed_texture_formats.set_bit(Texture::CM_fxt1); + break; + + default: + break; + } + } + delete[] formats; + } + _supports_bgr = has_extension("GL_EXT_bgra") || is_at_least_version(1, 2); _supports_rescale_normal = @@ -764,6 +829,57 @@ reset() { GLCAT.debug() << "immediate mode commands will be used instead of vertex arrays.\n"; } + + if (!_supports_compressed_texture) { + GLCAT.debug() + << "Texture compression is not supported.\n"; + + } else { + GLint num_compressed_formats; + GLP(GetIntegerv)(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &num_compressed_formats); + if (num_compressed_formats == 0) { + GLCAT.debug() + << "No specific compressed texture formats are supported.\n"; + } else { + GLCAT.debug() + << "Supported compressed texture formats:\n"; + GLint *formats = new GLint[num_compressed_formats]; + GLP(GetIntegerv)(GL_COMPRESSED_TEXTURE_FORMATS, formats); + for (int i = 0; i < num_compressed_formats; ++i) { + switch (formats[i]) { + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + GLCAT.debug(false) << " GL_COMPRESSED_RGB_S3TC_DXT1_EXT\n"; + break; + + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + GLCAT.debug(false) << " GL_COMPRESSED_RGBA_S3TC_DXT1_EXT\n"; + break; + + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + GLCAT.debug(false) << " GL_COMPRESSED_RGBA_S3TC_DXT3_EXT\n"; + break; + + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + GLCAT.debug(false) << " GL_COMPRESSED_RGBA_S3TC_DXT5_EXT\n"; + break; + + case GL_COMPRESSED_RGB_FXT1_3DFX: + GLCAT.debug(false) << " GL_COMPRESSED_RGB_FXT1_3DFX\n"; + break; + + case GL_COMPRESSED_RGBA_FXT1_3DFX: + GLCAT.debug(false) << " GL_COMPRESSED_RGBA_FXT1_3DFX\n"; + break; + + default: + GLCAT.debug(false) + << " Unknown compressed format 0x" << hex << formats[i] + << dec << "\n"; + } + } + delete[] formats; + } + } } report_my_gl_errors(); @@ -2028,6 +2144,302 @@ release_texture(TextureContext *tc) { delete gtc; } +//////////////////////////////////////////////////////////////////// +// Function: GLGraphicsStateGuardian::extract_texture_data +// Access: Public, Virtual +// Description: This method should only be called by the +// GraphicsEngine. Do not call it directly; call +// GraphicsEngine::extract_texture_data() instead. +// +// This method will be called in the draw thread to +// download the texture memory's image into its +// ram_image value. It returns true on success, false +// otherwise. +//////////////////////////////////////////////////////////////////// +bool CLP(GraphicsStateGuardian):: +extract_texture_data(Texture *tex) { + // Make sure the error stack is cleared out before we begin. + report_my_gl_errors(); + + TextureContext *tc = tex->prepare_now(get_prepared_objects(), this); + nassertr(tc != (TextureContext *)NULL, false); + CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc); + GLenum target = get_texture_target(tex->get_texture_type()); + GLP(BindTexture)(target, gtc->_index); + + report_my_gl_errors(); + + GLint wrap_u, wrap_v, wrap_w; + GLint minfilter, magfilter; + GLfloat border_color[4]; + + GLP(GetTexParameteriv)(target, GL_TEXTURE_WRAP_S, &wrap_u); + GLP(GetTexParameteriv)(target, GL_TEXTURE_WRAP_T, &wrap_v); + wrap_w = GL_REPEAT; + if (_supports_3d_texture) { + GLP(GetTexParameteriv)(target, GL_TEXTURE_WRAP_R, &wrap_w); + } + GLP(GetTexParameteriv)(target, GL_TEXTURE_MIN_FILTER, &minfilter); + + // Mesa has a bug querying this property. + magfilter = GL_LINEAR; + // GLP(GetTexParameteriv)(target, GL_TEXTURE_MAG_FILTER, &magfilter); + + GLP(GetTexParameterfv)(target, GL_TEXTURE_BORDER_COLOR, border_color); + + GLenum page_target = target; + if (target == GL_TEXTURE_CUBE_MAP) { + // We need a particular page to get the level parameter from. + page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X; + } + + GLint width = 0, height = 0, depth = 0; + GLP(GetTexLevelParameteriv)(page_target, 0, GL_TEXTURE_WIDTH, &width); + GLP(GetTexLevelParameteriv)(page_target, 0, GL_TEXTURE_HEIGHT, &height); + if (_supports_3d_texture) { + GLP(GetTexLevelParameteriv)(page_target, 0, GL_TEXTURE_DEPTH, &depth); + } + report_my_gl_errors(); + + GLint internal_format; + GLP(GetTexLevelParameteriv)(page_target, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format); + + // Make sure we were able to query those parameters properly. + GLenum error_code = GLP(GetError)(); + if (error_code != GL_NO_ERROR) { + const GLubyte *error_string = GLUP(ErrorString)(error_code); + GLCAT.error() + << "Unable to query texture parameters for " << tex->get_name(); + if (error_string != (const GLubyte *)NULL) { + GLCAT.error(false) + << " : " << error_string; + } + GLCAT.error(false) + << "\n"; + + return false; + } + + Texture::ComponentType type = Texture::T_unsigned_byte; + Texture::Format format = Texture::F_rgb; + Texture::CompressionMode compression = Texture::CM_off; + + switch (internal_format) { + case GL_COLOR_INDEX: + format = Texture::F_color_index; + break; + case GL_STENCIL_INDEX: + format = Texture::F_stencil_index; + break; + case GL_DEPTH_COMPONENT: + type = Texture::T_float; + format = Texture::F_depth_component; + break; + + case GL_RGBA: + format = Texture::F_rgba; + break; + case GL_RGBA4: + format = Texture::F_rgba4; + break; + case GL_RGBA8: + format = Texture::F_rgba8; + break; + case GL_RGBA12: + type = Texture::T_unsigned_short; + format = Texture::F_rgba12; + break; + + case GL_RGB: + format = Texture::F_rgb; + break; + case GL_RGB5: + format = Texture::F_rgb5; + break; + case GL_RGB5_A1: + format = Texture::F_rgba5; + break; + case GL_RGB8: + format = Texture::F_rgb8; + break; + case GL_RGB12: + format = Texture::F_rgb12; + break; + case GL_R3_G3_B2: + format = Texture::F_rgb332; + + case GL_RED: + format = Texture::F_red; + break; + case GL_GREEN: + format = Texture::F_green; + break; + case GL_BLUE: + format = Texture::F_blue; + break; + case GL_ALPHA: + format = Texture::F_alpha; + break; + case GL_LUMINANCE: + format = Texture::F_luminance; + break; + case GL_LUMINANCE_ALPHA: + format = Texture::F_luminance_alpha; + break; + + case GL_COMPRESSED_RGB: + format = Texture::F_rgb; + compression = Texture::CM_on; + break; + case GL_COMPRESSED_RGBA: + format = Texture::F_rgba; + compression = Texture::CM_on; + break; + case GL_COMPRESSED_ALPHA: + format = Texture::F_alpha; + compression = Texture::CM_on; + break; + case GL_COMPRESSED_LUMINANCE: + format = Texture::F_luminance; + compression = Texture::CM_on; + break; + case GL_COMPRESSED_LUMINANCE_ALPHA: + format = Texture::F_luminance_alpha; + compression = Texture::CM_on; + break; + + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + format = Texture::F_rgb; + compression = Texture::CM_dxt1; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + format = Texture::F_rgbm; + compression = Texture::CM_dxt1; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + format = Texture::F_rgba; + compression = Texture::CM_dxt3; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + format = Texture::F_rgba; + compression = Texture::CM_dxt5; + break; + case GL_COMPRESSED_RGB_FXT1_3DFX: + format = Texture::F_rgb; + compression = Texture::CM_fxt1; + break; + case GL_COMPRESSED_RGBA_FXT1_3DFX: + format = Texture::F_rgba; + compression = Texture::CM_fxt1; + break; + } + + /* + switch (target) { + case GL_TEXTURE_1D: + tex->setup_1d_texture(width, type, format); + break; + + case GL_TEXTURE_2D: + tex->setup_2d_texture(width, height, type, format); + break; + + case GL_TEXTURE_3D: + tex->setup_3d_texture(width, height, depth, type, format); + break; + + case GL_TEXTURE_CUBE_MAP: + tex->setup_cube_map(width, type, format); + break; + } + */ + + tex->set_wrap_u(get_panda_wrap_mode(wrap_u)); + tex->set_wrap_v(get_panda_wrap_mode(wrap_v)); + tex->set_wrap_w(get_panda_wrap_mode(wrap_w)); + tex->set_border_color(Colorf(border_color[0], border_color[1], + border_color[2], border_color[3])); + + tex->set_minfilter(get_panda_filter_type(minfilter)); + // tex->set_magfilter(get_panda_filter_type(magfilter)); + + PTA_uchar image; + size_t page_size = 0; + + if (target == GL_TEXTURE_CUBE_MAP) { + // A cube map, compressed or uncompressed. This we must extract + // one page at a time. + + // If the cube map is compressed, we assume that all the + // compressed pages are exactly the same size. OpenGL doesn't + // make this assumption, but it happens to be true for all + // currently extant compression schemes, and it makes things + // simpler for us. (It also makes things much simpler for the + // graphics hardware, so it's likely to continue to be true for a + // while at least.) + + GLenum external_format = get_external_image_format(tex); + GLenum pixel_type = get_component_type(type); + page_size = tex->get_expected_ram_page_size(); + + if (compression != Texture::CM_off) { + GLint image_size; + GLP(GetTexLevelParameteriv)(page_target, 0, + GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &image_size); + nassertr(image_size <= (int)page_size, false); + page_size = image_size; + } + + image = PTA_uchar::empty_array(page_size * 6); + + for (int z = 0; z < 6; ++z) { + page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + z; + + if (compression == Texture::CM_off) { + GLP(GetTexImage)(page_target, 0, external_format, pixel_type, + image.p() + z * page_size); + } else { + _glGetCompressedTexImage(page_target, 0, image.p() + z * page_size); + } + } + + } else if (compression == Texture::CM_off) { + // An uncompressed 1-d, 2-d, or 3-d texture. + image = PTA_uchar::empty_array(tex->get_expected_ram_image_size()); + GLenum external_format = get_external_image_format(tex); + GLenum pixel_type = get_component_type(type); + GLP(GetTexImage)(target, 0, external_format, pixel_type, image.p()); + + } else { + // A compressed 1-d, 2-d, or 3-d texture. + GLint image_size; + GLP(GetTexLevelParameteriv)(target, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &image_size); + page_size = image_size / tex->get_z_size(); + image = PTA_uchar::empty_array(image_size); + _glGetCompressedTexImage(target, 0, image.p()); + } + + // Now see if we were successful. + error_code = GLP(GetError)(); + if (error_code != GL_NO_ERROR) { + const GLubyte *error_string = GLUP(ErrorString)(error_code); + GLCAT.error() + << "Unable to extract texture for " << tex->get_name(); + if (error_string != (const GLubyte *)NULL) { + GLCAT.error(false) + << " : " << error_string; + } + GLCAT.error(false) + << "\n"; + + return false; + } + + tex->set_ram_image(image, compression, page_size); + + return true; +} + //////////////////////////////////////////////////////////////////// // Function: GLGraphicsStateGuardian::prepare_geom // Access: Public, Virtual @@ -2516,13 +2928,13 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr, GLenum target = get_texture_target(tex->get_texture_type()); GLP(BindTexture)(target, gtc->_index); - GLint internal_format = get_internal_image_format(tex->get_format()); + GLint internal_format = get_internal_image_format(tex); if (z >= 0) { // Copy to a cube map face. This doesn't seem to work too well // with CopyTexSubImage2D, so we always use CopyTexImage2D. GLP(CopyTexImage2D)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + z, 0, - get_internal_image_format(tex->get_format()), + get_internal_image_format(tex), xo, yo, w, h, 0); } else { @@ -2658,7 +3070,7 @@ framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr, component_type, format); } - GLenum external_format = get_external_image_format(format); + GLenum external_format = get_external_image_format(tex); if (GLCAT.is_spam()) { GLCAT.spam() @@ -3909,12 +4321,12 @@ get_texture_target(Texture::TextureType texture_type) const { //////////////////////////////////////////////////////////////////// // Function: GLGraphicsStateGuardian::get_texture_wrap_mode -// Access: Protected, Static +// Access: Protected // Description: Maps from the Texture's internal wrap mode symbols to // GL's. //////////////////////////////////////////////////////////////////// GLenum CLP(GraphicsStateGuardian):: -get_texture_wrap_mode(Texture::WrapMode wm) { +get_texture_wrap_mode(Texture::WrapMode wm) const { if (CLP(ignore_clamp)) { return GL_REPEAT; } @@ -3941,6 +4353,36 @@ get_texture_wrap_mode(Texture::WrapMode wm) { return _edge_clamp; } +//////////////////////////////////////////////////////////////////// +// Function: GLGraphicsStateGuardian::get_panda_wrap_mode +// Access: Protected, Static +// Description: Maps from the GL's internal wrap mode symbols to +// Panda's. +//////////////////////////////////////////////////////////////////// +Texture::WrapMode CLP(GraphicsStateGuardian):: +get_panda_wrap_mode(GLenum wm) { + switch (wm) { + case GL_CLAMP: + case GL_CLAMP_TO_EDGE: + return Texture::WM_clamp; + + case GL_CLAMP_TO_BORDER: + return Texture::WM_border_color; + + case GL_REPEAT: + return Texture::WM_repeat; + + case GL_MIRROR_CLAMP_EXT: + case GL_MIRROR_CLAMP_TO_EDGE_EXT: + return Texture::WM_mirror; + + case GL_MIRROR_CLAMP_TO_BORDER_EXT: + return Texture::WM_mirror_once; + } + GLCAT.error() << "Unexpected GL wrap mode " << (int)wm << "\n"; + return Texture::WM_clamp; +} + //////////////////////////////////////////////////////////////////// // Function: GLGraphicsStateGuardian::get_texture_filter_type // Access: Protected, Static @@ -3992,6 +4434,32 @@ get_texture_filter_type(Texture::FilterType ft, bool ignore_mipmaps) { return GL_NEAREST; } +//////////////////////////////////////////////////////////////////// +// Function: GLGraphicsStateGuardian::get_panda_filter_type +// Access: Protected, Static +// Description: Maps from the GL's internal filter type symbols +// to Panda's. +//////////////////////////////////////////////////////////////////// +Texture::FilterType CLP(GraphicsStateGuardian):: +get_panda_filter_type(GLenum ft) { + switch (ft) { + case GL_NEAREST: + return Texture::FT_nearest; + case GL_LINEAR: + return Texture::FT_linear; + case GL_NEAREST_MIPMAP_NEAREST: + return Texture::FT_nearest_mipmap_nearest; + case GL_LINEAR_MIPMAP_NEAREST: + return Texture::FT_linear_mipmap_nearest; + case GL_NEAREST_MIPMAP_LINEAR: + return Texture::FT_nearest_mipmap_linear; + case GL_LINEAR_MIPMAP_LINEAR: + return Texture::FT_linear_mipmap_linear; + } + GLCAT.error() << "Unexpected GL filter type " << (int)ft << "\n"; + return Texture::FT_linear; +} + //////////////////////////////////////////////////////////////////// // Function: GLGraphicsStateGuardian::get_component_type // Access: Protected, Static @@ -4021,8 +4489,81 @@ get_component_type(Texture::ComponentType component_type) { // to GL's. //////////////////////////////////////////////////////////////////// GLint CLP(GraphicsStateGuardian):: -get_external_image_format(Texture::Format format) const { - switch (format) { +get_external_image_format(Texture *tex) const { + Texture::CompressionMode compression = tex->get_ram_image_compression(); + if (compression != Texture::CM_off && + get_supports_compressed_texture_format(compression)) { + switch (compression) { + case Texture::CM_on: + switch (tex->get_format()) { + case Texture::F_color_index: + case Texture::F_stencil_index: + case Texture::F_depth_component: + // This shouldn't be possible. + nassertr(false, GL_RGB); + break; + + case Texture::F_rgba: + case Texture::F_rgbm: + case Texture::F_rgba4: + case Texture::F_rgba8: + case Texture::F_rgba12: + return GL_COMPRESSED_RGBA; + + case Texture::F_rgb: + case Texture::F_rgb5: + case Texture::F_rgba5: + case Texture::F_rgb8: + case Texture::F_rgb12: + case Texture::F_rgb332: + return GL_COMPRESSED_RGB; + + case Texture::F_alpha: + return GL_COMPRESSED_ALPHA; + + case Texture::F_red: + case Texture::F_green: + case Texture::F_blue: + case Texture::F_luminance: + return GL_COMPRESSED_LUMINANCE; + + case Texture::F_luminance_alpha: + case Texture::F_luminance_alphamask: + return GL_COMPRESSED_LUMINANCE_ALPHA; + } + break; + + case Texture::CM_dxt1: + if (Texture::has_alpha(tex->get_format())) { + return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + } else { + return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + } + + case Texture::CM_dxt3: + return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + + case Texture::CM_dxt5: + return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + + case Texture::CM_fxt1: + if (Texture::has_alpha(tex->get_format())) { + return GL_COMPRESSED_RGBA_FXT1_3DFX; + } else { + return GL_COMPRESSED_RGB_FXT1_3DFX; + } + + case Texture::CM_default: + case Texture::CM_off: + case Texture::CM_dxt2: + case Texture::CM_dxt4: + // This shouldn't happen. + nassertr(false, GL_RGB); + break; + } + } + + switch (tex->get_format()) { case Texture::F_color_index: return GL_COLOR_INDEX; case Texture::F_stencil_index: @@ -4059,19 +4600,129 @@ get_external_image_format(Texture::Format format) const { } GLCAT.error() << "Invalid Texture::Format value in get_external_image_format(): " - << (int)format << "\n"; + << (int)tex->get_format() << "\n"; return GL_RGB; } //////////////////////////////////////////////////////////////////// // Function: GLGraphicsStateGuardian::get_internal_image_format -// Access: Protected, Static +// Access: Protected // Description: Maps from the Texture's Format symbols to a // suitable internal format for GL textures. //////////////////////////////////////////////////////////////////// GLint CLP(GraphicsStateGuardian):: -get_internal_image_format(Texture::Format format) { - switch (format) { +get_internal_image_format(Texture *tex) const { + Texture::CompressionMode compression = tex->get_compression(); + if (compression == Texture::CM_default) { + compression = (compressed_textures) ? Texture::CM_on : Texture::CM_off; + } + bool is_3d = (tex->get_texture_type() == Texture::TT_3d_texture); + + if (get_supports_compressed_texture_format(compression)) { + switch (compression) { + case Texture::CM_on: + // The user asked for just generic compression. OpenGL supports + // requesting just generic compression, but we'd like to go ahead + // and request a specific type (if we can figure out an + // appropriate choice), since that makes saving the result as a + // pre-compressed texture more dependable--this way, we will know + // which compression algorithm was applied. + + switch (tex->get_format()) { + case Texture::F_color_index: + case Texture::F_stencil_index: + case Texture::F_depth_component: + // Unsupported; fall through to below. + break; + + case Texture::F_rgbm: + if (get_supports_compressed_texture_format(Texture::CM_dxt1) && !is_3d) { + return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + } else if (get_supports_compressed_texture_format(Texture::CM_fxt1) && !is_3d) { + return GL_COMPRESSED_RGBA_FXT1_3DFX; + } else { + return GL_COMPRESSED_RGBA; + } + + case Texture::F_rgba4: + if (get_supports_compressed_texture_format(Texture::CM_dxt3) && !is_3d) { + return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + } else if (get_supports_compressed_texture_format(Texture::CM_fxt1) && !is_3d) { + return GL_COMPRESSED_RGBA_FXT1_3DFX; + } else { + return GL_COMPRESSED_RGBA; + } + + case Texture::F_rgba: + case Texture::F_rgba8: + case Texture::F_rgba12: + if (get_supports_compressed_texture_format(Texture::CM_dxt5) && !is_3d) { + return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } else if (get_supports_compressed_texture_format(Texture::CM_fxt1) && !is_3d) { + return GL_COMPRESSED_RGBA_FXT1_3DFX; + } else { + return GL_COMPRESSED_RGBA; + } + + case Texture::F_rgb: + case Texture::F_rgb5: + case Texture::F_rgba5: + case Texture::F_rgb8: + case Texture::F_rgb12: + case Texture::F_rgb332: + if (get_supports_compressed_texture_format(Texture::CM_dxt1) && !is_3d) { + return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + } else if (get_supports_compressed_texture_format(Texture::CM_fxt1) && !is_3d) { + return GL_COMPRESSED_RGB_FXT1_3DFX; + } else { + return GL_COMPRESSED_RGB; + } + + case Texture::F_alpha: + return GL_COMPRESSED_ALPHA; + + case Texture::F_red: + case Texture::F_green: + case Texture::F_blue: + case Texture::F_luminance: + return GL_COMPRESSED_LUMINANCE; + + case Texture::F_luminance_alpha: + case Texture::F_luminance_alphamask: + return GL_COMPRESSED_LUMINANCE_ALPHA; + } + break; + + case Texture::CM_dxt1: + if (Texture::has_alpha(tex->get_format())) { + return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + } else { + return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + } + + case Texture::CM_dxt3: + return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + + case Texture::CM_dxt5: + return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + + case Texture::CM_fxt1: + if (Texture::has_alpha(tex->get_format())) { + return GL_COMPRESSED_RGBA_FXT1_3DFX; + } else { + return GL_COMPRESSED_RGB_FXT1_3DFX; + } + + case Texture::CM_default: + case Texture::CM_off: + case Texture::CM_dxt2: + case Texture::CM_dxt4: + // No compression: fall through to below. + break; + } + } + + switch (tex->get_format()) { case Texture::F_color_index: return GL_COLOR_INDEX; case Texture::F_stencil_index: @@ -4117,78 +4768,37 @@ get_internal_image_format(Texture::Format format) { default: GLCAT.error() << "Invalid image format in get_internal_image_format(): " - << (int)format << "\n"; + << (int)tex->get_format() << "\n"; return GL_RGB; } } //////////////////////////////////////////////////////////////////// -// Function: GLGraphicsStateGuardian::get_external_texture_bytes +// Function: GLGraphicsStateGuardian::is_compressed_format // Access: Protected, Static -// Description: Computes the number of bytes that should be in the -// "external", or local, texture buffer before -// transferring to OpenGL. This is just used for -// sending data to PStats. +// Description: Returns true if the indicated GL internal format +// represents a compressed texture format, false +// otherwise. //////////////////////////////////////////////////////////////////// -int CLP(GraphicsStateGuardian):: -get_external_texture_bytes(int width, int height, int depth, - GLint external_format, GLenum component_type) { - int num_components; - switch (external_format) { - case GL_COLOR_INDEX: - case GL_STENCIL_INDEX: - case GL_DEPTH_COMPONENT: - case GL_RED: - case GL_GREEN: - case GL_BLUE: - case GL_ALPHA: - case GL_LUMINANCE: - num_components = 1; - break; - - case GL_LUMINANCE_ALPHA: - num_components = 2; - break; - - case GL_BGR: - case GL_RGB: - num_components = 3; - break; - - case GL_BGRA: - case GL_RGBA: - num_components = 4; - break; +bool CLP(GraphicsStateGuardian):: +is_compressed_format(GLenum format) { + switch (format) { + case GL_COMPRESSED_RGB: + case GL_COMPRESSED_RGBA: + case GL_COMPRESSED_ALPHA: + case GL_COMPRESSED_LUMINANCE: + case GL_COMPRESSED_LUMINANCE_ALPHA: + case GL_COMPRESSED_RGB_FXT1_3DFX: + case GL_COMPRESSED_RGBA_FXT1_3DFX: + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + return true; default: - GLCAT.error() - << "Unexpected external_format in get_external_texture_bytes(): " - << hex << external_format << dec << "\n"; - num_components = 3; + return false; } - - int component_width; - switch (component_type) { - case GL_UNSIGNED_BYTE: - component_width = 1; - break; - - case GL_UNSIGNED_SHORT: - component_width = 2; - break; - - case GL_FLOAT: - component_width = 4; - break; - - default: - GLCAT.error() - << "Unexpected component_type in get_external_texture_bytes(): " - << hex << component_type << dec << "\n"; - component_width = 1; - } - - return width * height * depth * num_components * component_width; } //////////////////////////////////////////////////////////////////// @@ -5488,6 +6098,7 @@ specify_texture(Texture *tex) { Texture::FilterType minfilter = tex->get_minfilter(); Texture::FilterType magfilter = tex->get_magfilter(); bool uses_mipmaps = tex->uses_mipmaps() && !CLP(ignore_mipmaps); + Texture::CompressionMode image_compression = tex->get_ram_image_compression(); #ifndef NDEBUG if (CLP(force_mipmaps)) { @@ -5498,7 +6109,8 @@ specify_texture(Texture *tex) { #endif if (_supports_generate_mipmap && - (auto_generate_mipmaps || !tex->might_have_ram_image())) { + (auto_generate_mipmaps || !tex->might_have_ram_image() || + image_compression != Texture::CM_off)) { // If the hardware can automatically generate mipmaps, ask it to // do so now, but only if the texture requires them. GLP(TexParameteri)(target, GL_GENERATE_MIPMAP, uses_mipmaps); @@ -5645,13 +6257,14 @@ upload_texture(CLP(TextureContext) *gtc) { if (image.is_null()) { return false; } + Texture::CompressionMode image_compression = tex->get_ram_image_compression(); int width = tex->get_x_size(); int height = tex->get_y_size(); int depth = tex->get_z_size(); - GLint internal_format = get_internal_image_format(tex->get_format()); - GLint external_format = get_external_image_format(tex->get_format()); + GLint internal_format = get_internal_image_format(tex); + GLint external_format = get_external_image_format(tex); GLenum component_type = get_component_type(tex->get_component_type()); // Ensure that the texture fits within the GL's specified limits. @@ -5682,7 +6295,7 @@ upload_texture(CLP(TextureContext) *gtc) { // if the user had specified max-texture-dimension to reduce the // texture at load time instead. Of course, the user doesn't always // know ahead of time what the hardware limits are. - if (max_dimension > 0) { + if (max_dimension > 0 && image_compression == Texture::CM_off) { if (width > max_dimension) { int byte_chunk = texel_size; int stride = 1; @@ -5734,9 +6347,11 @@ upload_texture(CLP(TextureContext) *gtc) { } #ifndef NDEBUG - int wanted_size = - compute_gl_image_size(width, height, depth, external_format, component_type); - nassertr(wanted_size == (int)image.size(), false); + if (image_compression == Texture::CM_off) { + int wanted_size = + compute_gl_image_size(width, height, depth, external_format, component_type); + nassertr(wanted_size == (int)image.size(), false); + } #endif // NDEBUG GLP(PixelStorei)(GL_UNPACK_ALIGNMENT, 1); @@ -5759,43 +6374,43 @@ upload_texture(CLP(TextureContext) *gtc) { return false; } - size_t page_size = height * width * texel_size; + size_t page_size = tex->get_ram_page_size(); const unsigned char *image_base = image; success = success && upload_texture_image (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_POSITIVE_X, internal_format, width, height, depth, external_format, component_type, - image_base); + image_base, page_size, image_compression); image_base += page_size; success = success && upload_texture_image (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, internal_format, width, height, depth, external_format, component_type, - image_base); + image_base, page_size, image_compression); image_base += page_size; success = success && upload_texture_image (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, internal_format, width, height, depth, external_format, component_type, - image_base); + image_base, page_size, image_compression); image_base += page_size; success = success && upload_texture_image (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, internal_format, width, height, depth, external_format, component_type, - image_base); + image_base, page_size, image_compression); image_base += page_size; success = success && upload_texture_image (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, internal_format, width, height, depth, external_format, component_type, - image_base); + image_base, page_size, image_compression); image_base += page_size; success = success && upload_texture_image (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, internal_format, width, height, depth, external_format, component_type, - image_base); + image_base, page_size, image_compression); image_base += page_size; nassertr((size_t)(image_base - image) == image.size(), false); @@ -5805,7 +6420,7 @@ upload_texture(CLP(TextureContext) *gtc) { success = upload_texture_image (gtc, uses_mipmaps, get_texture_target(tex->get_texture_type()), internal_format, width, height, depth, external_format, component_type, - image); + image, image.size(), image_compression); } if (success) { @@ -5841,14 +6456,26 @@ upload_texture_image(CLP(TextureContext) *gtc, GLenum target, GLint internal_format, int width, int height, int depth, GLint external_format, GLenum component_type, - const unsigned char *image) { + const unsigned char *image, + size_t image_size, + Texture::CompressionMode image_compression) { + // Make sure the error stack is cleared out before we begin. + report_my_gl_errors(); + if (target == GL_NONE) { // Unsupported target (e.g. 3-d texturing on GL 1.1). return false; } + if (image_compression != Texture::CM_off && !_supports_compressed_texture) { + return false; + } + PStatTimer timer(_load_texture_pcollector); if (uses_mipmaps) { +#ifdef DO_PSTATS + _data_transferred_pcollector.add_level(image_size * 4 / 3); +#endif #ifndef NDEBUG if (CLP(show_mipmaps) && target == GL_TEXTURE_2D) { build_phony_mipmaps(gtc->_texture); @@ -5857,13 +6484,15 @@ upload_texture_image(CLP(TextureContext) *gtc, } else #endif - if (!_supports_generate_mipmap || !auto_generate_mipmaps) { - // We only need to build the mipmaps by hand if the GL - // doesn't support generating them automatically. + if (!_supports_generate_mipmap || + (!auto_generate_mipmaps && image_compression == Texture::CM_off)) { + // We only need to build the mipmaps by hand if (a) the GL + // doesn't support generating them automatically, or (b) the + // user has specifically requested we don't use GL's + // auto-mipmap feature (but if the source texture image is + // compressed, we can't build mipmaps, so we ignore + // auto_generate_mipmaps in that case). bool success = true; -#ifdef DO_PSTATS - _data_transferred_pcollector.add_level(get_external_texture_bytes(width, height, depth, external_format, component_type) * 4 / 3); -#endif switch (target) { case GL_TEXTURE_1D: GLUP(Build1DMipmaps)(target, internal_format, width, @@ -5896,6 +6525,16 @@ upload_texture_image(CLP(TextureContext) *gtc, } } + if (GLCAT.is_debug()) { + if (image_compression != Texture::CM_off) { + GLCAT.debug() + << "loading pre-compressed texture " << gtc->_texture->get_name() << "\n"; + } else if (is_compressed_format(internal_format)) { + GLCAT.debug() + << "compressing texture " << gtc->_texture->get_name() << "\n"; + } + } + if (!gtc->_already_applied || gtc->_internal_format != internal_format || gtc->_width != width || @@ -5903,10 +6542,11 @@ upload_texture_image(CLP(TextureContext) *gtc, gtc->_depth != depth) { // We need to reload a new image. #ifdef DO_PSTATS - _data_transferred_pcollector.add_level(get_external_texture_bytes(width, height, depth, external_format, component_type)); + _data_transferred_pcollector.add_level(image_size); #endif switch (target) { case GL_TEXTURE_1D: + nassertr(image_compression == Texture::CM_off, false); GLP(TexImage1D)(target, 0, internal_format, width, 0, external_format, component_type, image); @@ -5914,6 +6554,7 @@ upload_texture_image(CLP(TextureContext) *gtc, case GL_TEXTURE_3D: if (_supports_3d_texture) { + nassertr(image_compression == Texture::CM_off, false); _glTexImage3D(target, 0, internal_format, width, height, depth, 0, external_format, component_type, image); @@ -5924,25 +6565,31 @@ upload_texture_image(CLP(TextureContext) *gtc, break; default: - GLP(TexImage2D)(target, 0, internal_format, - width, height, 0, - external_format, component_type, image); + if (image_compression == Texture::CM_off) { + GLP(TexImage2D)(target, 0, internal_format, + width, height, 0, + external_format, component_type, image); + } else { + _glCompressedTexImage2D(target, 0, external_format, width, height, + 0, image_size, image); + } } - } else { // We can reload the image over the previous image, possibly // saving on texture memory fragmentation. #ifdef DO_PSTATS - _data_transferred_pcollector.add_level(get_external_texture_bytes(width, height, depth, external_format, component_type)); + _data_transferred_pcollector.add_level(image_size); #endif switch (target) { case GL_TEXTURE_1D: + nassertr(image_compression == Texture::CM_off, false); GLP(TexSubImage1D)(target, 0, 0, width, external_format, component_type, image); break; case GL_TEXTURE_3D: if (_supports_3d_texture) { + nassertr(image_compression == Texture::CM_off, false); _glTexSubImage3D(target, 0, 0, 0, 0, width, height, depth, external_format, component_type, image); } else { @@ -5952,8 +6599,13 @@ upload_texture_image(CLP(TextureContext) *gtc, break; default: - GLP(TexSubImage2D)(target, 0, 0, 0, width, height, - external_format, component_type, image); + if (image_compression == Texture::CM_off) { + GLP(TexSubImage2D)(target, 0, 0, 0, width, height, + external_format, component_type, image); + } else { + _glCompressedTexSubImage2D(target, 0, 0, 0, width, height, + external_format, image_size, image); + } break; } } @@ -5971,6 +6623,8 @@ upload_texture_image(CLP(TextureContext) *gtc, } GLCAT.error(false) << "\n"; + + return false; } return true; @@ -6121,16 +6775,10 @@ build_phony_mipmap_level(int level, int x_size, int y_size) { GLCAT.warning() << "Unable to load phony mipmap image.\n"; } else { - GLenum internal_format = get_internal_image_format(tex->get_format()); - GLenum external_format = get_external_image_format(tex->get_format()); + GLenum internal_format = get_internal_image_format(tex); + GLenum external_format = get_external_image_format(tex); GLenum component_type = get_component_type(tex->get_component_type()); -#ifdef DO_PSTATS - int num_bytes = - get_external_texture_bytes(tex->get_x_size(), tex->get_y_size(), 1, - external_format, component_type); - _data_transferred_pcollector.add_level(num_bytes); -#endif GLP(TexImage2D)(GL_TEXTURE_2D, level, internal_format, tex->get_x_size(), tex->get_y_size(), 0, external_format, component_type, tex->get_ram_image()); @@ -6161,7 +6809,7 @@ save_mipmap_images(Texture *tex) { name = filename.get_basename_wo_extension(); } - GLenum external_format = get_external_image_format(tex->get_format()); + GLenum external_format = get_external_image_format(tex); GLenum type = get_component_type(tex->get_component_type()); int x_size = tex->get_x_size(); diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.h b/panda/src/glstuff/glGraphicsStateGuardian_src.h index 6614971aa8..c1e7cd14bb 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.h +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.h @@ -65,6 +65,9 @@ typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, typedef void (APIENTRYP PFNGLDRAWBUFFERSARBPROC) (GLsizei n, const GLenum *bufs); typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage); typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, GLvoid *img); class CLP(GeomContext); @@ -106,6 +109,7 @@ public: virtual TextureContext *prepare_texture(Texture *tex); virtual void release_texture(TextureContext *tc); + virtual bool extract_texture_data(Texture *tex); virtual GeomContext *prepare_geom(Geom *geom); virtual void release_geom(GeomContext *gc); @@ -236,14 +240,14 @@ protected: static GLenum get_numeric_type(Geom::NumericType numeric_type); GLenum get_texture_target(Texture::TextureType texture_type) const; - GLenum get_texture_wrap_mode(Texture::WrapMode wm); + GLenum get_texture_wrap_mode(Texture::WrapMode wm) const; + static Texture::WrapMode get_panda_wrap_mode(GLenum wm); static GLenum get_texture_filter_type(Texture::FilterType ft, bool ignore_mipmaps); + static Texture::FilterType get_panda_filter_type(GLenum ft); static GLenum get_component_type(Texture::ComponentType component_type); - GLint get_external_image_format(Texture::Format format) const; - static GLint get_internal_image_format(Texture::Format format); - static int get_external_texture_bytes(int width, int height, int depth, - GLint external_format, - GLenum component_type); + GLint get_external_image_format(Texture *tex) const; + GLint get_internal_image_format(Texture *tex) const; + static bool is_compressed_format(GLenum format); static GLint get_texture_apply_mode_type(TextureStage::Mode am); static GLint get_texture_combine_type(TextureStage::CombineMode cm); GLint get_texture_src_type(TextureStage::CombineSource cs, @@ -268,7 +272,9 @@ protected: GLenum target, GLint internal_format, int width, int height, int depth, GLint external_format, GLenum component_type, - const unsigned char *image); + const unsigned char *image, + size_t image_size, + Texture::CompressionMode image_compression); void do_point_size(); @@ -361,6 +367,10 @@ public: PFNGLTEXIMAGE3DPROC _glTexImage3D; PFNGLTEXSUBIMAGE3DPROC _glTexSubImage3D; + PFNGLCOMPRESSEDTEXIMAGE2DPROC _glCompressedTexImage2D; + PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC _glCompressedTexSubImage2D; + PFNGLGETCOMPRESSEDTEXIMAGEPROC _glGetCompressedTexImage; + bool _supports_bgr; bool _supports_rescale_normal; diff --git a/panda/src/gobj/Sources.pp b/panda/src/gobj/Sources.pp index 54fb0e9af6..cca6352591 100644 --- a/panda/src/gobj/Sources.pp +++ b/panda/src/gobj/Sources.pp @@ -1,6 +1,7 @@ #define OTHER_LIBS interrogatedb:c dconfig:c dtoolconfig:m \ dtoolutil:c dtoolbase:c dtool:m prc:c #define OSX_SYS_LIBS mx +#define USE_PACKAGES zlib #begin lib_target #define TARGET gobj diff --git a/panda/src/gobj/config_gobj.cxx b/panda/src/gobj/config_gobj.cxx index 67387bc0b3..190dae0911 100644 --- a/panda/src/gobj/config_gobj.cxx +++ b/panda/src/gobj/config_gobj.cxx @@ -79,6 +79,13 @@ ConfigVariableBool keep_texture_ram "texture image from disk; but it will consume memory somewhat " "wastefully.")); +ConfigVariableBool compressed_textures +("compressed-textures", false, + PRC_DESC("Set this to true to compress textures as they are loaded into " + "texture memory, if the driver supports this. Specifically, this " + "changes the meaning of set_compression(Texture::CM_default) to " + "Texture::CM_on.")); + ConfigVariableBool vertex_buffers ("vertex-buffers", true, PRC_DESC("Set this true to allow the use of vertex buffers (or buffer " @@ -167,11 +174,6 @@ ConfigVariableBool connect_triangle_strips "triangle strip may help performance by reducing the number " "of separate graphics calls that have to be made.")); -ConfigVariableEnum bam_texture_mode -("bam-texture-mode", BTM_relative, - PRC_DESC("Set this to specify how textures should be written into Bam files." - "See the panda source or documentation for available options.")); - ConfigVariableEnum textures_power_2 ("textures-power-2", ATS_down, PRC_DESC("Specify whether textures should automatically be constrained to " @@ -300,52 +302,6 @@ ConfigureFn(config_gobj) { InternalName::register_with_read_factory(); } -ostream & -operator << (ostream &out, BamTextureMode btm) { - switch (btm) { - case BTM_unchanged: - return out << "unchanged"; - - case BTM_fullpath: - return out << "fullpath"; - - case BTM_relative: - return out << "relative"; - - case BTM_basename: - return out << "basename"; - - case BTM_rawdata: - return out << "rawdata"; - } - - return out << "**invalid BamTextureMode (" << (int)btm << ")**"; -} - -istream & -operator >> (istream &in, BamTextureMode &btm) { - string word; - in >> word; - - if (cmp_nocase(word, "unchanged") == 0) { - btm = BTM_unchanged; - } else if (cmp_nocase(word, "fullpath") == 0) { - btm = BTM_fullpath; - } else if (cmp_nocase(word, "relative") == 0) { - btm = BTM_relative; - } else if (cmp_nocase(word, "basename") == 0) { - btm = BTM_basename; - } else if (cmp_nocase(word, "rawdata") == 0) { - btm = BTM_rawdata; - - } else { - gobj_cat->error() << "Invalid BamTextureMode value: " << word << "\n"; - btm = BTM_relative; - } - - return in; -} - ostream & operator << (ostream &out, AutoTextureScale ats) { switch (ats) { diff --git a/panda/src/gobj/config_gobj.h b/panda/src/gobj/config_gobj.h index 0f4f63d30d..a14ec15084 100644 --- a/panda/src/gobj/config_gobj.h +++ b/panda/src/gobj/config_gobj.h @@ -28,16 +28,6 @@ NotifyCategoryDecl(gobj, EXPCL_PANDA, EXPTP_PANDA); -enum BamTextureMode { - BTM_unchanged, - BTM_fullpath, - BTM_relative, - BTM_basename, - BTM_rawdata -}; -EXPCL_PANDA ostream &operator << (ostream &out, BamTextureMode btm); -EXPCL_PANDA istream &operator >> (istream &in, BamTextureMode &btm); - enum AutoTextureScale { ATS_none, ATS_down, @@ -49,6 +39,7 @@ EXPCL_PANDA istream &operator >> (istream &in, AutoTextureScale &ats); // Configure variables for gobj package. extern EXPCL_PANDA ConfigVariableInt max_texture_dimension; extern EXPCL_PANDA ConfigVariableBool keep_texture_ram; +extern EXPCL_PANDA ConfigVariableBool compressed_textures; extern EXPCL_PANDA ConfigVariableBool vertex_buffers; extern EXPCL_PANDA ConfigVariableBool vertex_arrays; extern EXPCL_PANDA ConfigVariableBool display_lists; @@ -58,7 +49,6 @@ extern EXPCL_PANDA ConfigVariableBool matrix_palette; extern EXPCL_PANDA ConfigVariableBool display_list_animation; extern EXPCL_PANDA ConfigVariableBool connect_triangle_strips; -extern EXPCL_PANDA ConfigVariableEnum bam_texture_mode; extern EXPCL_PANDA ConfigVariableEnum textures_power_2; extern EXPCL_PANDA ConfigVariableEnum textures_square; extern EXPCL_PANDA ConfigVariableBool textures_header_only; diff --git a/panda/src/gobj/texture.I b/panda/src/gobj/texture.I index 564a02e270..1b05eba9a6 100644 --- a/panda/src/gobj/texture.I +++ b/panda/src/gobj/texture.I @@ -398,13 +398,29 @@ get_border_color() const { return _border_color; } +//////////////////////////////////////////////////////////////////// +// Function: Texture::get_compression +// Access: Published +// Description: Returns the compression mode requested for this +// particular texture, or CM_off if the texture is not +// to be compressed. +// +// If a value other than CM_off is returned, this is +// not a guarantee that the texture is actually +// successfully compressed on the GSG. It may be that +// the GSG does not support the requested compression +// mode, in which case the texture may actually be +// stored uncompressed in texture memory. +//////////////////////////////////////////////////////////////////// +INLINE Texture::CompressionMode Texture:: +get_compression() const { + return _compression; +} + //////////////////////////////////////////////////////////////////// // Function: Texture::get_render_to_texture // Access: Published -// Description: Returns the solid color of the texture's border. -// Some OpenGL implementations use a border for tiling -// textures; in Panda, it is only used for specifying -// the clamp color. +// Description: //////////////////////////////////////////////////////////////////// INLINE bool Texture:: get_render_to_texture() const { @@ -446,7 +462,24 @@ might_have_ram_image() const { //////////////////////////////////////////////////////////////////// INLINE size_t Texture:: get_ram_image_size() const { - return _image.size(); + return _ram_image.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: Texture::get_ram_page_size +// Access: Published +// Description: Returns the number of bytes used by the in-memory +// image per page, or 0 if there is no in-memory image. +// +// For a non-compressed texture, this is the same as +// get_expected_ram_page_size(). For a compressed +// texture, this may be a smaller value. (We do assume +// that all pages will be the same size on a compressed +// texture). +//////////////////////////////////////////////////////////////////// +INLINE size_t Texture:: +get_ram_page_size() const { + return _ram_image_compression != CM_off ? _ram_page_size : get_expected_ram_page_size(); } //////////////////////////////////////////////////////////////////// @@ -474,6 +507,21 @@ get_expected_ram_page_size() const { return (size_t)(_x_size * _y_size * _num_components * _component_width); } +//////////////////////////////////////////////////////////////////// +// Function: Texture::get_ram_image_compression +// Access: Published +// Description: Returns the compression mode in which the ram image +// is already stored pre-compressed. If this is other +// than CM_off, you cannot rely on the contents of the +// ram image to be anything predicatable (it will not be +// an array of x by y pixels, and it probably won't have +// the same length as get_expected_ram_image_size()). +//////////////////////////////////////////////////////////////////// +INLINE Texture::CompressionMode Texture:: +get_ram_image_compression() const { + return _ram_image_compression; +} + //////////////////////////////////////////////////////////////////// // Function: Texture::set_keep_ram_image // Access: Published @@ -678,6 +726,30 @@ get_loaded_from_disk() const { return _loaded_from_disk; } +//////////////////////////////////////////////////////////////////// +// Function: Texture::set_loaded_from_txo +// Access: Published +// Description: Sets the flag that indicates the texture has been +// loaded from a txo file. You probably shouldn't be +// setting this directly; it is set automatically when a +// Texture is loaded. +//////////////////////////////////////////////////////////////////// +INLINE void Texture:: +set_loaded_from_txo() { + _loaded_from_txo = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: Texture::get_loaded_from_txo +// Access: Published +// Description: Returns the flag that indicates the texture has been +// loaded from a txo file. +//////////////////////////////////////////////////////////////////// +INLINE bool Texture:: +get_loaded_from_txo() const { + return _loaded_from_txo; +} + //////////////////////////////////////////////////////////////////// // Function: Texture::get_match_framebuffer_format // Access: Public @@ -725,7 +797,7 @@ set_match_framebuffer_format(bool flag) { //////////////////////////////////////////////////////////////////// INLINE void Texture:: store_unscaled_byte(int &index, int value) { - _image[index++] = (uchar)value; + _ram_image[index++] = (uchar)value; } //////////////////////////////////////////////////////////////////// @@ -744,8 +816,8 @@ store_unscaled_short(int &index, int value) { uchar uc[2]; } v; v.us = (ushort)value; - _image[index++] = v.uc[0]; - _image[index++] = v.uc[1]; + _ram_image[index++] = v.uc[0]; + _ram_image[index++] = v.uc[1]; } //////////////////////////////////////////////////////////////////// @@ -786,8 +858,8 @@ store_scaled_short(int &index, int value, double scale) { //////////////////////////////////////////////////////////////////// INLINE double Texture:: get_unsigned_byte(int &index) const { - nassertr(index >= 0 && index < (int)_image.size(), 0.0); - return (double)_image[index++] / 255.0; + nassertr(index >= 0 && index < (int)_ram_image.size(), 0.0); + return (double)_ram_image[index++] / 255.0; } //////////////////////////////////////////////////////////////////// @@ -800,12 +872,29 @@ get_unsigned_byte(int &index) const { //////////////////////////////////////////////////////////////////// INLINE double Texture:: get_unsigned_short(int &index) const { - nassertr(index >= 0 && index+1 < (int)_image.size(), 0.0); + nassertr(index >= 0 && index+1 < (int)_ram_image.size(), 0.0); union { ushort us; uchar uc[2]; } v; - v.uc[0] = _image[index++]; - v.uc[1] = _image[index++]; + v.uc[0] = _ram_image[index++]; + v.uc[1] = _ram_image[index++]; return (double)v.us / 65535.0; } + +//////////////////////////////////////////////////////////////////// +// Function: Texture::is_txo_filename +// Access: Private, Static +// Description: Returns true if the indicated filename ends in .txo +// or .txo.pz, false otherwise. +//////////////////////////////////////////////////////////////////// +INLINE bool Texture:: +is_txo_filename(const Filename &fullpath) { + string extension = fullpath.get_extension(); +#ifdef HAVE_ZLIB + if (extension == "pz") { + extension = Filename(fullpath.get_basename_wo_extension()).get_extension(); + } +#endif // HAVE_ZLIB + return (extension == "txo"); +} diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx index 339e41c4d6..1b8c2c4dbc 100644 --- a/panda/src/gobj/texture.cxx +++ b/panda/src/gobj/texture.cxx @@ -29,6 +29,11 @@ #include "preparedGraphicsObjects.h" #include "pnmImage.h" #include "virtualFileSystem.h" +#include "datagramInputFile.h" +#include "datagramOutputFile.h" +#include "bam.h" +#include "zStream.h" +#include "indent.h" #include @@ -58,6 +63,9 @@ Texture(const string &name) : _keep_ram_image = true; _all_dirty_flags = 0; _border_color.set(0.0f, 0.0f, 0.0f, 1.0f); + _compression = CM_default; + _ram_image_compression = CM_off; + _ram_page_size = 0; _render_to_texture = false; _match_framebuffer_format = false; @@ -69,6 +77,7 @@ Texture(const string &name) : set_component_type(T_unsigned_byte); _loaded_from_disk = false; + _loaded_from_txo = false; } //////////////////////////////////////////////////////////////////// @@ -95,6 +104,7 @@ Texture(const Texture ©) : _format(copy._format), _component_type(copy._component_type), _loaded_from_disk(copy._loaded_from_disk), + _loaded_from_txo(copy._loaded_from_txo), _wrap_u(copy._wrap_u), _wrap_v(copy._wrap_v), _wrap_w(copy._wrap_w), @@ -103,12 +113,56 @@ Texture(const Texture ©) : _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), _all_dirty_flags(0), - _image(copy._image) + _ram_image(copy._ram_image), + _ram_image_compression(copy._ram_image_compression), + _ram_page_size(copy._ram_page_size) { } +//////////////////////////////////////////////////////////////////// +// 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 ©) { + Namable::operator = (copy); + _filename = copy._filename; + _alpha_filename = copy._alpha_filename; + _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_components = copy._num_components; + _component_width = copy._component_width; + _texture_type = copy._texture_type; + _format = copy._format; + _component_type = copy._component_type; + _loaded_from_disk = copy._loaded_from_disk; + _loaded_from_txo = copy._loaded_from_txo; + _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; + _all_dirty_flags = 0; + _ram_image = copy._ram_image; + _ram_image_compression = copy._ram_image_compression; + _ram_page_size = copy._ram_page_size; +} + //////////////////////////////////////////////////////////////////// // Function: Texture::Destructor // Access: Published, Virtual @@ -170,6 +224,7 @@ setup_texture(Texture::TextureType texture_type, int x_size, int y_size, clear_ram_image(); _loaded_from_disk = false; + _loaded_from_txo = false; } //////////////////////////////////////////////////////////////////// @@ -187,7 +242,7 @@ setup_texture(Texture::TextureType texture_type, int x_size, int y_size, void Texture:: generate_normalization_cube_map(int size) { setup_cube_map(size, T_unsigned_byte, F_rgb); - PTA_uchar image = modify_ram_image(); + PTA_uchar image = make_ram_image(); float half_size = (float)size * 0.5f; float center = half_size - 0.5f; @@ -284,9 +339,18 @@ generate_normalization_cube_map(int size) { // than this number. // // This also implicitly sets keep_ram_image to false. +// +// If the filename has the extension .txo, this +// implicitly reads a texture object instead of a +// filename (which replaces all of the texture +// properties). //////////////////////////////////////////////////////////////////// bool Texture:: read(const Filename &fullpath, int z, int primary_file_num_channels) { + if (is_txo_filename(fullpath)) { + return read_txo_file(fullpath); + } + PNMImage image; if (textures_header_only) { @@ -461,11 +525,24 @@ read(const Filename &fullpath, const Filename &alpha_fullpath, //////////////////////////////////////////////////////////////////// // Function: Texture::write // Access: Published -// Description: Writes the texture to the indicated filename. +// Description: Writes the texture to the indicated filename. If the +// filename ends in the extension .txo, this implicitly +// writes a Panda texture object (.txo) instead of an +// image file. //////////////////////////////////////////////////////////////////// bool Texture:: write(const Filename &name, int z) const { + if (!has_ram_image()) { + ((Texture *)this)->get_ram_image(); + } nassertr(has_ram_image(), false); + + if (is_txo_filename(name)) { + return write_txo_file(name); + } + + nassertr(get_ram_image_compression() == CM_off, false); + PNMImage pnmimage; if (!store(pnmimage, z)) { return false; @@ -479,6 +556,114 @@ write(const Filename &name, int z) const { return true; } +//////////////////////////////////////////////////////////////////// +// 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. +// +// The filename is just for reference. +//////////////////////////////////////////////////////////////////// +bool Texture:: +read_txo(istream &in, const string &filename) { + DatagramInputFile din; + + if (!din.open(in)) { + gobj_cat.error() + << "Could not read texture object: " << filename << "\n"; + return false; + } + + string head; + if (!din.read_header(head, _bam_header.size())) { + gobj_cat.error() + << filename << " is not a texture object file.\n"; + return false; + } + + if (head != _bam_header) { + gobj_cat.error() + << filename << " is not a texture object file.\n"; + return false; + } + + BamReader reader(&din, filename); + if (!reader.init()) { + return false; + } + + TypedWritable *object = reader.read_object(); + if (object == (TypedWritable *)NULL) { + gobj_cat.error() + << "Texture object " << filename << " is empty.\n"; + return false; + + } 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 false; + } + + PT(Texture) other = DCAST(Texture, object); + if (!reader.resolve()) { + gobj_cat.error() + << "Unable to fully resolve texture object file.\n"; + return false; + } + + (*this) = (*other); + _loaded_from_disk = true; + _loaded_from_txo = true; + return true; +} + +//////////////////////////////////////////////////////////////////// +// 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 { + DatagramOutputFile dout; + + if (!dout.open(out)) { + 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, filename); + if (!writer.init()) { + return false; + } + + writer.set_file_texture_mode(BTM_rawdata); + + if (!writer.write_object(this)) { + return false; + } + + if (!has_ram_image()) { + gobj_cat.error() + << get_name() << " does not have ram image\n"; + return false; + } + + return true; +} + //////////////////////////////////////////////////////////////////// // Function: Texture::read_pages // Access: Published @@ -497,6 +682,10 @@ write(const Filename &name, int z) const { //////////////////////////////////////////////////////////////////// bool Texture:: read_pages(Filename fullpath_pattern, int z_size) { + if (is_txo_filename(fullpath_pattern)) { + return read_txo_file(fullpath_pattern); + } + fullpath_pattern.set_pattern(true); if (!fullpath_pattern.has_hash()) { gobj_cat.error() @@ -566,6 +755,10 @@ read_pages(Filename fullpath_pattern, int z_size) { //////////////////////////////////////////////////////////////////// bool Texture:: write_pages(Filename fullpath_pattern) { + if (is_txo_filename(fullpath_pattern)) { + return write_txo_file(fullpath_pattern); + } + fullpath_pattern.set_pattern(true); if (!fullpath_pattern.has_hash()) { gobj_cat.error() @@ -938,13 +1131,37 @@ set_border_color(const Colorf &color) { } } +//////////////////////////////////////////////////////////////////// +// Function: Texture::set_compression +// Access: Published +// Description: Requests that this particular Texture be compressed +// when it is loaded into texture memory. +// +// This refers to the internal compression of the +// texture image within texture memory; it is not +// related to jpeg or png compression, which are disk +// file compression formats. The actual disk file that +// generated this texture may be stored in a compressed +// or uncompressed format supported by Panda; it will be +// decompressed on load, and then recompressed by the +// graphics API if this parameter is not CM_off. +// +// If the GSG does not support this texture compression +// mode, the texture will silently be loaded +// uncompressed. +//////////////////////////////////////////////////////////////////// +void Texture:: +set_compression(Texture::CompressionMode compression) { + if (_compression != compression) { + mark_dirty(DF_image); + _compression = compression; + } +} + //////////////////////////////////////////////////////////////////// // Function: Texture::set_render_to_texture // Access: Published -// Description: Specifies the solid color of the texture's border. -// Some OpenGL implementations use a border for tiling -// textures; in Panda, it is only used for specifying -// the clamp color. +// Description: //////////////////////////////////////////////////////////////////// void Texture:: set_render_to_texture(bool render_to_texture) { @@ -981,7 +1198,7 @@ set_render_to_texture(bool render_to_texture) { //////////////////////////////////////////////////////////////////// bool Texture:: has_ram_image() const { - return !_image.empty(); + return !_ram_image.empty(); } //////////////////////////////////////////////////////////////////// @@ -1018,26 +1235,28 @@ get_ram_image() { reload_ram_image(); } - return _image; + return _ram_image; } //////////////////////////////////////////////////////////////////// // Function: Texture::modify_ram_image // Access: Published // Description: Returns a modifiable pointer to the system-RAM image. -// If the RAM image has been dumped, creates a new one. +// This assumes the RAM image should be uncompressed. +// If the RAM image has been dumped, or is stored +// compressed, creates a new one. // // This also implicitly sets keep_ram_image to true. //////////////////////////////////////////////////////////////////// PTA_uchar Texture:: modify_ram_image() { - if (_image.empty()) { + if (_ram_image.empty() || _ram_image_compression != CM_off) { make_ram_image(); } mark_dirty(DF_image); _keep_ram_image = true; - return _image; + return _ram_image; } //////////////////////////////////////////////////////////////////// @@ -1051,25 +1270,35 @@ modify_ram_image() { //////////////////////////////////////////////////////////////////// PTA_uchar Texture:: make_ram_image() { - _image = PTA_uchar::empty_array(get_expected_ram_image_size()); + _ram_image = PTA_uchar::empty_array(get_expected_ram_image_size()); + _ram_image_compression = CM_off; mark_dirty(DF_image); _keep_ram_image = true; - return _image; + return _ram_image; } //////////////////////////////////////////////////////////////////// // Function: Texture::set_ram_image // Access: Published // Description: Replaces the current system-RAM image with the new -// data. +// data. If compression is not CM_off, it indicates +// that the new data is already pre-compressed in the +// indicated format. // // This also implicitly sets keep_ram_image to true. //////////////////////////////////////////////////////////////////// void Texture:: -set_ram_image(PTA_uchar image) { - nassertv(image.size() == get_expected_ram_image_size()); - if (_image != image) { - _image = image; +set_ram_image(PTA_uchar image, Texture::CompressionMode compression, + size_t page_size) { + nassertv(compression != CM_default); + nassertv(compression != CM_off || image.size() == get_expected_ram_image_size()); + if (_ram_image != image || _ram_image_compression != compression) { + _ram_image = image; + _ram_image_compression = compression; + _ram_page_size = page_size; + if (page_size == 0) { + _ram_page_size = image.size(); + } mark_dirty(DF_image); } _keep_ram_image = true; @@ -1082,7 +1311,8 @@ set_ram_image(PTA_uchar image) { //////////////////////////////////////////////////////////////////// void Texture:: clear_ram_image() { - _image.clear(); + _ram_image.clear(); + _ram_image_compression = CM_off; } //////////////////////////////////////////////////////////////////// @@ -1172,6 +1402,169 @@ release_all() { 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 { + indent(out, indent_level) + << get_type() << " " << get_name(); + if (!get_filename().empty()) { + out << " (from " << get_filename() << ")"; + } + out << "\n"; + + indent(out, indent_level + 2); + + switch (get_texture_type()) { + case TT_1d_texture: + out << "1-d, " << get_x_size(); + break; + + case TT_2d_texture: + out << "2-d, " << get_x_size() << " x " << get_y_size(); + break; + + case TT_3d_texture: + out << "3-d, " << get_x_size() << " x " << get_y_size() + << " x " << get_z_size(); + break; + + case TT_cube_map: + out << "cube map, " << get_x_size() << " x " << get_y_size(); + break; + } + + out << " pixels, each " << get_num_components(); + + switch (get_component_type()) { + case T_unsigned_byte: + out << " bytes"; + break; + + case T_unsigned_short: + out << " shorts"; + break; + + case T_float: + out << " floats"; + break; + } + + out << ", "; + switch (get_format()) { + case F_color_index: + out << "color_index"; + break; + case F_stencil_index: + out << "stencil_index"; + break; + case F_depth_component: + out << "depth_component"; + break; + + case F_rgba: + out << "rgba"; + break; + case F_rgbm: + out << "rgbm"; + 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; + } + + if (get_compression() != CM_default) { + out << ", compression " << get_compression(); + } + out << "\n"; + + indent(out, indent_level + 2); + + switch (get_texture_type()) { + case TT_1d_texture: + out << get_wrap_u() << ", "; + break; + + case TT_2d_texture: + out << get_wrap_u() << " x " << get_wrap_v() << ", "; + break; + + case TT_3d_texture: + out << get_wrap_u() << " x " << get_wrap_v() + << " x " << get_wrap_w() << ", "; + break; + + case TT_cube_map: + break; + } + + out << "min " << get_minfilter() + << ", mag " << get_magfilter() + << ", aniso " << get_anisotropic_degree() + << ", border " << get_border_color() + << "\n"; + + indent(out, indent_level + 2); + if (has_ram_image()) { + out << get_ram_image_size() << " bytes in ram, compression " + << get_ram_image_compression() << "\n"; + + } else { + out << "no ram image\n"; + } +} + //////////////////////////////////////////////////////////////////// // Function: Texture::set_format // Access: Published @@ -1307,7 +1700,7 @@ prepare_now(PreparedGraphicsObjects *prepared_objects, gobj_cat.debug() << "Dumping RAM for texture " << get_name() << "\n"; } - _image.clear(); + _ram_image.clear(); } } return tc; @@ -1465,17 +1858,23 @@ reconsider_dirty() { //////////////////////////////////////////////////////////////////// void Texture:: reload_ram_image() { - if (_texture_type == TT_1d_texture || _texture_type == TT_2d_texture) { - gobj_cat.info() - << "Reloading texture " << get_name() << "\n"; + gobj_cat.info() + << "Reloading texture " << get_name() << "\n"; - make_ram_image(); + make_ram_image(); + + if (_texture_type == TT_1d_texture || _texture_type == TT_2d_texture) { + // 1-d or 2-d texture. Just one page. if (has_alpha_fullpath()) { read(get_fullpath(), get_alpha_fullpath(), 0, _primary_file_num_channels, _alpha_file_channel); } else { read(get_fullpath(), 0, _primary_file_num_channels); } + + } else { + // 3-d texture or cube map. Multiple pages. + read_pages(get_fullpath(), _z_size); } } @@ -1510,6 +1909,50 @@ down_to_power_2(int value) { return x; } +//////////////////////////////////////////////////////////////////// +// 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_luminance_alpha: + case F_luminance_alphamask: + return true; + + default: + return false; + } +} + //////////////////////////////////////////////////////////////////// // Function: Texture::reconsider_z_size // Access: Protected @@ -1530,9 +1973,9 @@ reconsider_z_size(int z) { // Increase the size of the data buffer to make room for the new // texture level. size_t new_size = get_expected_ram_image_size(); - if (!_image.is_null() && new_size > _image.size()) { - _image.insert(_image.end(), new_size - _image.size(), 0); - nassertr(_image.size() == new_size, false); + if (!_ram_image.is_null() && new_size > _ram_image.size()) { + _ram_image.insert(_ram_image.end(), new_size - _ram_image.size(), 0); + nassertr(_ram_image.size() == new_size, false); } } @@ -1710,6 +2153,66 @@ consider_downgrade(PNMImage &pnmimage, int num_channels) { } } +//////////////////////////////////////////////////////////////////// +// Function: Texture::read_txo_file +// Access: Private +// Description: Called internally when read() or read_pages() detects +// a txo file. +//////////////////////////////////////////////////////////////////// +bool Texture:: +read_txo_file(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 = read_txo(*in, fullpath); + vfs->close_read_file(in); + + set_fullpath(fullpath); + clear_alpha_fullpath(); + _keep_ram_image = false; + + return success; +} + +//////////////////////////////////////////////////////////////////// +// Function: Texture::write_txo_file +// Access: Private +// Description: Called internally when write() or write_pages() detects +// a txo file. +//////////////////////////////////////////////////////////////////// +bool Texture:: +write_txo_file(const Filename &fullpath) const { + Filename filename = Filename::binary_filename(fullpath); + ofstream out; + if (!filename.open_write(out)) { + gobj_cat.error() + << "Unable to open " << filename << "\n"; + return false; + } + +#ifdef HAVE_ZLIB + if (fullpath.get_extension() == "pz") { + OCompressStream compressor(&out, false); + return write_txo(compressor); + } +#endif // HAVE_ZLIB + return write_txo(out, fullpath); +} + //////////////////////////////////////////////////////////////////// // Function: Texture::register_with_read_factory // Access: Public, Static @@ -1752,7 +2255,12 @@ make_from_bam(const FactoryParams ¶ms) { if (has_rawdata) { // If the raw image data is included, then just create a Texture // and don't load from the file. - me = new Texture(""); + me = new Texture(name); + me->_filename = filename; + me->_alpha_filename = alpha_filename; + me->_primary_file_num_channels = primary_file_num_channels; + me->_alpha_file_channel = alpha_file_channel; + me->_texture_type = texture_type; } else { if (filename.empty()) { @@ -1828,6 +2336,10 @@ fillin(DatagramIterator &scan, BamReader *manager, bool has_rawdata) { _magfilter = (FilterType)scan.get_uint8(); _anisotropic_degree = scan.get_int16(); _border_color.read_datagram(scan); + _compression = CM_default; + if (manager->get_file_minor_ver() >= 1) { + _compression = (CompressionMode)scan.get_uint8(); + } Format format = (Format)scan.get_uint8(); int num_components = scan.get_uint8(); @@ -1849,15 +2361,21 @@ fillin(DatagramIterator &scan, BamReader *manager, bool has_rawdata) { _z_size = scan.get_uint32(); _component_type = (ComponentType)scan.get_uint8(); _component_width = scan.get_uint8(); + _ram_image_compression = CM_off; + _ram_page_size = 0; + if (manager->get_file_minor_ver() >= 1) { + _ram_image_compression = (CompressionMode)scan.get_uint8(); + _ram_page_size = scan.get_uint32(); + } _loaded_from_disk = false; PN_uint32 u_size = scan.get_uint32(); // fill the _image buffer with image data string temp_buff = scan.extract_bytes(u_size); - _image = PTA_uchar::empty_array((int) u_size); + _ram_image = PTA_uchar::empty_array((int) u_size); for (PN_uint32 u_idx=0; u_idx < u_size; ++u_idx) { - _image[(int)u_idx] = (uchar) temp_buff[u_idx]; + _ram_image[(int)u_idx] = (uchar) temp_buff[u_idx]; } } } @@ -1870,11 +2388,6 @@ fillin(DatagramIterator &scan, BamReader *manager, bool has_rawdata) { //////////////////////////////////////////////////////////////////// void Texture:: write_datagram(BamWriter *manager, Datagram &me) { - bool has_bam_dir = !manager->get_filename().empty(); - Filename bam_dir = manager->get_filename().get_dirname(); - Filename filename = get_filename(); - Filename alpha_filename = get_alpha_filename(); - // 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 @@ -1883,10 +2396,24 @@ write_datagram(BamWriter *manager, Datagram &me) { // Otherwise, we just write out the filename, and assume whoever // loads the bam file later will have access to the image file on // disk. + BamTextureMode file_texture_mode = manager->get_file_texture_mode(); bool has_rawdata = - (bam_texture_mode == BTM_rawdata || (has_ram_image() && filename.empty())); + (file_texture_mode == BTM_rawdata || (has_ram_image() && get_filename().empty())); + if (has_rawdata && !has_ram_image()) { + get_ram_image(); + if (!has_ram_image()) { + // No image data after all. + has_rawdata = false; + } + } - switch (bam_texture_mode) { + bool has_bam_dir = !manager->get_filename().empty(); + Filename bam_dir = manager->get_filename().get_dirname(); + Filename filename = get_filename(); + Filename alpha_filename = get_alpha_filename(); + + + switch (file_texture_mode) { case BTM_unchanged: case BTM_rawdata: break; @@ -1928,7 +2455,7 @@ write_datagram(BamWriter *manager, Datagram &me) { default: gobj_cat.error() - << "Unsupported bam-texture-mode: " << (int)bam_texture_mode << "\n"; + << "Unsupported bam-texture-mode: " << (int)file_texture_mode << "\n"; } me.add_string(get_name()); @@ -1947,6 +2474,7 @@ write_datagram(BamWriter *manager, Datagram &me) { me.add_uint8(_magfilter); me.add_int16(_anisotropic_degree); _border_color.write_datagram(me); + me.add_uint8(_compression); me.add_uint8(_format); me.add_uint8(_num_components); @@ -1959,9 +2487,11 @@ write_datagram(BamWriter *manager, Datagram &me) { me.add_uint32(_z_size); me.add_uint8(_component_type); me.add_uint8(_component_width); + me.add_uint8(_ram_image_compression); + me.add_uint32(_ram_page_size); - me.add_uint32(_image.size()); - me.append_data(_image, _image.size()); + me.add_uint32(_ram_image.size()); + me.append_data(_ram_image, _ram_image.size()); } } @@ -2046,3 +2576,33 @@ operator >> (istream &in, Texture::WrapMode &wm) { wm = Texture::string_wrap_mode(word); return in; } + +//////////////////////////////////////////////////////////////////// +// Function: Texture::CompressionMode output operator +// Description: +//////////////////////////////////////////////////////////////////// +ostream & +operator << (ostream &out, Texture::CompressionMode cm) { + switch (cm) { + case Texture::CM_default: + return out << "default"; + case Texture::CM_off: + return out << "off"; + case Texture::CM_on: + return out << "on"; + case Texture::CM_fxt1: + return out << "fxt1"; + case Texture::CM_dxt1: + return out << "dxt1"; + case Texture::CM_dxt2: + return out << "dxt2"; + case Texture::CM_dxt3: + return out << "dxt3"; + case Texture::CM_dxt4: + return out << "dxt4"; + case Texture::CM_dxt5: + return out << "dxt5"; + } + + return out << "(**invalid Texture::CompressionMode(" << (int)cm << ")**)"; +} diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h index c6f58da40b..e74a85dda1 100644 --- a/panda/src/gobj/texture.h +++ b/panda/src/gobj/texture.h @@ -144,10 +144,31 @@ PUBLISHED: WM_invalid }; + enum CompressionMode { + // Generic compresssion modes. Usually, you should choose one of + // these. + CM_default, // on or off, according to compressed-textures + CM_off, // uncompressed image + CM_on, // whatever compression the driver supports + + // Specific compression modes. Use only when you really want to + // use a particular compression algorithm. Use with caution; not + // all drivers support all compression modes. You can use + // GSG::get_supports_compressed_texture_format() to query the + // available compression modes for a particular GSG. + CM_fxt1, + CM_dxt1, + CM_dxt2, + CM_dxt3, + CM_dxt4, + CM_dxt5, + }; + PUBLISHED: Texture(const string &name = string()); protected: Texture(const Texture ©); + void operator = (const Texture ©); PUBLISHED: virtual ~Texture(); @@ -179,6 +200,9 @@ PUBLISHED: int primary_file_num_channels = 0, int alpha_file_channel = 0); bool write(const Filename &fullpath, int z = 0) const; + bool read_txo(istream &in, const string &filename = "stream"); + bool write_txo(ostream &out, const string &filename = "stream") const; + bool read_pages(Filename fullpath_pattern, int z_size = 0); bool write_pages(Filename fullpath_pattern); @@ -213,6 +237,7 @@ PUBLISHED: void set_magfilter(FilterType filter); void set_anisotropic_degree(int anisotropic_degree); void set_border_color(const Colorf &color); + void set_compression(CompressionMode compression); void set_render_to_texture(bool render_to_texture); INLINE WrapMode get_wrap_u() const; @@ -222,18 +247,22 @@ PUBLISHED: INLINE FilterType get_magfilter() const; INLINE int get_anisotropic_degree() const; INLINE Colorf get_border_color() const; + INLINE CompressionMode get_compression() const; INLINE bool get_render_to_texture() const; INLINE bool uses_mipmaps() const; virtual bool has_ram_image() const; INLINE bool might_have_ram_image() const; INLINE size_t get_ram_image_size() const; + INLINE size_t get_ram_page_size() const; INLINE size_t get_expected_ram_image_size() const; INLINE size_t get_expected_ram_page_size() const; CPTA_uchar get_ram_image(); + INLINE CompressionMode get_ram_image_compression() const; PTA_uchar modify_ram_image(); PTA_uchar make_ram_image(); - void set_ram_image(PTA_uchar image); + void set_ram_image(PTA_uchar image, CompressionMode compression = CM_off, + size_t page_size = 0); void clear_ram_image(); INLINE void set_keep_ram_image(bool keep_ram_image); virtual bool get_keep_ram_image() const; @@ -242,6 +271,8 @@ PUBLISHED: bool release(PreparedGraphicsObjects *prepared_objects); int release_all(); + void write(ostream &out, int indent_level) const; + PUBLISHED: // These are published, but in general, you shouldn't be mucking // with these values; they are set automatically when a texture is @@ -264,6 +295,9 @@ PUBLISHED: INLINE void set_loaded_from_disk(); INLINE bool get_loaded_from_disk() const; + INLINE void set_loaded_from_txo(); + INLINE bool get_loaded_from_txo() const; + public: INLINE bool get_match_framebuffer_format() const; INLINE void set_match_framebuffer_format(bool flag); @@ -298,6 +332,9 @@ public: static int up_to_power_2(int value); static int down_to_power_2(int value); + static bool is_specific(CompressionMode compression); + static bool has_alpha(Format format); + protected: virtual void reconsider_dirty(); virtual void reload_ram_image(); @@ -319,6 +356,10 @@ private: INLINE double get_unsigned_byte(int &index) const; INLINE double get_unsigned_short(int &index) const; + INLINE static bool is_txo_filename(const Filename &fullpath); + bool read_txo_file(const Filename &fullpath); + bool write_txo_file(const Filename &fullpath) const; + protected: Filename _filename; Filename _alpha_filename; @@ -344,6 +385,7 @@ protected: ComponentType _component_type; bool _loaded_from_disk; + bool _loaded_from_txo; WrapMode _wrap_u; WrapMode _wrap_v; @@ -353,6 +395,7 @@ protected: int _anisotropic_degree; bool _keep_ram_image; Colorf _border_color; + CompressionMode _compression; bool _render_to_texture; bool _match_framebuffer_format; @@ -377,7 +420,9 @@ protected: // texture. int _all_dirty_flags; - PTA_uchar _image; + PTA_uchar _ram_image; + CompressionMode _ram_image_compression; + size_t _ram_page_size; // Datagram stuff @@ -417,6 +462,8 @@ EXPCL_PANDA istream &operator >> (istream &in, Texture::FilterType &ft); EXPCL_PANDA ostream &operator << (ostream &out, Texture::WrapMode wm); EXPCL_PANDA istream &operator >> (istream &in, Texture::WrapMode &wm); +EXPCL_PANDA ostream &operator << (ostream &out, Texture::CompressionMode cm); + #include "texture.I" #endif diff --git a/panda/src/gobj/videoTexture.cxx b/panda/src/gobj/videoTexture.cxx index 6bb22bd4e4..ee7bd01252 100644 --- a/panda/src/gobj/videoTexture.cxx +++ b/panda/src/gobj/videoTexture.cxx @@ -33,6 +33,9 @@ VideoTexture:: VideoTexture(const string &name) : Texture(name) { + // We don't want to try to compress each frame as it's loaded. + _compression = CM_off; + _video_width = 0; _video_height = 0; @@ -69,7 +72,7 @@ has_ram_image() const { if (this_frame != _last_frame_update) { return false; } - return !_image.empty(); + return !_ram_image.empty(); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/grutil/openCVTexture.cxx b/panda/src/grutil/openCVTexture.cxx index 8186022c96..7f1910861d 100644 --- a/panda/src/grutil/openCVTexture.cxx +++ b/panda/src/grutil/openCVTexture.cxx @@ -346,7 +346,7 @@ update_frame(int frame) { const unsigned char *source = page._color.get_frame_data(frame); if (source != NULL) { nassertv(get_video_width() <= _x_size && get_video_height() <= _y_size); - unsigned char *dest = _image.p() + get_expected_ram_page_size() * z; + unsigned char *dest = _ram_image.p() + get_expected_ram_page_size() * z; int dest_row_width = (_x_size * _num_components * _component_width); int source_row_width = get_video_width() * 3; @@ -385,7 +385,7 @@ update_frame(int frame) { const unsigned char *source = page._alpha.get_frame_data(frame); if (source != NULL) { nassertv(get_video_width() <= _x_size && get_video_height() <= _y_size); - unsigned char *dest = _image.p() + get_expected_ram_page_size() * z; + unsigned char *dest = _ram_image.p() + get_expected_ram_page_size() * z; int dest_row_width = (_x_size * _num_components * _component_width); int source_row_width = get_video_width() * 3; diff --git a/panda/src/pgraph/bamFile.cxx b/panda/src/pgraph/bamFile.cxx index da7565630d..31518a518f 100644 --- a/panda/src/pgraph/bamFile.cxx +++ b/panda/src/pgraph/bamFile.cxx @@ -369,6 +369,30 @@ get_current_minor_ver() { return _bam_minor_ver; } +//////////////////////////////////////////////////////////////////// +// Function: BamFile::get_reader +// Access: Public +// Description: Returns the BamReader in charge of performing the +// read operations. This will return NULL unless +// open_read() was called. +//////////////////////////////////////////////////////////////////// +BamReader *BamFile:: +get_reader() { + return _reader; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamFile::get_writer +// Access: Public +// Description: Returns the BamWriter in charge of performing the +// write operations. This will return NULL unless +// open_write() was called. +//////////////////////////////////////////////////////////////////// +BamWriter *BamFile:: +get_writer() { + return _writer; +} + //////////////////////////////////////////////////////////////////// // Function: BamFile::continue_open_read // Access: Private diff --git a/panda/src/pgraph/bamFile.h b/panda/src/pgraph/bamFile.h index b1f6d6b084..f282948316 100644 --- a/panda/src/pgraph/bamFile.h +++ b/panda/src/pgraph/bamFile.h @@ -78,6 +78,9 @@ PUBLISHED: int get_current_major_ver(); int get_current_minor_ver(); + BamReader *get_reader(); + BamWriter *get_writer(); + private: bool continue_open_read(const string &bam_filename, bool report_errors); bool continue_open_write(const string &bam_filename, bool report_errors); diff --git a/panda/src/putil/Sources.pp b/panda/src/putil/Sources.pp index 81b0857836..7ec6a9c17a 100644 --- a/panda/src/putil/Sources.pp +++ b/panda/src/putil/Sources.pp @@ -12,7 +12,9 @@ animInterface.h animInterface.I \ bam.h bamEndian.h \ bamReader.I bamReader.N bamReader.h bamReaderParam.I \ - bamReaderParam.h bamWriter.I bamWriter.h \ + bamReaderParam.h \ + bamTextureMode.h \ + bamWriter.I bamWriter.h \ bitArray.I bitArray.h \ bitMask.I bitMask.h \ buttonHandle.I \ @@ -58,7 +60,9 @@ #define INCLUDED_SOURCES \ animInterface.cxx \ bamEndian.cxx \ - bamReader.cxx bamReaderParam.cxx bamWriter.cxx \ + bamReader.cxx bamReaderParam.cxx \ + bamTextureMode.cxx \ + bamWriter.cxx \ bitArray.cxx \ bitMask.cxx \ buttonHandle.cxx buttonRegistry.cxx \ @@ -91,6 +95,7 @@ animInterface.h animInterface.I \ bam.h bamEndian.h \ bamReader.I bamReader.h bamReaderParam.I bamReaderParam.h \ + bamTextureMode.h \ bamWriter.I bamWriter.h \ bitArray.I bitArray.h \ bitMask.I bitMask.h \ diff --git a/panda/src/putil/bam.h b/panda/src/putil/bam.h index 78a3fcc5e0..16e7ef7daf 100644 --- a/panda/src/putil/bam.h +++ b/panda/src/putil/bam.h @@ -36,7 +36,8 @@ static const unsigned short _bam_major_ver = 6; // Bumped to major version 5 on 5/6/05 for new Geom implementation. // Bumped to major version 6 on 2/11/06 to factor out PandaNode::CData. -static const unsigned short _bam_minor_ver = 0; +static const unsigned short _bam_minor_ver = 1; +// Bumped to minor version 1 on 3/12/06 to add Texture::_compression. #endif diff --git a/panda/src/putil/bamTextureMode.cxx b/panda/src/putil/bamTextureMode.cxx new file mode 100644 index 0000000000..fef4a3bb67 --- /dev/null +++ b/panda/src/putil/bamTextureMode.cxx @@ -0,0 +1,66 @@ +// Filename: bamTextureMode.cxx +// Created by: drose (14Mar06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "bamTextureMode.h" +#include "config_util.h" + +ostream & +operator << (ostream &out, BamTextureMode btm) { + switch (btm) { + case BTM_unchanged: + return out << "unchanged"; + + case BTM_fullpath: + return out << "fullpath"; + + case BTM_relative: + return out << "relative"; + + case BTM_basename: + return out << "basename"; + + case BTM_rawdata: + return out << "rawdata"; + } + + return out << "**invalid BamTextureMode (" << (int)btm << ")**"; +} + +istream & +operator >> (istream &in, BamTextureMode &btm) { + string word; + in >> word; + + if (cmp_nocase(word, "unchanged") == 0) { + btm = BTM_unchanged; + } else if (cmp_nocase(word, "fullpath") == 0) { + btm = BTM_fullpath; + } else if (cmp_nocase(word, "relative") == 0) { + btm = BTM_relative; + } else if (cmp_nocase(word, "basename") == 0) { + btm = BTM_basename; + } else if (cmp_nocase(word, "rawdata") == 0) { + btm = BTM_rawdata; + + } else { + util_cat->error() << "Invalid BamTextureMode value: " << word << "\n"; + btm = BTM_relative; + } + + return in; +} diff --git a/panda/src/putil/bamTextureMode.h b/panda/src/putil/bamTextureMode.h new file mode 100644 index 0000000000..a71289afa7 --- /dev/null +++ b/panda/src/putil/bamTextureMode.h @@ -0,0 +1,36 @@ +// Filename: bamTextureMode.h +// Created by: drose (14Mar06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#ifndef BAMTEXTUREMODE_H +#define BAMTEXTUREMODE_H + +#include "pandabase.h" + +// This enum is used to control how textures are written to a bam +// stream. +enum BamTextureMode { + BTM_unchanged, + BTM_fullpath, + BTM_relative, + BTM_basename, + BTM_rawdata +}; +EXPCL_PANDA ostream &operator << (ostream &out, BamTextureMode btm); +EXPCL_PANDA istream &operator >> (istream &in, BamTextureMode &btm); + +#endif diff --git a/panda/src/putil/bamWriter.I b/panda/src/putil/bamWriter.I index 2a2c8b3f47..b2a19ea24c 100644 --- a/panda/src/putil/bamWriter.I +++ b/panda/src/putil/bamWriter.I @@ -43,3 +43,29 @@ INLINE BamEndian BamWriter:: get_file_endian() const { return _file_endian; } + +//////////////////////////////////////////////////////////////////// +// Function: BamWriter::get_file_texture_mode +// Access: Public +// Description: Returns the BamTextureMode preference indicated by +// the Bam file currently being written. Texture +// objects written to this Bam file will be encoded +// according to the specified mode. +//////////////////////////////////////////////////////////////////// +INLINE BamTextureMode BamWriter:: +get_file_texture_mode() const { + return _file_texture_mode; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamWriter::set_file_texture_mode +// Access: Public +// Description: Changes the BamTextureMode preference for +// the Bam file currently being written. Texture +// objects written to this Bam file will be encoded +// according to the specified mode. +//////////////////////////////////////////////////////////////////// +INLINE void BamWriter:: +set_file_texture_mode(BamTextureMode file_texture_mode) { + _file_texture_mode = file_texture_mode; +} diff --git a/panda/src/putil/bamWriter.cxx b/panda/src/putil/bamWriter.cxx index 58982afe24..436b4395da 100644 --- a/panda/src/putil/bamWriter.cxx +++ b/panda/src/putil/bamWriter.cxx @@ -78,6 +78,7 @@ init() { _long_pta_id = false; _file_endian = bam_endian; + _file_texture_mode = bam_texture_mode; // Write out the current major and minor BAM file version numbers. Datagram header; diff --git a/panda/src/putil/bamWriter.h b/panda/src/putil/bamWriter.h index 8a494f298a..7683833461 100644 --- a/panda/src/putil/bamWriter.h +++ b/panda/src/putil/bamWriter.h @@ -22,6 +22,7 @@ #include "pandabase.h" #include "notify.h" #include "bamEndian.h" +#include "bamTextureMode.h" #include "typedWritable.h" #include "datagramSink.h" #include "pdeque.h" @@ -88,6 +89,9 @@ public: INLINE BamEndian get_file_endian() const; + INLINE BamTextureMode get_file_texture_mode() const; + INLINE void set_file_texture_mode(BamTextureMode file_texture_mode); + public: // Functions to support classes that write themselves to the Bam. @@ -109,6 +113,7 @@ private: Filename _filename; BamEndian _file_endian; + BamTextureMode _file_texture_mode; // This is the set of all TypeHandles already written. pset _types_written; diff --git a/panda/src/putil/config_util.cxx b/panda/src/putil/config_util.cxx index 2543e4ffb4..8a3ed0b273 100644 --- a/panda/src/putil/config_util.cxx +++ b/panda/src/putil/config_util.cxx @@ -53,6 +53,11 @@ ConfigVariableEnum bam_endian "may set it to \"littleendian\" or \"bigendian\" to target a " "particular platform.")); +ConfigVariableEnum bam_texture_mode +("bam-texture-mode", BTM_relative, + PRC_DESC("Set this to specify how textures should be written into Bam files." + "See the panda source or documentation for available options.")); + ConfigVariableSearchPath model_path ("model-path", PRC_DESC("The default directories to search for all models and general " diff --git a/panda/src/putil/config_util.h b/panda/src/putil/config_util.h index a33d6506bb..cb6ace487f 100644 --- a/panda/src/putil/config_util.h +++ b/panda/src/putil/config_util.h @@ -24,6 +24,7 @@ #include "configVariableSearchPath.h" #include "configVariableEnum.h" #include "bamEndian.h" +#include "bamTextureMode.h" #include "dconfig.h" class DSearchPath; @@ -36,9 +37,10 @@ NotifyCategoryDecl(bam, EXPCL_PANDA, EXPTP_PANDA); // because we must be able to access it at static init time. Instead // of declaring it a global constant, we'll make it a member of // MemoryUsage. - //extern EXPCL_PANDA const bool track_memory_usage; + extern EXPCL_PANDA ConfigVariableEnum bam_endian; +extern EXPCL_PANDA ConfigVariableEnum bam_texture_mode; extern EXPCL_PANDA ConfigVariableSearchPath model_path; extern EXPCL_PANDA ConfigVariableSearchPath texture_path; diff --git a/panda/src/putil/load_prc_file.h b/panda/src/putil/load_prc_file.h index 2ed7c7a378..f41e117d5b 100644 --- a/panda/src/putil/load_prc_file.h +++ b/panda/src/putil/load_prc_file.h @@ -16,8 +16,8 @@ // //////////////////////////////////////////////////////////////////// -#ifndef LOAD_EGG_FILE_H -#define LOAD_EGG_FILE_H +#ifndef LOAD_PRC_FILE_H +#define LOAD_PRC_FILE_H #include "pandabase.h" diff --git a/panda/src/putil/putil_composite1.cxx b/panda/src/putil/putil_composite1.cxx index 97ec16a96d..bda7a36219 100644 --- a/panda/src/putil/putil_composite1.cxx +++ b/panda/src/putil/putil_composite1.cxx @@ -2,6 +2,7 @@ #include "bamEndian.cxx" #include "bamReader.cxx" #include "bamReaderParam.cxx" +#include "bamTextureMode.cxx" #include "bamWriter.cxx" #include "bitArray.cxx" #include "bitMask.cxx" diff --git a/panda/src/text/dynamicTextPage.cxx b/panda/src/text/dynamicTextPage.cxx index 093a3e0cb0..edd5f8ac08 100644 --- a/panda/src/text/dynamicTextPage.cxx +++ b/panda/src/text/dynamicTextPage.cxx @@ -33,6 +33,10 @@ DynamicTextPage:: DynamicTextPage(DynamicTextFont *font, int page_number) : _font(font) { + // Since the texture might change frequently, don't try to compress + // it by default. + _compression = CM_off; + _x_size = _font->get_page_x_size(); _y_size = _font->get_page_y_size(); diff --git a/pandatool/src/bam/bamInfo.cxx b/pandatool/src/bam/bamInfo.cxx index bcb96d1ce8..d5b455ceb9 100644 --- a/pandatool/src/bam/bamInfo.cxx +++ b/pandatool/src/bam/bamInfo.cxx @@ -21,6 +21,7 @@ #include "bamFile.h" #include "pandaNode.h" #include "geomNode.h" +#include "texture.h" #include "recorderHeader.h" #include "recorderFrame.h" #include "recorderTable.h" @@ -145,6 +146,10 @@ get_info(const Filename &filename) { objects[0]->is_of_type(PandaNode::get_class_type())) { describe_scene_graph(DCAST(PandaNode, objects[0])); + } else if (objects.size() == 1 && + objects[0]->is_of_type(Texture::get_class_type())) { + describe_texture(DCAST(Texture, objects[0])); + } else if (!objects.empty() && objects[0]->is_of_type(RecorderHeader::get_class_type())) { describe_session(DCAST(RecorderHeader, objects[0]), objects); @@ -189,6 +194,16 @@ describe_scene_graph(PandaNode *node) { } } +//////////////////////////////////////////////////////////////////// +// Function: BamInfo::describe_texture +// Access: Private +// Description: Called for Bam files that contain a Texture object. +//////////////////////////////////////////////////////////////////// +void BamInfo:: +describe_texture(Texture *tex) { + tex->write(nout, 2); +} + //////////////////////////////////////////////////////////////////// // Function: BamInfo::describe_session // Access: Private diff --git a/pandatool/src/bam/bamInfo.h b/pandatool/src/bam/bamInfo.h index 829faac448..6b9def0d48 100644 --- a/pandatool/src/bam/bamInfo.h +++ b/pandatool/src/bam/bamInfo.h @@ -48,6 +48,7 @@ private: bool get_info(const Filename &filename); void describe_scene_graph(PandaNode *node); + void describe_texture(Texture *tex); void describe_session(RecorderHeader *header, const Objects &objects); void describe_general_object(TypedWritable *object); void list_hierarchy(PandaNode *node, int indent_level); diff --git a/pandatool/src/bam/eggToBam.cxx b/pandatool/src/bam/eggToBam.cxx index e4bff28e00..e5a0c53a5d 100644 --- a/pandatool/src/bam/eggToBam.cxx +++ b/pandatool/src/bam/eggToBam.cxx @@ -24,6 +24,18 @@ #include "config_egg2pg.h" #include "config_gobj.h" #include "config_chan.h" +#include "pandaNode.h" +#include "geomNode.h" +#include "renderState.h" +#include "textureAttrib.h" +#include "dcast.h" +#include "graphicsPipeSelection.h" +#include "graphicsEngine.h" +#include "graphicsBuffer.h" +#include "graphicsStateGuardian.h" +#include "load_prc_file.h" +#include "windowProperties.h" +#include "frameBufferProperties.h" //////////////////////////////////////////////////////////////////// // Function: EggToBam::Constructor @@ -36,7 +48,10 @@ EggToBam() : { set_program_description ("This program reads Egg files and outputs Bam files, the binary format " - "suitable for direct loading of animation and models into Panda."); + "suitable for direct loading of animation and models into Panda. Bam " + "files are tied to a particular version of Panda, so should not be " + "considered replacements for egg files, but they tend to be smaller and " + "load much faster than the equivalent egg files."); // -f is always in effect for egg2bam. It doesn't make sense to // provide it as an option to the user. @@ -107,6 +122,53 @@ EggToBam() : "way to ensure the bam file is completely self-contained.", &EggToBam::dispatch_none, &_tex_rawdata); + add_option + ("txo", "", 0, + "Rather than writing texture data directly into the bam file, as in " + "-rawtex, create a texture object for each referenced texture. A " + "texture object is a kind of mini-bam file, with a .txo extension, " + "that contains all of the data needed to recreate a texture, including " + "its image contents, filter and wrap settings, and so on. 3-D textures " + "and cube maps can also be represented in a single .txo file. Texture " + "object files, like bam files, are tied to a particular version of " + "Panda.", + &EggToBam::dispatch_none, &_tex_txo); + +#ifdef HAVE_ZLIB + add_option + ("txopz", "", 0, + "In addition to writing texture object files as above, compress each " + "one using pzip to a .txo.pz file. In many cases, this will yield a " + "disk file size comparable to that achieved by png compression. This " + "is an on-disk compression only, and does not affect the amount of " + "RAM or texture memory consumed by the texture when it is loaded.", + &EggToBam::dispatch_none, &_tex_txopz); +#endif // HAVE_ZLIB + + add_option + ("ctex", "", 0, + "Asks the graphics card to pre-compress the texture images when using " + "-rawtex or -txo. " +#ifdef HAVE_ZLIB + "This is unrelated to the on-disk compression achieved " + "via -txopz (and it may be used in conjunction with that parameter). " +#endif // HAVE_ZLIB + "This will result in a smaller RAM and texture memory footprint for " + "the texture images. The same " + "effect can be achieved at load time by setting compressed-textures in " + "your Config.prc file; but -ctex pre-compresses the " + "textures so that they do not need to be compressed at load time. " + "Note that using -ctex makes .txo files that are only guaranteed to load " + "on the particular graphics card that was used to generate them.", + &EggToBam::dispatch_none, &_tex_ctex); + + add_option + ("load-display", "display name", 0, + "Specifies the particular display module to load to perform the texture " + "compression requested by -ctex. If this is omitted, the default is " + "taken from the Config.prc file.", + &EggToBam::dispatch_string, NULL, &_load_display); + redescribe_option ("cs", "Specify the coordinate system of the resulting " + _format_name + @@ -118,6 +180,7 @@ EggToBam() : _egg_flatten = 0; _egg_combine_geoms = 0; _egg_suppress_hidden = 1; + _tex_txopz = false; } //////////////////////////////////////////////////////////////////// @@ -162,6 +225,31 @@ run() { nout << "Unable to build scene graph from egg file.\n"; exit(1); } + + if (_tex_ctex) { + if (!make_buffer()) { + nout << "Unable to initialize graphics context; cannot compress textures.\n"; + exit(1); + } + } + + if (_tex_txo || _tex_txopz || (_tex_ctex && _tex_rawdata)) { + collect_textures(root); + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *tex = (*ti); + if (_tex_ctex) { + tex->set_keep_ram_image(true); + tex->set_compression(Texture::CM_on); + if (!_engine->extract_texture_data(tex, _gsg)) { + nout << " couldn't compress " << tex->get_name() << "\n"; + } + } + if (_tex_txo || _tex_txopz) { + convert_txo(tex); + } + } + } if (_ls) { root->ls(nout, 0); @@ -170,7 +258,7 @@ run() { // This should be guaranteed because we pass false to the // constructor, above. nassertv(has_output_filename()); - + Filename filename = get_output_filename(); filename.make_dir(); nout << "Writing " << filename << "\n"; @@ -214,6 +302,158 @@ handle_args(ProgramBase::Args &args) { return EggToSomething::handle_args(args); } +//////////////////////////////////////////////////////////////////// +// Function: EggToBam::collect_textures +// Access: Private +// Description: Recursively walks the scene graph, looking for +// Texture references. +//////////////////////////////////////////////////////////////////// +void EggToBam:: +collect_textures(PandaNode *node) { + collect_textures(node->get_state()); + if (node->is_geom_node()) { + GeomNode *geom_node = DCAST(GeomNode, node); + int num_geoms = geom_node->get_num_geoms(); + for (int i = 0; i < num_geoms; ++i) { + collect_textures(geom_node->get_geom_state(i)); + } + } + + PandaNode::Children children = node->get_children(); + int num_children = children.get_num_children(); + for (int i = 0; i < num_children; ++i) { + collect_textures(children.get_child(i)); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: EggToBam::collect_textures +// Access: Private +// Description: Recursively walks the scene graph, looking for +// Texture references. +//////////////////////////////////////////////////////////////////// +void EggToBam:: +collect_textures(const RenderState *state) { + const TextureAttrib *tex_attrib = state->get_texture(); + if (tex_attrib != (TextureAttrib *)NULL) { + int num_on_stages = tex_attrib->get_num_on_stages(); + for (int i = 0; i < num_on_stages; ++i) { + _textures.insert(tex_attrib->get_on_texture(tex_attrib->get_on_stage(i))); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: EggToBam::convert_txo +// Access: Private +// Description: If the indicated Texture was not already loaded from +// a txo file, writes it to a txo file and updates the +// Texture object to reference the new file. +//////////////////////////////////////////////////////////////////// +void EggToBam:: +convert_txo(Texture *tex) { + if (!tex->get_loaded_from_txo()) { + Filename fullpath = tex->get_fullpath().get_filename_index(0); + if (_tex_txopz) { + fullpath.set_extension("txo.pz"); + // We use this clumsy syntax so that the new extension appears to be + // two separate extensions, .txo followed by .pz, which is what + // Texture::write() expects to find. + fullpath = Filename(fullpath.get_fullpath()); + } else { + fullpath.set_extension("txo"); + } + + if (tex->write(fullpath)) { + nout << " Writing " << fullpath; + if (tex->get_ram_image_compression() != Texture::CM_off) { + nout << " (compressed " << tex->get_ram_image_compression() << ")"; + } + nout << "\n"; + tex->set_loaded_from_txo(); + tex->set_fullpath(fullpath); + tex->clear_alpha_fullpath(); + + Filename filename = tex->get_filename().get_filename_index(0); + if (_tex_txopz) { + filename.set_extension("txo.pz"); + filename = Filename(filename.get_fullpath()); + } else { + filename.set_extension("txo"); + } + + tex->set_filename(filename); + tex->clear_alpha_filename(); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: EggToBam::make_buffer +// Access: Private +// Description: Creates a GraphicsBuffer for communicating with the +// graphics card. +//////////////////////////////////////////////////////////////////// +bool EggToBam:: +make_buffer() { + if (!_load_display.empty()) { + // Override the user's config file with the command-line parameter. + string prc = "load-display " + _load_display; + load_prc_file_data("prc", prc); + } + + GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr(); + _pipe = selection->make_default_pipe(); + if (_pipe == (GraphicsPipe *)NULL) { + nout << "Unable to create graphics pipe.\n"; + return false; + } + + _engine = new GraphicsEngine; + + FrameBufferProperties fbprops = FrameBufferProperties::get_default(); + + // Some graphics drivers can only create single-buffered offscreen + // buffers. So request that. + fbprops.set_frame_buffer_mode((fbprops.get_frame_buffer_mode() & ~FrameBufferProperties::FM_buffer) | FrameBufferProperties::FM_single_buffer); + + PT(GraphicsStateGuardian) gsg = + _engine->make_gsg(_pipe, fbprops); + if (gsg == (GraphicsStateGuardian *)NULL) { + nout << "Unable to create GraphicsStateGuardian.\n"; + return false; + } + _gsg = gsg; + + // We don't care how big the buffer is; we just need it to manifest + // the GSG. + _buffer = _engine->make_buffer(_gsg, "buffer", 0, 1, 1); + _engine->open_windows(); + if (_buffer == (GraphicsOutput *)NULL || !_buffer->is_valid()) { + if (_buffer != (GraphicsOutput *)NULL) { + _engine->remove_window(_buffer); + } + + // Couldn't create a buffer; try for a window instead. + GraphicsWindow *window = _engine->make_window(_gsg, "window", 0); + if (window == (GraphicsWindow *)NULL) { + nout << "Unable to create graphics window.\n"; + return false; + } + WindowProperties props; + props.set_size(1, 1); + props.set_origin(0, 0); + props.set_undecorated(true); + props.set_open(true); + props.set_z_order(WindowProperties::Z_bottom); + window->request_properties(props); + _buffer = window; + _engine->open_windows(); + } + + return true; +} + int main(int argc, char *argv[]) { EggToBam prog; diff --git a/pandatool/src/bam/eggToBam.h b/pandatool/src/bam/eggToBam.h index 4df8f3a5a7..3f8988f782 100644 --- a/pandatool/src/bam/eggToBam.h +++ b/pandatool/src/bam/eggToBam.h @@ -22,6 +22,15 @@ #include "pandatoolbase.h" #include "eggToSomething.h" +#include "pset.h" +#include "graphicsPipe.h" + +class PandaNode; +class RenderState; +class Texture; +class GraphicsEngine; +class GraphicsStateGuardian; +class GraphicsOutput; //////////////////////////////////////////////////////////////////// // Class : EggToBam @@ -37,6 +46,16 @@ protected: virtual bool handle_args(Args &args); private: + void collect_textures(PandaNode *node); + void collect_textures(const RenderState *state); + void convert_txo(Texture *tex); + + bool make_buffer(); + +private: + typedef pset Textures; + Textures _textures; + bool _has_egg_flatten; int _egg_flatten; bool _has_egg_combine_geoms; @@ -47,6 +66,16 @@ private: int _compression_quality; bool _compression_off; bool _tex_rawdata; + bool _tex_txo; + bool _tex_txopz; + bool _tex_ctex; + string _load_display; + + // The rest of this is required to support -ctex. + PT(GraphicsPipe) _pipe; + GraphicsStateGuardian *_gsg; + GraphicsEngine *_engine; + GraphicsOutput *_buffer; }; #endif