David Rose 21 лет назад
Родитель
Сommit
5761ae9d2c

+ 1 - 1
panda/src/display/frameBufferProperties.h

@@ -38,7 +38,6 @@ PUBLISHED:
   INLINE bool operator != (const FrameBufferProperties &other) const;
 
   enum FrameBufferMode {
-    FM_rgba =          0x0000,
     FM_rgb =           0x0000,
     FM_index =         0x0001,
     FM_single_buffer = 0x0000,
@@ -47,6 +46,7 @@ PUBLISHED:
     FM_buffer        = 0x0006,  // == (FM_single_buffer | FM_double_buffer | FM_triple_buffer)
     FM_accum =         0x0008,
     FM_alpha =         0x0010,
+    FM_rgba =          0x0010,  // == (FM_rgb | FM_alpha)
     FM_depth =         0x0020,
     FM_stencil =       0x0040,
     FM_multisample =   0x0080,

+ 17 - 2
panda/src/display/graphicsEngine.cxx

@@ -383,7 +383,7 @@ remove_all_windows() {
     GraphicsOutput *win = (*wi);
     do_remove_window(win);
   }
-
+  
   _windows.clear();
 
   _app.do_release(this);
@@ -449,6 +449,21 @@ render_frame() {
   if (_flip_state != FS_flip) {
     do_flip_frame();
   }
+
+  // Are any of the windows ready to be deleted?
+  Windows new_windows;
+  new_windows.reserve(_windows.size());
+  Windows::iterator wi;
+  for (wi = _windows.begin(); wi != _windows.end(); ++wi) {
+    GraphicsOutput *win = (*wi);
+    if (win->get_delete_flag()) {
+      do_remove_window(win);
+
+    } else {
+      new_windows.push_back(win);
+    }
+  }
+  new_windows.swap(_windows);
   
   // Grab each thread's mutex again after all windows have flipped.
   Threads::const_iterator ti;
@@ -1303,7 +1318,7 @@ add_window(Windows &wlist, GraphicsOutput *window) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: GraphicsEngine::WindowRenderer::remove_window_now
+//     Function: GraphicsEngine::WindowRenderer::remove_window
 //       Access: Public
 //  Description: Immediately removes the indicated window from all
 //               lists.  If the window is currently open and is

+ 53 - 0
panda/src/display/graphicsOutput.I

@@ -160,6 +160,59 @@ is_valid() const {
   return _is_valid;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsOutput::set_one_shot
+//       Access: Published
+//  Description: Changes the current setting of the one-shot flag.
+//               When this is true, the GraphicsOutput will
+//               automatically detach its texture (if it has one) and
+//               remove itself from the GraphicsEngine after it
+//               renders the next frame.
+////////////////////////////////////////////////////////////////////
+INLINE void GraphicsOutput::
+set_one_shot(bool one_shot) {
+  _one_shot = one_shot;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsOutput::get_one_shot
+//       Access: Published
+//  Description: Returns the current setting of the one-shot flag.
+//               When this is true, the GraphicsOutput will
+//               automatically detach its texture (if it has one) and
+//               remove itself from the GraphicsEngine after it
+//               renders the next frame.
+////////////////////////////////////////////////////////////////////
+INLINE bool GraphicsOutput::
+get_one_shot() const {
+  return _one_shot;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsOutput::clear_delete_flag
+//       Access: Published
+//  Description: Resets the delete flag, so the GraphicsOutput will
+//               not be automatically deleted before the beginning of
+//               the next frame.
+////////////////////////////////////////////////////////////////////
+INLINE void GraphicsOutput::
+clear_delete_flag() {
+  _delete_flag = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsOutput::get_delete_flag
+//       Access: Published
+//  Description: Returns the current setting of the delete flag.  When
+//               this is true, the GraphicsOutput will automatically
+//               be removed before the beginning of the next frame by
+//               the GraphicsEngine.
+////////////////////////////////////////////////////////////////////
+INLINE bool GraphicsOutput::
+get_delete_flag() const {
+  return _delete_flag;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsOutput::get_sort
 //       Access: Published

+ 81 - 38
panda/src/display/graphicsOutput.cxx

@@ -19,6 +19,7 @@
 #include "graphicsOutput.h"
 #include "graphicsPipe.h"
 #include "graphicsEngine.h"
+#include "graphicsWindow.h"
 #include "config_display.h"
 #include "mutexHolder.h"
 #include "hardwareChannel.h"
@@ -54,6 +55,9 @@ GraphicsOutput(GraphicsPipe *pipe, GraphicsStateGuardian *gsg,
   _flip_ready = false;
   _needs_context = true;
   _sort = 0;
+  _active = true;
+  _one_shot = false;
+  _delete_flag = false;
 
   int mode = gsg->get_properties().get_frame_buffer_mode();
   if ((mode & FrameBufferProperties::FM_buffer) == FrameBufferProperties::FM_single_buffer) {
@@ -112,6 +116,73 @@ GraphicsOutput::
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsOutput::detach_texture
+//       Access: Published
+//  Description: Disassociates the texture from the GraphicsOutput.
+//               It will no longer be filled as the frame renders, and
+//               it may be used (with its current contents) as a
+//               texture in its own right.
+////////////////////////////////////////////////////////////////////
+void GraphicsOutput::
+detach_texture() {
+  MutexHolder holder(_lock);
+  _texture = NULL;
+  _copy_texture = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsOutput::setup_copy_texture
+//       Access: Published
+//  Description: Creates a new Texture object, suitable for copying
+//               the contents of this buffer into, and stores it in
+//               _texture.  This also disassociates the previous
+//               texture (if any).
+////////////////////////////////////////////////////////////////////
+void GraphicsOutput::
+setup_copy_texture(const string &name) {
+  MutexHolder holder(_lock);
+
+  _texture = new Texture();
+  _texture->set_name(name);
+  _texture->set_wrapu(Texture::WM_clamp);
+  _texture->set_wrapv(Texture::WM_clamp);
+
+  // We should match the texture format up with the framebuffer
+  // format.  Easier said than done!
+  if (_gsg != (GraphicsStateGuardian *)NULL) {
+    int mode = _gsg->get_properties().get_frame_buffer_mode();
+    PixelBuffer *pb = _texture->_pbuffer;
+
+    if (mode & FrameBufferProperties::FM_alpha) {
+      pb->set_format(PixelBuffer::F_rgba8);
+      pb->set_num_components(4);
+      pb->set_component_width(1);
+      pb->set_image_type(PixelBuffer::T_unsigned_byte);
+
+    } else {
+      pb->set_format(PixelBuffer::F_rgb8);
+      pb->set_num_components(3);
+      pb->set_component_width(1);
+      pb->set_image_type(PixelBuffer::T_unsigned_byte);
+    }
+  }
+
+  _copy_texture = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsOutput::set_active
+//       Access: Published
+//  Description: Sets the active flag associated with the
+//               GraphicsOutput.  If the GraphicsOutput is marked
+//               inactive, nothing is rendered.
+////////////////////////////////////////////////////////////////////
+void GraphicsOutput::
+set_active(bool active) {
+  _active = active;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsOutput::is_active
 //       Access: Published, Virtual
@@ -120,7 +191,7 @@ GraphicsOutput::
 ////////////////////////////////////////////////////////////////////
 bool GraphicsOutput::
 is_active() const {
-  return is_valid();
+  return _active && is_valid();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -571,6 +642,15 @@ end_frame() {
   if (!_gsg->get_properties().is_single_buffered()) {
     _flip_ready = true;
   }
+
+  if (_one_shot && !show_buffers) {
+    // In one-shot mode, we request the GraphicsEngine to delete the
+    // window after we have rendered a frame.  But when show-buffers
+    // mode is enabled, we don't do this, to give the user a chance to
+    // see the output.
+    _active = false;
+    _delete_flag = true;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -684,43 +764,6 @@ declare_channel(int index, GraphicsChannel *chan) {
   _channels[index] = chan;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsOutput::setup_copy_texture
-//       Access: Protected
-//  Description: Creates a new Texture object, suitable for copying
-//               the contents of this buffer into, and stores it in
-//               _texture.
-////////////////////////////////////////////////////////////////////
-void GraphicsOutput::
-setup_copy_texture(const string &name) {
-  _texture = new Texture();
-  _texture->set_name(name);
-  _texture->set_wrapu(Texture::WM_clamp);
-  _texture->set_wrapv(Texture::WM_clamp);
-
-  // We should match the texture format up with the framebuffer
-  // format.  Easier said than done!
-  if (_gsg != (GraphicsStateGuardian *)NULL) {
-    int mode = _gsg->get_properties().get_frame_buffer_mode();
-    PixelBuffer *pb = _texture->_pbuffer;
-
-    if (mode & FrameBufferProperties::FM_alpha) {
-      pb->set_format(PixelBuffer::F_rgba8);
-      pb->set_num_components(4);
-      pb->set_component_width(1);
-      pb->set_image_type(PixelBuffer::T_unsigned_byte);
-
-    } else {
-      pb->set_format(PixelBuffer::F_rgb8);
-      pb->set_num_components(3);
-      pb->set_component_width(1);
-      pb->set_image_type(PixelBuffer::T_unsigned_byte);
-    }
-  }
-
-  _copy_texture = true;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsOutput::do_determine_display_regions
 //       Access: Private

+ 15 - 2
panda/src/display/graphicsOutput.h

@@ -75,16 +75,25 @@ PUBLISHED:
 
   INLINE bool has_texture() const;  
   INLINE Texture *get_texture() const;  
+  void detach_texture();
+  void setup_copy_texture(const string &name);
 
   INLINE int get_x_size() const;
   INLINE int get_y_size() const;
   INLINE bool has_size() const;
   INLINE bool is_valid() const;
 
+  void set_active(bool active);
   virtual bool is_active() const;
 
-  INLINE int get_sort() const;
+  INLINE void set_one_shot(bool one_shot);
+  INLINE bool get_one_shot() const;
+
+  INLINE void clear_delete_flag();
+  INLINE bool get_delete_flag() const;
+
   void set_sort(int sort);
+  INLINE int get_sort() const;
 
   GraphicsChannel *get_channel(int index);
   void remove_channel(int index);
@@ -145,7 +154,6 @@ public:
 
 protected:
   void declare_channel(int index, GraphicsChannel *chan);
-  void setup_copy_texture(const string &name);
   
 protected:
   PT(GraphicsStateGuardian) _gsg;
@@ -162,6 +170,11 @@ private:
 
   int _sort;
 
+protected:
+  bool _active;
+  bool _one_shot;
+  bool _delete_flag;
+
 protected:
   Mutex _lock; 
   // protects _channels, _display_regions.

+ 1 - 1
panda/src/display/graphicsWindow.cxx

@@ -160,7 +160,7 @@ request_properties(const WindowProperties &requested_properties) {
 bool GraphicsWindow::
 is_active() const {
   // Make this smarter?
-  return _properties.get_open() && !_properties.get_minimized();
+  return _active && _properties.get_open() && !_properties.get_minimized();
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/display/parasiteBuffer.cxx

@@ -73,7 +73,7 @@ ParasiteBuffer::
 ////////////////////////////////////////////////////////////////////
 bool ParasiteBuffer::
 is_active() const {
-  return _host->is_active();
+  return _active && _host->is_active();
 }
 
 ////////////////////////////////////////////////////////////////////

+ 13 - 1
panda/src/egg2pg/eggLoader.cxx

@@ -3109,7 +3109,19 @@ apply_tex_mat(CPT(RenderAttrib) tex_mat_attrib,
                    tex_mat(1, 0), tex_mat(1, 1), 0.0f, tex_mat(1, 2),
                    0.0f, 0.0f, 1.0f, 0.0f,
                    tex_mat(2, 0), tex_mat(2, 1), 0.0f, tex_mat(2, 2));
-    CPT(TransformState) transform = TransformState::make_mat(mat4);
+    CPT(TransformState) transform;
+
+    LVecBase3f scale, shear, hpr, translate;
+    if (decompose_matrix(mat4, scale, shear, hpr, translate)) {
+      // If the texture matrix can be represented componentwise, do
+      // so.
+      transform = TransformState::make_pos_hpr_scale_shear
+        (translate, hpr, scale, shear);
+
+    } else {
+      // Otherwise, make a matrix transform.
+      transform = TransformState::make_mat(mat4);
+    }
   
     if (tex_mat_attrib == (const RenderAttrib *)NULL) {
       tex_mat_attrib = TexMatrixAttrib::make();

+ 20 - 34
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -2030,27 +2030,7 @@ copy_texture(Texture *tex, const DisplayRegion *dr) {
   nassertv(tex != NULL && dr != NULL);
 
   int xo, yo, w, h;
-
-#if 0
-  // Determine the size of the grab from the given display region
-  // If the requested region is not a power of two, grab a region that is
-  // a power of two that contains the requested region
-  int req_w, req_h;
-  dr->get_region_pixels(xo, yo, req_w, req_h);
-  w = binary_log_cap(req_w);
-  h = binary_log_cap(req_h);
-  if (w != req_w || h != req_h) {
-    tex->_requested_w = req_w;
-    tex->_requested_h = req_h;
-    tex->_has_requested_size = true;
-  }
-#else
-  // doing the above is bad unless you also provide some way
-  // for the caller to adjust his texture coordinates accordingly
-  // this was done for 'draw_texture' but not for anything else
-  
   dr->get_region_pixels(xo, yo, w, h);
-#endif
 
   PixelBuffer *pb = tex->_pbuffer;
   pb->set_xsize(w);
@@ -3230,8 +3210,7 @@ specify_texture(Texture *tex) {
 
   Texture::FilterType minfilter = tex->get_minfilter();
   Texture::FilterType magfilter = tex->get_magfilter();
-
-  bool uses_mipmaps = tex->uses_mipmaps();
+  bool uses_mipmaps = tex->uses_mipmaps() && !CLP(ignore_mipmaps);
 
 #ifndef NDEBUG
   if (CLP(force_mipmaps)) {
@@ -3240,11 +3219,25 @@ specify_texture(Texture *tex) {
     uses_mipmaps = true;
   }
 #endif
+
+  if (_supports_generate_mipmap) {
+    // If the hardware can automatically generate mipmaps, ask it to
+    // do so now, but only if the texture requires them.
+    GLP(TexParameteri)(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, uses_mipmaps);
+
+  } else if (!tex->might_have_ram_image()) {
+    // If the hardware can't automatically generate mipmaps, but it's
+    // a dynamically generated texture (that is, the RAM image isn't
+    // available so it didn't pass through the CPU), then we'd better
+    // not try to enable mipmap filtering, since we can't generate
+    // mipmaps.
+    uses_mipmaps = false;
+  }
  
   GLP(TexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
-                     get_texture_filter_type(tex->get_minfilter()));
+                     get_texture_filter_type(minfilter, !uses_mipmaps));
   GLP(TexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
-                     get_texture_filter_type(tex->get_magfilter()));
+                     get_texture_filter_type(magfilter, true));
 
   report_my_gl_errors();
 }
@@ -3378,10 +3371,7 @@ apply_texture_immediate(CLP(TextureContext) *gtc, Texture *tex) {
       
     } else 
 #endif 
-      if (_supports_generate_mipmap) {
-        GLP(TexParameteri)(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
-
-      } else {
+      if (!_supports_generate_mipmap) {
         // We only need to build the mipmaps by hand if the GL
         // doesn't support generating them automatically.
         GLUP(Build2DMipmaps)(GL_TEXTURE_2D, internal_format,
@@ -3402,10 +3392,6 @@ apply_texture_immediate(CLP(TextureContext) *gtc, Texture *tex) {
         report_my_gl_errors();
         return true;
       }
-  } else {
-    if (_supports_generate_mipmap) {
-      GLP(TexParameteri)(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
-    }
   }
 
   GLint border_width = tex->get_border_width();
@@ -3711,11 +3697,11 @@ get_texture_wrap_mode(Texture::WrapMode wm) const {
 //               to GL's.
 ////////////////////////////////////////////////////////////////////
 GLenum CLP(GraphicsStateGuardian)::
-get_texture_filter_type(Texture::FilterType ft) {
+get_texture_filter_type(Texture::FilterType ft, bool ignore_mipmaps) {
   if (CLP(ignore_filters)) {
     return GL_NEAREST;
 
-  } else if (CLP(ignore_mipmaps)) {
+  } else if (ignore_mipmaps) {
     switch (ft) {
     case Texture::FT_nearest_mipmap_nearest:
     case Texture::FT_nearest:

+ 1 - 1
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -217,7 +217,7 @@ protected:
                          const RenderBuffer &rb);
 
   GLenum get_texture_wrap_mode(Texture::WrapMode wm) const;
-  static GLenum get_texture_filter_type(Texture::FilterType ft);
+  static GLenum get_texture_filter_type(Texture::FilterType ft, bool ignore_mipmaps);
   static GLenum get_image_type(PixelBuffer::Type type);
   GLint get_external_image_format(PixelBuffer::Format format) const;
   static GLint get_internal_image_format(PixelBuffer::Format format);

+ 16 - 1
panda/src/gobj/texture.I

@@ -110,7 +110,7 @@ uses_mipmaps() const {
 //  Description: Returns true if the Texture has its image contents
 //               available in main RAM, false if it exists only in
 //               texture memory or in the prepared GSG context.
-
+//
 //               Note that this has nothing to do with whether
 //               get_ram_image() will fail or not.  Even if
 //               has_ram_image() returns false, get_ram_image() may
@@ -137,6 +137,21 @@ has_ram_image() const {
   return !_pbuffer->_image.empty();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::might_have_ram_image
+//       Access: Public
+//  Description: Returns true if the texture's image contents are
+//               currently available in main RAM, or there is reason
+//               to believe it can be loaded on demand.  That is, this
+//               function returns a "best guess" as to whether
+//               get_ram_image() will succeed without actually calling
+//               it first.
+////////////////////////////////////////////////////////////////////
+INLINE bool Texture::
+might_have_ram_image() const {
+  return (has_ram_image() || has_filename());
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::set_keep_ram_image
 //       Access: Public

+ 7 - 3
panda/src/gobj/texture.cxx

@@ -619,12 +619,16 @@ release_all() {
 //               false return value from has_ram_image() indicates
 //               only that get_ram_image() may need to reload the
 //               texture from disk, which it will do automatically.
+//               However, you can call might_have_ram_image(), which
+//               will return true if the ram image exists, or there is
+//               a reasonable reason to believe it can be loaded.
 //
 //               On the other hand, it is possible that the texture
 //               cannot be found on disk or is otherwise unavailable.
-//               If that happens, this function returns NULL.  There
-//               is no way to predict whether get_ram_image() will
-//               return NULL without calling it first.
+//               If that happens, this function will return NULL.
+//               There is no way to predict with 100% accuracy whether
+//               get_ram_image() will return NULL without calling it
+//               first; might_have_ram_image() is the closest.
 ////////////////////////////////////////////////////////////////////
 PixelBuffer *Texture::
 get_ram_image() {

+ 1 - 0
panda/src/gobj/texture.h

@@ -116,6 +116,7 @@ public:
   int release_all();
 
   INLINE bool has_ram_image() const;
+  INLINE bool might_have_ram_image() const;
   PixelBuffer *get_ram_image();
   INLINE void set_keep_ram_image(bool keep_ram_image);
   INLINE bool get_keep_ram_image() const;

+ 2 - 1
panda/src/grutil/Sources.pp

@@ -12,7 +12,8 @@
     cardMaker.I cardMaker.h \
     config_grutil.h \
     frameRateMeter.I frameRateMeter.h \
-    lineSegs.I lineSegs.h
+    lineSegs.I lineSegs.h \
+    multitexReducer.I multitexReducer.h multitexReducer.cxx
     
   #define INCLUDED_SOURCES \
     cardMaker.cxx \

+ 50 - 0
panda/src/grutil/multitexReducer.I

@@ -0,0 +1,50 @@
+// Filename: multitexReducer.I
+// Created by:  drose (30Nov04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::StageInfo::operator <
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE bool MultitexReducer::StageInfo::
+operator < (const MultitexReducer::StageInfo &other) const {
+  if (_stage != other._stage) {
+    return _stage < other._stage;
+  }
+  if (_tex != other._tex) {
+    return _tex < other._tex;
+  }
+  if (_tex_mat != other._tex_mat) {
+    return _tex_mat < other._tex_mat;
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::GeomInfo::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE MultitexReducer::GeomInfo::
+GeomInfo(GeomNode *geom_node, int index) :
+  _geom_node(geom_node),
+  _index(index)
+{
+}

+ 486 - 0
panda/src/grutil/multitexReducer.cxx

@@ -0,0 +1,486 @@
+// Filename: multitexReducer.cxx
+// Created by:  drose (30Nov04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "multitexReducer.h"
+#include "pandaNode.h"
+#include "geomNode.h"
+#include "geom.h"
+#include "renderState.h"
+#include "transformState.h"
+#include "graphicsOutput.h"
+#include "graphicsChannel.h"
+#include "graphicsLayer.h"
+#include "displayRegion.h"
+#include "nodePath.h"
+#include "camera.h"
+#include "orthographicLens.h"
+#include "cardMaker.h"
+#include "colorBlendAttrib.h"
+#include "config_grutil.h"
+#include "dcast.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MultitexReducer::
+MultitexReducer() {
+  _target_stage = TextureStage::get_default();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::Destructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MultitexReducer::
+~MultitexReducer() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::clear
+//       Access: Published
+//  Description: Removes the record of nodes that were previously
+//               discovered by scan().
+////////////////////////////////////////////////////////////////////
+void MultitexReducer::
+clear() {
+  _stages.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::scan
+//       Access: Published
+//  Description: Starts scanning the hierarchy beginning at the
+//               indicated node.  Any GeomNodes discovered in the
+//               hierarchy with multitexture will be added to internal
+//               structures in the MultitexReducer so that a future
+//               call to flatten() will operate on all of these at
+//               once.
+//
+//               The indicated transform and state are the state
+//               inherited from the node's ancestors; any multitexture
+//               operations will be accumulated from the indicated
+//               starting state.
+////////////////////////////////////////////////////////////////////
+void MultitexReducer::
+scan(PandaNode *node, const RenderState *state, const TransformState *transform) {
+  CPT(RenderState) next_state = node->get_state()->compose(state);
+  CPT(TransformState) next_transform = node->get_transform()->compose(transform);
+
+  if (node->is_geom_node()) {
+    scan_geom_node(DCAST(GeomNode, node), next_state, next_transform);
+  }
+
+  PandaNode::Children cr = node->get_children();
+  int num_children = cr.get_num_children();
+  for (int i = 0; i < num_children; i++) {
+    scan(cr.get_child(i), next_state, next_transform);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::set_target
+//       Access: Published
+//  Description: Specifies the target TextureStage (and TexCoordName)
+//               that will be left on each multitexture node after the
+//               flatten operation has completed.
+////////////////////////////////////////////////////////////////////
+void MultitexReducer::
+set_target(TextureStage *stage) {
+  _target_stage = stage;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::flatten
+//       Access: Published
+//  Description: Actually performs the reducing operations on the
+//               nodes that were previously scanned.
+//
+//               A window that can be used to create texture buffers
+//               suitable for rendering this geometry must be
+//               supplied.  This specifies the particular GSG that
+//               will be used to composite the textures.
+////////////////////////////////////////////////////////////////////
+void MultitexReducer::
+flatten(GraphicsOutput *window) {
+  if (grutil_cat.is_debug()) {
+    grutil_cat.debug()
+      << "Beginning flatten operation\n";
+    Stages::const_iterator mi;
+    for (mi = _stages.begin(); mi != _stages.end(); ++mi) {
+      const StageList &stage_list = (*mi).first;
+      const GeomList &geom_list = (*mi).second;
+      grutil_cat.debug(false)
+        << "stage_list for:";
+      for (GeomList::const_iterator gi = geom_list.begin();
+           gi != geom_list.end(); 
+           ++gi) {
+        const GeomInfo &geom_info = (*gi);
+        grutil_cat.debug(false)
+          << " (" << geom_info._geom_node->get_name() << " g" 
+          << geom_info._index << ")";
+      }
+      grutil_cat.debug(false) << ":\n";
+
+      StageList::const_iterator si;
+      for (si = stage_list.begin(); si != stage_list.end(); ++si) {
+        const StageInfo &stage_info = (*si);
+        grutil_cat.debug(false)
+          << "  " << *stage_info._stage << " " << *stage_info._tex
+          << " " << *stage_info._tex_mat << "\n";
+      }
+    }
+  }
+
+  Stages::const_iterator mi;
+  for (mi = _stages.begin(); mi != _stages.end(); ++mi) {
+    const StageList &stage_list = (*mi).first;
+    const GeomList &geom_list = (*mi).second;
+
+    // Create an offscreen buffer in which to render the new texture.
+    int x_size, y_size, aniso_degree;
+    Texture::FilterType minfilter, magfilter;
+    determine_size(x_size, y_size, aniso_degree,
+                   minfilter, magfilter, stage_list);
+
+    GraphicsOutput *buffer = 
+      window->make_texture_buffer("multitex", x_size, y_size);
+    buffer->set_one_shot(true);
+    Texture *tex = buffer->get_texture();
+    tex->set_anisotropic_degree(aniso_degree);
+    tex->set_minfilter(minfilter);
+    tex->set_magfilter(magfilter);
+
+    // Set up the offscreen buffer to render 0,0 to 1,1.  This will be
+    // the whole texture, but nothing outside the texture.
+    GraphicsChannel *chan = buffer->get_channel(0);
+    GraphicsLayer *layer = chan->make_layer(0);
+    DisplayRegion *dr = layer->make_display_region();
+    PT(Camera) cam_node = new Camera("multitexCam");
+    PT(Lens) lens = new OrthographicLens();
+    lens->set_film_size(1.0f, 1.0f);
+    lens->set_film_offset(0.5f, 0.5f);
+    lens->set_near_far(-1000.0f, 1000.0f);
+    cam_node->set_lens(lens);
+
+    // Create a root node for the buffer's scene graph, and set up
+    // some appropriate properties for it.
+    NodePath render("buffer");
+    cam_node->set_scene(render);
+    render.set_bin("unsorted", 0);
+    render.set_depth_test(false);
+    render.set_depth_write(false);
+
+    NodePath cam = render.attach_new_node(cam_node);
+    dr->set_camera(cam);
+
+    // Put one plain white card in the background for the first
+    // texture layer to apply onto.
+    CardMaker cm("background");
+    cm.set_frame(0.0f, 1.0f, 0.0f, 1.0f);
+    render.attach_new_node(cm.generate());
+
+    StageList::const_iterator si;
+    for (si = stage_list.begin(); si != stage_list.end(); ++si) {
+      const StageInfo &stage_info = (*si);
+
+      make_texture_layer(render, stage_info, geom_list);
+    }
+
+    // Now modify the geometry to apply the new texture, instead of
+    // the old multitexture.
+    CPT(RenderAttrib) new_ta = DCAST(TextureAttrib, TextureAttrib::make())->
+      add_on_stage(_target_stage, tex);
+
+    GeomList::const_iterator gi;
+    for (gi = geom_list.begin(); gi != geom_list.end(); ++gi) {
+      const GeomInfo &geom_info = (*gi);
+      
+      CPT(RenderState) geom_state = 
+        geom_info._geom_node->get_geom_state(geom_info._index);
+      geom_state = geom_state->add_attrib(new_ta);
+      geom_info._geom_node->set_geom_state(geom_info._index, geom_state);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::scan_geom_node
+//       Access: Private
+//  Description: Adds the Geoms in the indicated GeomNode to the
+//               internal database of multitexture elements.
+////////////////////////////////////////////////////////////////////
+void MultitexReducer::
+scan_geom_node(GeomNode *node, const RenderState *state, 
+               const TransformState *transform) {
+  if (grutil_cat.is_debug()) {
+    grutil_cat.debug()
+      << "scan_geom_node(" << *node << ", " << *state << ", "
+      << *transform << ")\n";
+  }
+
+  int num_geoms = node->get_num_geoms();
+  for (int gi = 0; gi < num_geoms; gi++) {
+    CPT(RenderState) geom_net_state = 
+      node->get_geom_state(gi)->compose(state);
+
+    // Get out the net TextureAttrib and TexMatrixAttrib from the state.
+    const RenderAttrib *attrib;
+    const TextureAttrib *ta = NULL;
+
+    attrib = geom_net_state->get_attrib(TextureAttrib::get_class_type());
+    if (attrib != (const RenderAttrib *)NULL) {
+      ta = DCAST(TextureAttrib, attrib);
+    }
+
+    if (ta != (TextureAttrib *)NULL && ta->get_num_on_stages() >= 2) {
+      // Ok, we have multitexture.  Record the Geom.
+      CPT(TexMatrixAttrib) tma = DCAST(TexMatrixAttrib, TexMatrixAttrib::make());
+      attrib = geom_net_state->get_attrib(TexMatrixAttrib::get_class_type());
+      if (attrib != (const RenderAttrib *)NULL) {
+        tma = DCAST(TexMatrixAttrib, attrib);
+      }
+
+      StageList stage_list;
+      
+      int num_stages = ta->get_num_on_stages();
+      for (int si = 0; si < num_stages; si++) {
+        TextureStage *stage = ta->get_on_stage(si);
+        
+        stage_list.push_back(StageInfo(stage, ta, tma));
+      }
+
+      record_stage_list(stage_list, GeomInfo(node, gi));
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::record_stage_list
+//       Access: Private
+//  Description: Adds the record of this one Geom and its associated
+//               StageList.
+////////////////////////////////////////////////////////////////////
+void MultitexReducer::
+record_stage_list(const MultitexReducer::StageList &stage_list, 
+                  const MultitexReducer::GeomInfo &geom_info) {
+  if (grutil_cat.is_debug()) {
+    grutil_cat.debug()
+      << "record_stage_list for " << geom_info._geom_node->get_name() << " g" 
+      << geom_info._index << ":\n";
+    StageList::const_iterator si;
+    for (si = stage_list.begin(); si != stage_list.end(); ++si) {
+      const StageInfo &stage_info = (*si);
+      grutil_cat.debug(false)
+        << "  " << *stage_info._stage << " " << *stage_info._tex
+        << " " << *stage_info._tex_mat << "\n";
+    }
+  }
+
+  _stages[stage_list].push_back(geom_info);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::determine_size
+//       Access: Private
+//  Description: Tries to guess what size to make the new, collapsed
+//               texture based on the sizes of all of the textures
+//               used in the stage_list.
+////////////////////////////////////////////////////////////////////
+void MultitexReducer::
+determine_size(int &x_size, int &y_size, int &aniso_degree,
+               Texture::FilterType &minfilter, Texture::FilterType &magfilter, 
+               const MultitexReducer::StageList &stage_list) const {
+  x_size = 0;
+  y_size = 0;
+  aniso_degree = 0;
+  minfilter = Texture::FT_nearest;
+  magfilter = Texture::FT_nearest;
+
+  StageList::const_iterator si;
+  for (si = stage_list.begin(); si != stage_list.end(); ++si) {
+    const StageInfo &stage_info = (*si);
+    Texture *tex = stage_info._tex;
+    PixelBuffer *pbuffer = tex->_pbuffer;
+
+    if (stage_info._stage == _target_stage) {
+      // If we find the target stage, use that.
+      x_size = pbuffer->get_xsize();
+      y_size = pbuffer->get_ysize();
+      aniso_degree = tex->get_anisotropic_degree();
+      minfilter = tex->get_minfilter();
+      magfilter = tex->get_magfilter();
+      return;
+    }
+
+    // If we never run across the target stage, just use the maximum
+    // of all encountered textures.
+    x_size = max(x_size, pbuffer->get_xsize());
+    y_size = max(y_size, pbuffer->get_ysize());
+    aniso_degree = max(aniso_degree, tex->get_anisotropic_degree());
+    minfilter = (Texture::FilterType)max((int)minfilter, (int)tex->get_minfilter());
+    magfilter = (Texture::FilterType)max((int)magfilter, (int)tex->get_magfilter());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::make_texture_layer
+//       Access: Private
+//  Description: Creates geometry to render the texture into the
+//               offscreen buffer using the same effects that were
+//               requested by its multitexture specification.
+////////////////////////////////////////////////////////////////////
+void MultitexReducer::
+make_texture_layer(const NodePath &render, 
+                   const MultitexReducer::StageInfo &stage_info, 
+                   const MultitexReducer::GeomList &geom_list) {
+  CPT(RenderAttrib) cba;
+
+  switch (stage_info._stage->get_mode()) {
+  case TextureStage::M_modulate:
+    cba = ColorBlendAttrib::make
+      (ColorBlendAttrib::M_add, ColorBlendAttrib::O_fbuffer_color,
+       ColorBlendAttrib::O_zero);
+    break;
+
+  case TextureStage::M_decal:
+    cba = ColorBlendAttrib::make
+      (ColorBlendAttrib::M_add, ColorBlendAttrib::O_incoming_alpha,
+       ColorBlendAttrib::O_one_minus_incoming_alpha);
+    break;
+
+  case TextureStage::M_blend:
+    cba = ColorBlendAttrib::make
+      (ColorBlendAttrib::M_add, ColorBlendAttrib::O_constant_color,
+       ColorBlendAttrib::O_one_minus_incoming_color,
+       stage_info._stage->get_color());
+    break;
+
+  case TextureStage::M_replace:
+    cba = ColorBlendAttrib::make_off();
+    break;
+
+  case TextureStage::M_add:
+    cba = ColorBlendAttrib::make
+      (ColorBlendAttrib::M_add, ColorBlendAttrib::O_one,
+       ColorBlendAttrib::O_one);
+    break;
+
+  case TextureStage::M_combine:
+    // We don't support the texture combiner here right now.  For now,
+    // this is the same as modulate.
+    cba = ColorBlendAttrib::make
+      (ColorBlendAttrib::M_add, ColorBlendAttrib::O_fbuffer_color,
+       ColorBlendAttrib::O_zero);
+    break;
+  }
+
+  NodePath geom;
+
+  if (stage_info._stage->get_texcoord_name() == _target_stage->get_texcoord_name()) {
+    // If this TextureStage uses the target texcoords, we can just
+    // generate a simple card the fills the entire buffer.
+    CardMaker cm(stage_info._tex->get_name());
+    cm.set_uv_range(TexCoordf(0.0f, 0.0f), TexCoordf(1.0f, 1.0f));
+    cm.set_has_uvs(true);
+    cm.set_frame(0.0f, 1.0f, 0.0f, 1.0f);
+    
+    geom = render.attach_new_node(cm.generate());
+
+  } else {
+    // If this TextureStage uses some other texcoords, we have to
+    // generate geometry that maps the texcoords to the target space.
+    // This will work only for very simple cases where the geometry is
+    // not too extensive and doesn't repeat over the same UV's.
+    PT(GeomNode) geom_node = new GeomNode(stage_info._tex->get_name());
+    transfer_geom(geom_node, stage_info._stage->get_texcoord_name(), 
+                  geom_list);
+    
+    geom = render.attach_new_node(geom_node);
+
+    // Make sure we override the vertex color, so we don't pollute the
+    // texture with geometry color.
+    geom.set_color(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
+  }
+
+  if (!stage_info._tex_mat->is_identity()) {
+    geom.set_tex_transform(TextureStage::get_default(), stage_info._tex_mat);
+  }
+
+  geom.set_texture(stage_info._tex);
+  geom.node()->set_attrib(cba);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::transfer_geom
+//       Access: Private
+//  Description: Copy the vertices from the indicated geom_list,
+//               mapping the vertex coordinates so that the geometry
+//               will render the appropriate distortion on the texture
+//               to map UV's from the specified set of texture
+//               coordinates to the target set.
+////////////////////////////////////////////////////////////////////
+void MultitexReducer::
+transfer_geom(GeomNode *geom_node, const TexCoordName *texcoord_name,
+              const MultitexReducer::GeomList &geom_list) {
+  GeomList::const_iterator gi;
+  for (gi = geom_list.begin(); gi != geom_list.end(); ++gi) {
+    const GeomInfo &geom_info = (*gi);
+    
+    PT(Geom) geom = 
+      geom_info._geom_node->get_geom(geom_info._index)->make_copy();
+
+    PTA_Vertexf coords = PTA_Vertexf::empty_array(0);
+    PTA_TexCoordf texcoords = geom->get_texcoords_array(_target_stage->get_texcoord_name());
+    if (!texcoords.empty()) {
+      coords.reserve(texcoords.size());
+      for (size_t i = 0; i < texcoords.size(); i++) {
+        const TexCoordf &tc = texcoords[i];
+        Vertexf v(tc[0], 0.0f, tc[1]);
+        coords.push_back(v);
+      }
+      
+      geom->set_coords(coords, geom->get_texcoords_index(_target_stage->get_texcoord_name()));
+      geom->set_texcoords(TexCoordName::get_default(),
+                          geom->get_texcoords_array(texcoord_name),
+                          geom->get_texcoords_index(texcoord_name));
+      
+      geom_node->add_geom(geom);
+    }
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: MultitexReducer::StageInfo::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MultitexReducer::StageInfo::
+StageInfo(TextureStage *stage, const TextureAttrib *ta, 
+          const TexMatrixAttrib *tma) :
+  _stage(stage),
+  _tex_mat(TransformState::make_identity())
+{
+  _tex = ta->get_on_texture(_stage);
+  if (tma->has_stage(stage)) {
+    _tex_mat = tma->get_transform(stage);
+  }
+}

+ 119 - 0
panda/src/grutil/multitexReducer.h

@@ -0,0 +1,119 @@
+// Filename: multitexReducer.h
+// Created by:  drose (30Nov04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef MULTITEXREDUCER_H
+#define MULTITEXREDUCER_H
+
+#include "pandabase.h"
+#include "texture.h"
+#include "textureAttrib.h"
+#include "textureStage.h"
+#include "texMatrixAttrib.h"
+#include "transformState.h"
+#include "geomNode.h"
+#include "luse.h"
+#include "pointerTo.h"
+#include "pmap.h"
+#include "pvector.h"
+
+class GraphicsOutput;
+class PandaNode;
+class RenderState;
+class TransformState;
+
+///////////////////////////////////////////////////////////////////
+//       Class : MultitexReducer
+// Description : This object presents an interface for generating new
+//               texture images that represent the combined images
+//               from one or more individual textures, reproducing 
+//               certain kinds of multitexture effects without
+//               depending on multitexture support in the hardware.
+//
+//               This also flattens out texture matrices and removes
+//               extra texture coordinates from the Geoms.  It is thus
+//               not a complete substitute for true multitexturing,
+//               because it does not lend itself well to dynamic
+//               animation of the textures once they have been
+//               flattened.  It is, however, useful for "baking in" a
+//               particular multitexture effect.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA MultitexReducer {
+PUBLISHED:
+  MultitexReducer();
+  ~MultitexReducer();
+
+  void clear();
+  void scan(PandaNode *node, const RenderState *state, 
+            const TransformState *transform);
+
+  void set_target(TextureStage *stage);
+
+  void flatten(GraphicsOutput *window);
+
+private:
+  class StageInfo {
+  public:
+    StageInfo(TextureStage *stage, const TextureAttrib *ta, 
+              const TexMatrixAttrib *tma);
+
+    INLINE bool operator < (const StageInfo &other) const;
+
+    PT(TextureStage) _stage;
+    PT(Texture) _tex;
+    CPT(TransformState) _tex_mat;
+  };
+
+  typedef pvector<StageInfo> StageList;
+
+  class GeomInfo {
+  public:
+    INLINE GeomInfo(GeomNode *geom_node, int index);
+
+    PT(GeomNode) _geom_node;
+    int _index;
+  };
+
+  typedef pvector<GeomInfo> GeomList;
+
+  typedef pmap<StageList, GeomList> Stages;
+  Stages _stages;
+
+  PT(TextureStage) _target_stage;
+
+private:
+  void scan_geom_node(GeomNode *node, const RenderState *state, 
+                      const TransformState *transform);
+
+  void record_stage_list(const StageList &stage_list, 
+                         const GeomInfo &geom_info);
+
+  void determine_size(int &x_size, int &y_size, int &aniso_degree,
+                      Texture::FilterType &minfilter, 
+                      Texture::FilterType &magfilter, 
+                      const MultitexReducer::StageList &stage_list) const;
+
+  void make_texture_layer(const NodePath &render, 
+                          const StageInfo &stage_info, 
+                          const GeomList &geom_list);
+  void transfer_geom(GeomNode *geom_node, const TexCoordName *texcoord_name,
+                     const MultitexReducer::GeomList &geom_list);
+};
+
+#include "multitexReducer.I"
+
+#endif

+ 1 - 6
panda/src/pgraph/sceneGraphReducer.cxx

@@ -18,16 +18,11 @@
 
 #include "sceneGraphReducer.h"
 #include "config_pgraph.h"
-#include "colorAttrib.h"
-#include "texMatrixAttrib.h"
-#include "colorScaleAttrib.h"
 #include "accumulatedAttribs.h"
 
-#include "geomNode.h"
 #include "pointerTo.h"
-#include "geom.h"
-#include "indent.h"
 #include "plist.h"
+#include "pmap.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: SceneGraphReducer::flatten

+ 2 - 3
panda/src/pgraph/transformState.cxx

@@ -205,7 +205,7 @@ TransformState::
 bool TransformState::
 operator < (const TransformState &other) const {
   static const int significant_flags = 
-    (F_is_invalid | F_is_identity | F_components_given | F_hpr_given);
+    (F_is_invalid | F_is_identity | F_components_given | F_hpr_given | F_quat_given);
 
   int flags = (_flags & significant_flags);
   int other_flags = (other._flags & significant_flags);
@@ -219,8 +219,7 @@ operator < (const TransformState &other) const {
     return 0;
   }
 
-  if ((_flags & (F_components_given | F_hpr_given | F_quat_given)) == 
-      (F_components_given | F_hpr_given | F_quat_given)) {
+  if ((_flags & F_components_given) != 0) {
     // If the transform was specified componentwise, compare them
     // componentwise.
     int c = _pos.compare_to(other._pos);

+ 23 - 0
panda/src/testbed/pview.cxx

@@ -18,6 +18,8 @@
 
 #include "pandaFramework.h"
 #include "textNode.h"
+#include "multitexReducer.h"
+#include "configVariableBool.h"
 
 #ifndef HAVE_GETOPT
   #include "gnu_getopt.h"
@@ -29,6 +31,11 @@
 
 PandaFramework framework;
 
+ConfigVariableBool pview_test_hack
+("pview-test-hack", false,
+ "Enable the '0' key in pview to run whatever hacky test happens to be in "
+ "there right now.");
+
 void
 event_W(CPT_Event, void *) {
   // shift-W: open a new window on the same scene.
@@ -98,6 +105,19 @@ event_2(CPT_Event event, void *) {
   }
 }
 
+void
+event_0(CPT_Event event, void *) {
+  // 0: run hacky test.
+  MultitexReducer mr;
+
+  NodePath models = framework.get_models();
+  mr.scan(models.node(), models.get_net_state(), models.get_net_transform());
+
+  WindowFramework *wf = framework.get_window(0);
+  GraphicsWindow *win = wf->get_graphics_window();
+  mr.flatten(win);
+}
+
 void 
 usage() {
   cerr <<
@@ -210,6 +230,9 @@ main(int argc, char *argv[]) {
     framework.define_key("shift-w", "open a new window", event_W, NULL);
     framework.define_key("alt-enter", "toggle between window/fullscreen", event_Enter, NULL);
     framework.define_key("2", "split the window", event_2, NULL);
+    if (pview_test_hack) {
+      framework.define_key("0", "run quick hacky test", event_0, NULL);
+    }
     framework.main_loop();
     framework.report_frame_rate(nout);
   }